// 16 august 2014

#include "winapi_windows.h"
#include "_cgo_export.h"

// TODO eliminate duplicate code
// TODO top left pixel of checkbox state 0 not drawn?

HBITMAP unscaledBitmap(void *i, intptr_t dx, intptr_t dy)
{
	BITMAPINFO bi;
	VOID *ppvBits;
	HBITMAP bitmap;

	ZeroMemory(&bi, sizeof (BITMAPINFO));
	bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
	bi.bmiHeader.biWidth = (LONG) dx;
	bi.bmiHeader.biHeight = -((LONG) dy);			// negative height to force top-down drawing;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;
	bi.bmiHeader.biSizeImage = (DWORD) (dx * dy * 4);
	bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
	if (bitmap == NULL)
		xpanic("error creating HBITMAP for unscaled ImageList image copy", GetLastError());
	dotoARGB(i, (void *) ppvBits);
	return bitmap;
}

HIMAGELIST newImageList(int width, int height)
{
	HIMAGELIST il;

	// TODO does this strip alpha?
	// sinni800 in irc.freenode.net/#go-nuts suggests our use of *image.RGBA makes this not so much of an issue
	il = (*fv_ImageList_Create)(width, height, ILC_COLOR32, 20, 20);		// should be reasonable
	if (il == NULL)
		xpanic("error creating image list", GetLastError());
	return il;
}

void addImage(HIMAGELIST il, HWND hwnd, HBITMAP bitmap, int origwid, int oright, int width, int height)
{
	BOOL wasScaled = FALSE;
	HDC winDC, scaledDC, origDC;
	HBITMAP scaled;
	HBITMAP prevscaled, prevorig;

	// first we need to scale the bitmap
	if (origwid == width && oright == height) {
		scaled = bitmap;
		goto noscale;
	}
	wasScaled = TRUE;
	winDC = GetDC(hwnd);
	if (winDC == NULL)
		xpanic("error getting DC for window", GetLastError());
	origDC = CreateCompatibleDC(winDC);
	if (origDC == NULL)
		xpanic("error getting DC for original ImageList bitmap", GetLastError());
	prevorig = SelectObject(origDC, bitmap);
	if (prevorig == NULL)
		xpanic("error selecting original ImageList bitmap into DC", GetLastError());
	scaledDC = CreateCompatibleDC(origDC);
	if (scaledDC == NULL)
		xpanic("error getting DC for scaled ImageList bitmap", GetLastError());
	scaled = CreateCompatibleBitmap(origDC, width, height);
	if (scaled == NULL)
		xpanic("error creating scaled ImageList bitmap", GetLastError());
	prevscaled = SelectObject(scaledDC, scaled);
	if (prevscaled == NULL)
		xpanic("error selecting scaled ImageList bitmap into DC", GetLastError());
	if (SetStretchBltMode(scaledDC, COLORONCOLOR) == 0)
		xpanic("error setting scaling mode", GetLastError());
	if (StretchBlt(scaledDC, 0, 0, width, height,
		origDC, 0, 0, origwid, oright,
		SRCCOPY) == 0)
		xpanic("error scaling ImageList bitmap down", GetLastError());
	if (SelectObject(origDC, prevorig) != bitmap)
		xpanic("error selecting previous bitmap into original image's DC", GetLastError());
	if (DeleteDC(origDC) == 0)
		xpanic("error deleting original image's DC", GetLastError());
	if (SelectObject(scaledDC, prevscaled) != scaled)
		xpanic("error selecting previous bitmap into scaled image's DC", GetLastError());
	if (DeleteDC(scaledDC) == 0)
		xpanic("error deleting scaled image's DC", GetLastError());
	if (ReleaseDC(hwnd, winDC) == 0)
		xpanic("error deleting window DC", GetLastError());

noscale:
	if ((*fv_ImageList_Add)(il, scaled, NULL) == -1)
		xpanic("error adding ImageList image to image list", GetLastError());
	if (wasScaled)		// clean up
		if (DeleteObject(scaled) == 0)
			xpanic("error deleting scaled bitmap", GetLastError());
}

void applyImageList(HWND hwnd, UINT uMsg, WPARAM wParam, HIMAGELIST il)
{
	if (SendMessageW(hwnd, uMsg, wParam, (LPARAM) il) == (LRESULT) NULL)
;//TODO		xpanic("error setting image list", GetLastError());
	// TODO free old one here if any
}

static UINT dfcState(int cbstate)
{
	UINT ret;

	ret = DFCS_BUTTONCHECK;
	if ((cbstate & checkboxStateChecked) != 0)
		ret |= DFCS_CHECKED;
	if ((cbstate & checkboxStateHot) != 0)
		ret |= DFCS_HOT;
	if ((cbstate & checkboxStatePushed) != 0)
		ret |= DFCS_PUSHED;
	return ret;
}

static HBITMAP dfcImage(HDC dc, int width, int height, int cbState)
{
	BITMAPINFO bi;
	VOID *ppvBits;
	HBITMAP bitmap;
	RECT r;
	HDC drawDC;
	HBITMAP prevbitmap;

	r.left = 0;
	r.top = 0;
	r.right = width;
	r.bottom = height;
	ZeroMemory(&bi, sizeof (BITMAPINFO));
	bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
	bi.bmiHeader.biWidth = (LONG) width;
	bi.bmiHeader.biHeight = -((LONG) height);			// negative height to force top-down drawing;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;
	bi.bmiHeader.biSizeImage = (DWORD) (width * height * 4);
	bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
	if (bitmap == NULL)
		xpanic("error creating HBITMAP for unscaled ImageList image copy", GetLastError());

	drawDC = CreateCompatibleDC(dc);
	if (drawDC == NULL)
		xpanic("error getting DC for checkbox image list bitmap", GetLastError());
	prevbitmap = SelectObject(drawDC, bitmap);
	if (prevbitmap == NULL)
		xpanic("error selecting checkbox image list bitmap into DC", GetLastError());
	if (DrawFrameControl(drawDC, &r, DFC_BUTTON, dfcState(cbState)) == 0)
		xpanic("error drawing checkbox image", GetLastError());
	if (SelectObject(drawDC, prevbitmap) != bitmap)
		xpanic("error selecting previous bitmap into checkbox image's DC", GetLastError());
	if (DeleteDC(drawDC) == 0)
		xpanic("error deleting checkbox image's DC", GetLastError());

	return bitmap;
}

HIMAGELIST checkboxImageList = NULL;

void makeCheckboxImageList_DFC(HWND hwnddc)
{
	int width, height;
	int cbState;
	HDC dc;
	HIMAGELIST il;

	// there's no real metric around
	// let's use SM_CX/YSMICON and hope for the best
	width = GetSystemMetrics(SM_CXSMICON);
	height = GetSystemMetrics(SM_CYSMICON);
	dc = GetDC(hwnddc);
	if (dc == NULL)
		xpanic("error getting DC for making the checkbox image list", GetLastError());
	checkboxImageList = (*fv_ImageList_Create)(width, height, ILC_COLOR32, 20, 20);		// should be reasonable
	if (checkboxImageList == NULL)
		xpanic("error creating checkbox image list", GetLastError());
	for (cbState = 0; cbState < checkboxnStates; cbState++) {
		HBITMAP bitmap;

		bitmap = dfcImage(dc, width, height, cbState);
		if ((*fv_ImageList_Add)(checkboxImageList, bitmap, NULL) == -1)
			xpanic("error adding checkbox image to image list", GetLastError());
		if (DeleteObject(bitmap) == 0)
			xpanic("error deleting checkbox bitmap", GetLastError());
	}
	if (ReleaseDC(hwnddc, dc) == 0)
		xpanic("error deleting checkbox image list DC", GetLastError());
}

static HTHEME theme = NULL;

static void openTheme(HWND hwnd)
{
	if (theme != NULL)
		// TODO save HRESULT
		if (CloseThemeData(theme) != S_OK)
			xpanic("error closing theme", GetLastError());
	// ignore error; if it can't be done, we can fall back to DrawFrameControl()
	theme = OpenThemeData(hwnd, L"button");
}

static int themestates[checkboxnStates] = {
	CBS_UNCHECKEDNORMAL,			// 0
	CBS_CHECKEDNORMAL,				// checked
	CBS_UNCHECKEDHOT,				// hot
	CBS_CHECKEDHOT,					// checked | hot
	CBS_UNCHECKEDPRESSED,			// pushed
	CBS_CHECKEDPRESSED,				// checked | pushed
	CBS_UNCHECKEDPRESSED,			// hot | pushed
	CBS_CHECKEDPRESSED,				// checked | hot | pushed
};

static SIZE getStateSize(HDC dc, int cbState)
{
	SIZE s;

	// TODO use HRESULT
	if (GetThemePartSize(theme, dc, BP_CHECKBOX, themestates[cbState], NULL, TS_DRAW, &s) != S_OK)
		xpanic("error getting theme part size", GetLastError());
	return s;
}

static HBITMAP drawThemeImage(HDC dc, int width, int height, int cbState)
{
	BITMAPINFO bi;
	VOID *ppvBits;
	HBITMAP bitmap;
	RECT r;
	HDC drawDC;
	HBITMAP prevbitmap;

	r.left = 0;
	r.top = 0;
	r.right = width;
	r.bottom = height;
	ZeroMemory(&bi, sizeof (BITMAPINFO));
	bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
	bi.bmiHeader.biWidth = (LONG) width;
	bi.bmiHeader.biHeight = -((LONG) height);			// negative height to force top-down drawing;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;
	bi.bmiHeader.biCompression = BI_RGB;
	bi.bmiHeader.biSizeImage = (DWORD) (width * height * 4);
	bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &ppvBits, 0, 0);
	if (bitmap == NULL)
		xpanic("error creating HBITMAP for unscaled ImageList image copy", GetLastError());

	drawDC = CreateCompatibleDC(dc);
	if (drawDC == NULL)
		xpanic("error getting DC for checkbox image list bitmap", GetLastError());
	prevbitmap = SelectObject(drawDC, bitmap);
	if (prevbitmap == NULL)
		xpanic("error selecting checkbox image list bitmap into DC", GetLastError());
	// TODO get HRESULT
	if (DrawThemeBackground(theme, drawDC, BP_CHECKBOX, themestates[cbState], &r, NULL) != S_OK)
		xpanic("error drawing checkbox image", GetLastError());
	if (SelectObject(drawDC, prevbitmap) != bitmap)
		xpanic("error selecting previous bitmap into checkbox image's DC", GetLastError());
	if (DeleteDC(drawDC) == 0)
		xpanic("error deleting checkbox image's DC", GetLastError());

	return bitmap;
}


void makeCheckboxImageList_theme(HWND hwnddc)
{
	int width, height;
	int cbState;
	SIZE size;
	HDC dc;

	// first, make sure that all things have the same size
	dc = GetDC(hwnddc);
	if (dc == NULL)
		xpanic("error getting DC for making the checkbox image list", GetLastError());
	size = getStateSize(dc, 0);
	for (cbState = 1; cbState < checkboxnStates; cbState++) {
		SIZE against;

		against = getStateSize(dc, cbState);
		if (size.cx != against.cx || size.cy != against.cy)
			xpanic("size mismatch in checkbox states", GetLastError());
	}
	width = (int) size.cx;
	height = (int) size.cy;

	// NOW draw
	checkboxImageList = (*fv_ImageList_Create)(width, height, ILC_COLOR32, 20, 20);		// should be reasonable
	if (checkboxImageList == NULL)
		xpanic("error creating checkbox image list", GetLastError());
	for (cbState = 0; cbState < checkboxnStates; cbState++) {
		HBITMAP bitmap;

		bitmap = drawThemeImage(dc, width, height, cbState);
		if ((*fv_ImageList_Add)(checkboxImageList, bitmap, NULL) == -1)
			xpanic("error adding checkbox image to image list", GetLastError());
		if (DeleteObject(bitmap) == 0)
			xpanic("error deleting checkbox bitmap", GetLastError());
	}
	if (ReleaseDC(hwnddc, dc) == 0)
		xpanic("error deleting checkbox image list DC", GetLastError());
}

void makeCheckboxImageList(HWND hwnddc)
{
	if (theme == NULL)		// try to open the theme
		openTheme(hwnddc);
	if (theme != NULL) {		// use the theme
		makeCheckboxImageList_theme(hwnddc);
		return;
	}
	// couldn't open; fall back
	makeCheckboxImageList_DFC(hwnddc);
}