// 17 april 2016
#include "uipriv_windows.hpp"

// The Direct2D scratch window is a utility for libui internal use to do quick things with Direct2D.
// To use, call newD2DScratch() passing in a subclass procedure. This subclass procedure should handle the msgD2DScratchPaint message, which has the following usage:
// - wParam - 0
// - lParam - ID2D1RenderTarget *
// - lResult - 0
// You can optionally also handle msgD2DScratchLButtonDown, which is sent when the left mouse button is either pressed for the first time or held while the mouse is moving.
// - wParam - position in DIPs, as D2D1_POINT_2F *
// - lParam - size of render target in DIPs, as D2D1_SIZE_F *
// - lResult - 0
// Other messages can also be handled here.

// TODO allow resize

#define d2dScratchClass L"libui_d2dScratchClass"

// TODO clip rect
static HRESULT d2dScratchDoPaint(HWND hwnd, ID2D1RenderTarget *rt)
{
	COLORREF bgcolorref;
	D2D1_COLOR_F bgcolor;

	rt->BeginDraw();

	// TODO only clear the clip area
	// TODO clear with actual background brush
	bgcolorref = GetSysColor(COLOR_BTNFACE);
	bgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0;
	// due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks
	// it has not worked since 2008 and they have *never* fixed it
	// TODO now that -RTCc has just been deprecated entirely, should we switch back?
	bgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0;
	bgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0;
	bgcolor.a = 1.0;
	rt->Clear(&bgcolor);

	SendMessageW(hwnd, msgD2DScratchPaint, 0, (LPARAM) rt);

	return rt->EndDraw(NULL, NULL);
}

static void d2dScratchDoLButtonDown(HWND hwnd, ID2D1RenderTarget *rt, LPARAM lParam)
{
	double xpix, ypix;
	FLOAT dpix, dpiy;
	D2D1_POINT_2F pos;
	D2D1_SIZE_F size;

	xpix = (double) GET_X_LPARAM(lParam);
	ypix = (double) GET_Y_LPARAM(lParam);
	// these are in pixels; we need points
	// TODO separate the function from areautil.cpp?
	rt->GetDpi(&dpix, &dpiy);
	pos.x = (xpix * 96) / dpix;
	pos.y = (ypix * 96) / dpiy;

	size = realGetSize(rt);

	SendMessageW(hwnd, msgD2DScratchLButtonDown, (WPARAM) (&pos), (LPARAM) (&size));
}

static LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LONG_PTR init;
	ID2D1HwndRenderTarget *rt;
	ID2D1DCRenderTarget *dcrt;
	RECT client;
	HRESULT hr;

	init = GetWindowLongPtrW(hwnd, 0);
	if (!init) {
		if (uMsg == WM_CREATE)
			SetWindowLongPtrW(hwnd, 0, (LONG_PTR) TRUE);
		return DefWindowProcW(hwnd, uMsg, wParam, lParam);
	}

	rt = (ID2D1HwndRenderTarget *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
	if (rt == NULL) {
		rt = makeHWNDRenderTarget(hwnd);
		SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) rt);
	}

	switch (uMsg) {
	case WM_DESTROY:
		rt->Release();
		SetWindowLongPtrW(hwnd, 0, (LONG_PTR) FALSE);
		break;
	case WM_PAINT:
		hr = d2dScratchDoPaint(hwnd, rt);
		switch (hr) {
		case S_OK:
			if (ValidateRect(hwnd, NULL) == 0)
				logLastError(L"error validating D2D scratch control rect");
			break;
		case (HRESULT)D2DERR_RECREATE_TARGET:
			// DON'T validate the rect
			// instead, simply drop the render target
			// we'll get another WM_PAINT and make the render target again
			// TODO would this require us to invalidate the entire client area?
			rt->Release();
			SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) NULL);
			break;
		default:
			logHRESULT(L"error drawing D2D scratch window", hr);
		}
		return 0;
	case WM_PRINTCLIENT:
		uiWindowsEnsureGetClientRect(hwnd, &client);
		dcrt = makeHDCRenderTarget((HDC) wParam, &client);
		hr = d2dScratchDoPaint(hwnd, dcrt);
		if (hr != S_OK)
			logHRESULT(L"error printing D2D scratch window client area", hr);
		dcrt->Release();
		return 0;
	case WM_LBUTTONDOWN:
		d2dScratchDoLButtonDown(hwnd, rt, lParam);
		return 0;
	case WM_MOUSEMOVE:
		// also send LButtonDowns when dragging
		if ((wParam & MK_LBUTTON) != 0)
			d2dScratchDoLButtonDown(hwnd, rt, lParam);
		return 0;
	}
	return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

ATOM registerD2DScratchClass(HICON hDefaultIcon, HCURSOR hDefaultCursor)
{
	WNDCLASSW wc;

	ZeroMemory(&wc, sizeof (WNDCLASSW));
	wc.lpszClassName = d2dScratchClass;
	wc.lpfnWndProc = d2dScratchWndProc;
	wc.hInstance = hInstance;
	wc.hIcon = hDefaultIcon;
	wc.hCursor = hDefaultCursor;
	wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
	wc.cbWndExtra = sizeof (LONG_PTR);			// for the init status
	return RegisterClassW(&wc);
}

void unregisterD2DScratchClass(void)
{
	if (UnregisterClassW(d2dScratchClass, hInstance) == 0)
		logLastError(L"error unregistering D2D scratch window class");
}

HWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC subclass, DWORD_PTR subclassData)
{
	HWND hwnd;

	hwnd = CreateWindowExW(0,
		d2dScratchClass, L"",
		WS_CHILD | WS_VISIBLE,
		rect->left, rect->top,
		rect->right - rect->left, rect->bottom - rect->top,
		parent, controlID, hInstance, NULL);
	if (hwnd == NULL)
		// TODO return decoy window
		logLastError(L"error creating D2D scratch window");
	if (SetWindowSubclass(hwnd, subclass, 0, subclassData) == FALSE)
		logLastError(L"error subclassing D2D scratch window");
	return hwnd;
}
