/* $Id$ */
#include <Mw/Milsko.h>

typedef struct userdata {
	MwLL	ll;
	WNDPROC old;
} userdata_t;

static LRESULT CALLBACK wndproc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
	userdata_t* u = (userdata_t*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

	if(u == NULL) return DefWindowProc(hWnd, msg, wp, lp);

	if(msg == WM_PAINT) {
		PAINTSTRUCT ps;
		RECT	    rc;
		HBITMAP	    hbmp;
		HDC	    dc, hbdc;

		if(u->ll->copy_buffer) {
			GetClientRect(hWnd, &rc);

			dc   = GetDC(hWnd);
			hbmp = CreateCompatibleBitmap(dc, rc.right - rc.left, rc.bottom - rc.top);
			hbdc = CreateCompatibleDC(dc);
			SelectObject(hbdc, hbmp);
			ReleaseDC(hWnd, dc);

			u->ll->hDC = hbdc;
			MwLLDispatch(u->ll, draw, NULL);

			dc = BeginPaint(hWnd, &ps);
			StretchBlt(dc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hbdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SRCCOPY);
			EndPaint(hWnd, &ps);

			DeleteDC(hbdc);
			DeleteObject(hbmp);
		} else {
			u->ll->hDC = BeginPaint(hWnd, &ps);
			MwLLDispatch(u->ll, draw, NULL);
			EndPaint(hWnd, &ps);
		}
	} else if(msg == WM_LBUTTONDOWN) {
		MwPoint p;
		p.x = LOWORD(lp);
		p.y = HIWORD(lp);

		SetCapture(hWnd);
		MwLLDispatch(u->ll, down, &p);
	} else if(msg == WM_LBUTTONUP) {
		MwPoint p;
		p.x = LOWORD(lp);
		p.y = HIWORD(lp);

		SetCapture(NULL);
		MwLLDispatch(u->ll, up, &p);
	} else if(msg == WM_MOUSEMOVE) {
		MwPoint p;
		p.x = LOWORD(lp);
		p.y = HIWORD(lp);

		MwLLDispatch(u->ll, move, &p);
	} else if(msg == WM_SIZE) {
		MwLLDispatch(u->ll, resize, NULL);
	} else if(msg == WM_ERASEBKGND) {
		return 1;
	} else if(msg == WM_NCHITTEST) {
		return HTCLIENT;
	} else if(msg == WM_DESTROY) {
		MwLLDispatch(u->ll, close, NULL);
		PostQuitMessage(0);
	} else if(msg == WM_CLOSE) {
		DestroyWindow(hWnd);
	} else {
		return (u->old == NULL) ? DefWindowProc(hWnd, msg, wp, lp) : CallWindowProc(u->old, hWnd, msg, wp, lp);
	}
	return 0;
}

MwLL MwLLCreate(MwLL parent, int x, int y, int width, int height) {
	MwLL	    r = malloc(sizeof(*r));
	userdata_t* u = malloc(sizeof(*u));
	WNDCLASSEX  wc;

	memset(&wc, 0, sizeof(wc));
	wc.cbSize	 = sizeof(WNDCLASSEX);
	wc.style	 = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc	 = wndproc;
	wc.cbClsExtra	 = 0;
	wc.cbWndExtra	 = 0;
	wc.hInstance	 = (HINSTANCE)GetModuleHandle(NULL);
	wc.lpszClassName = "milsko";
	wc.lpszMenuName	 = NULL;
	wc.hCursor	 = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = GetSysColorBrush(COLOR_MENU);
	wc.hIcon	 = LoadIcon(NULL, IDI_WINLOGO);
	wc.hIconSm	 = NULL;

	MwLLCreateCommon(r);

	RegisterClassEx(&wc);

	r->copy_buffer = 1;
	r->hWnd	       = CreateWindow(parent == NULL ? "milsko" : "STATIC", "Milsko", parent == NULL ? (WS_OVERLAPPEDWINDOW) : (WS_CHILD | WS_VISIBLE), x, y, width, height, parent == NULL ? NULL : parent->hWnd, 0, wc.hInstance, NULL);
	r->hInstance   = wc.hInstance;

	u->ll = r;
	if(parent == NULL) {
		u->old = NULL;
	} else {
		u->old = (WNDPROC)SetWindowLongPtr(r->hWnd, GWLP_WNDPROC, (LPARAM)wndproc);
	}
	SetWindowLongPtr(r->hWnd, GWLP_USERDATA, (LPARAM)u);

	if(parent == NULL) {
		RECT rc;

		rc.left	  = 0;
		rc.top	  = 0;
		rc.right  = width;
		rc.bottom = height;
		AdjustWindowRect(&rc, GetWindowLongPtr(r->hWnd, GWL_STYLE), FALSE);
		SetWindowPos(r->hWnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE);

		ShowWindow(r->hWnd, SW_NORMAL);
		UpdateWindow(r->hWnd);

		InvalidateRect(r->hWnd, NULL, FALSE);
	}

	return r;
}

void MwLLDestroy(MwLL handle) {
	MwLLDestroyCommon(handle);

	/* for safety */
	SetWindowLongPtr(handle->hWnd, GWLP_USERDATA, (LPARAM)NULL);
	DestroyWindow(handle->hWnd);

	free(handle);
}

void MwLLPolygon(MwLL handle, MwPoint* points, int points_count, MwLLColor color) {
	POINT* p   = malloc(sizeof(*p) * points_count);
	HPEN   pen = CreatePen(PS_NULL, 0, RGB(0, 0, 0));
	int    i;

	for(i = 0; i < points_count; i++) {
		p[i].x = points[i].x;
		p[i].y = points[i].y;
	}
	SelectObject(handle->hDC, color->brush);
	SelectObject(handle->hDC, pen);
	SetPolyFillMode(handle->hDC, ALTERNATE);
	Polygon(handle->hDC, p, points_count);

	DeleteObject(pen);

	free(p);
}

MwLLColor MwLLAllocColor(MwLL handle, int r, int g, int b) {
	MwLLColor c  = malloc(sizeof(*c));
	HDC	  dc = GetDC(handle->hWnd);

	if(r > 255) r = 255;
	if(g > 255) g = 255;
	if(b > 255) b = 255;
	if(r < 0) r = 0;
	if(g < 0) g = 0;
	if(b < 0) b = 0;

	c->brush = CreateSolidBrush(GetNearestColor(dc, RGB(r, g, b)));
	c->red	 = r;
	c->green = g;
	c->blue	 = b;

	ReleaseDC(handle->hWnd, dc);

	return c;
}

void MwLLFreeColor(MwLLColor color) {
	DeleteObject(color->brush);

	free(color);
}

void MwLLGetXYWH(MwLL handle, int* x, int* y, unsigned int* w, unsigned int* h) {
	RECT rc;

	GetClientRect(handle->hWnd, &rc);
	MapWindowPoints(handle->hWnd, GetParent(handle->hWnd), (LPPOINT)&rc, 2);

	*x = rc.left;
	*y = rc.top;
	*w = rc.right - rc.left;
	*h = rc.bottom - rc.top;
}

void MwLLSetXY(MwLL handle, int x, int y) {
	SetWindowPos(handle->hWnd, NULL, x, y, 0, 0, SWP_NOSIZE);
	InvalidateRect(handle->hWnd, NULL, FALSE);
}

void MwLLSetWH(MwLL handle, int w, int h) {
	SetWindowPos(handle->hWnd, NULL, 0, 0, w, h, SWP_NOMOVE);
	InvalidateRect(handle->hWnd, NULL, FALSE);
}

void MwLLSetTitle(MwLL handle, const char* title) {
	SetWindowText(handle->hWnd, title);
}

int MwLLPending(MwLL handle) {
	MSG msg;

	(void)handle;

	return PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) ? 1 : 0;
}

void MwLLNextEvent(MwLL handle) {
	MSG msg;

	(void)handle;

	while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
		GetMessage(&msg, NULL, 0, 0);
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

void MwLLSleep(int ms) {
	Sleep(ms);
}

MwLLPixmap MwLLCreatePixmap(MwLL handle, unsigned char* data, int width, int height) {
	MwLLPixmap	 r  = malloc(sizeof(*r));
	HDC		 dc = GetDC(handle->hWnd);
	BITMAPINFOHEADER bmih;
	RGBQUAD*	 quad = NULL;
	int		 y, x;

	r->width  = width;
	r->height = height;

	bmih.biSize	     = sizeof(bmih);
	bmih.biWidth	     = width;
	bmih.biHeight	     = -(LONG)height;
	bmih.biPlanes	     = 1;
	bmih.biBitCount	     = 32;
	bmih.biCompression   = BI_RGB;
	bmih.biSizeImage     = 0;
	bmih.biXPelsPerMeter = 0;
	bmih.biYPelsPerMeter = 0;
	bmih.biClrUsed	     = 0;
	bmih.biClrImportant  = 0;

	r->hBitmap = CreateDIBSection(dc, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, (void**)&quad, NULL, (DWORD)0);

	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			RGBQUAD*       q  = &quad[y * width + x];
			unsigned char* px = &data[(y * width + x) * 4];
			q->rgbRed	  = px[0];
			q->rgbGreen	  = px[1];
			q->rgbBlue	  = px[2];
		}
	}

	ReleaseDC(handle->hWnd, dc);

	return r;
}

void MwLLDestroyPixmap(MwLLPixmap pixmap) {
	DeleteObject(pixmap->hBitmap);

	free(pixmap);
}

void MwLLDrawPixmap(MwLL handle, MwRect* rect, MwLLPixmap pixmap) {
	HDC hmdc = CreateCompatibleDC(handle->hDC);

	SelectObject(hmdc, pixmap->hBitmap);

	SetStretchBltMode(handle->hDC, HALFTONE);
	StretchBlt(handle->hDC, rect->x, rect->y, rect->width, rect->height, hmdc, 0, 0, pixmap->width, pixmap->height, SRCCOPY);

	DeleteDC(hmdc);
}

void MwLLSetIcon(MwLL handle, MwLLPixmap pixmap) {
	ICONINFO ii;
	HICON	 ico;
	HBITMAP	 mask;

	mask = CreateBitmap(pixmap->width, pixmap->height, 1, 1, NULL);

	memset(&ii, 0, sizeof(ii));
	ii.fIcon    = TRUE;
	ii.xHotspot = 0;
	ii.yHotspot = 0;
	ii.hbmMask  = mask;
	ii.hbmColor = pixmap->hBitmap;

	ico = CreateIconIndirect(&ii);

	DeleteObject(mask);

	SetClassLongPtr(handle->hWnd, GCLP_HICON, (LPARAM)ico);

	DestroyIcon(ico);
}

void MwLLForceRender(MwLL handle) {
	InvalidateRect(handle->hWnd, NULL, FALSE);
}

void MwLLSetCursor(MwLL handle, MwCursor* image, MwCursor* mask) {
	HCURSOR cursor;
	BYTE*	dmask  = malloc((MwCursorDataHeight / 8) * MwCursorDataHeight);
	BYTE*	dimage = malloc((MwCursorDataHeight / 8) * MwCursorDataHeight);
	int	y, x, ys, xs;

	xs = -mask->x + image->x;
	ys = MwCursorDataHeight + mask->y;
	ys = MwCursorDataHeight + image->y - ys;

	memset(dmask, 0xff, (MwCursorDataHeight / 8) * MwCursorDataHeight);
	memset(dimage, 0, (MwCursorDataHeight / 8) * MwCursorDataHeight);

	for(y = 0; y < mask->height; y++) {
		BYTE*	     l = &dmask[y * (MwCursorDataHeight / 8)];
		unsigned int n = mask->data[y];
		for(x = mask->width - 1; x >= 0; x--) {
			l[x / 8] = l[x / 8] >> 1;
			if(!(n & 1)) {
				l[x / 8] |= 1 << 7;
			}

			n = n >> 1;
		}
	}

	for(y = 0; y < image->height; y++) {
		BYTE*	     l = &dimage[(y + ys) * (MwCursorDataHeight / 8)];
		unsigned int n = image->data[y];

		for(x = image->width - 1; x >= 0; x--) {
			if(n & 1) {
				l[(x + xs) / 8] |= (1 << (7 - ((x + xs) % 8)));
			}

			n = n >> 1;
		}
	}

	cursor = CreateCursor(GetModuleHandle(NULL), xs, ys, MwCursorDataHeight, MwCursorDataHeight, dmask, dimage);

	SetClassLongPtr(handle->hWnd, GCLP_HCURSOR, (LPARAM)cursor);
	SetCursor(cursor);

	DestroyCursor(cursor);

	free(dimage);
	free(dmask);
}

void MwLLDetach(MwLL handle, MwPoint* point) {
	RECT   rc, rc2;
	LPARAM style = GetWindowLongPtr(handle->hWnd, GWL_STYLE);

	style |= WS_OVERLAPPEDWINDOW;

	GetWindowRect(GetParent(handle->hWnd), &rc);

	GetClientRect(handle->hWnd, &rc2);

	SetParent(handle->hWnd, NULL);

	rc.left += point->x;
	rc.top += point->y;

	SetWindowPos(handle->hWnd, HWND_TOPMOST, rc.left, rc.top, rc2.right == 0 ? 1 : rc2.right, rc2.bottom == 0 ? 1 : rc2.bottom, SWP_NOREDRAW);

	SetWindowLongPtr(handle->hWnd, GWL_STYLE, style);
}

void MwLLShow(MwLL handle, int show) {
	ShowWindow(handle->hWnd, show ? SW_NORMAL : SW_HIDE);
	if(show) SetFocus(handle->hWnd);
}
