#include <9pm/windows.h>
#undef Rectangle
#define Rectangle Rectangle9pm

#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/draw.h>
#include <9pm/memdraw.h>
#include <9pm/cursor.h>
#include <9pm/ns.h>
#include <9pm/thread.h>
#include <9pm/screen.h>
#include <9pm/keyboard.h>

Memimage	*gscreen;

static int mousequeue=1;
static int depth;
static int xreshaped;
static int dibtype;
static int fmt;

static	HINSTANCE	inst;
static	HWND		window;
static	HPALETTE	palette;
static	LOGPALETTE	*logpal;
static  Lock		gdilock;
static 	BITMAPINFO	*bmi;
static	HCURSOR		hcursor;

static void	winproc(void *);
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
static void	paletteinit(void);
static void	bmiinit(void);
static void	screenload2(Rectangle r, int ldepth, uchar *p, Point pt, int step);

/*
 * Note: Should be using SetClipboardViewer to watch clipboard.
 */
static int ready;
static Rendez	rend;
static int mouseinside;

Point	ZP;

static
isready(void*a)
{
	return ready;
}

static void
mkgscreen(void)
{
	RECT r;
	int dx, dy;

	if(!GetClientRect(window, &r)){
		oserror();
		panic("GetClientRect: %r");
	}
	dx = r.right - r.left;
	dy = r.bottom - r.top;
	gscreen = allocmemimage(Rect(0,0,dx,dy), fmt);
}

void
screeninit(void)
{
	memimageinit();
	depth = GetDeviceCaps(GetDC(NULL), BITSPIXEL);
	switch(depth){
	case 32:
		dibtype = DIB_RGB_COLORS;
		fmt = XRGB32;
		break;
	case 24:
		dibtype = DIB_RGB_COLORS;
		fmt = RGB24;
		break;
	case 16:
		dibtype = DIB_RGB_COLORS;
		fmt = RGB15;	/* [sic] */
		break;
	case 8:
	default:
		dibtype = DIB_PAL_COLORS;
		depth = 8;
		fmt = CMAP8;
		break;
	}
	proccreate(winproc, nil, 32768); 
	rendsleep(&rend, isready, 0);
	mkgscreen();
}

Memimage*
attachscreen(int *softscreen)
{
	screeninit();
	*softscreen = 1;
	drawconsinit();
	return gscreen;
}

Memimage*
reattachscreen(Memimage *m, int *softscreen)
{
	*softscreen = 1;
//	freememimage(m);
	mkgscreen();
	return gscreen;
}

void
detachscreen(Memimage *m)
{
//	freememimage(m);
}

static void
screenload(HDC hdc, Rectangle r)
{
	uchar *p;
	int dx, dy, delx, n, step;
	RECT winr;

	step = gscreen->width*sizeof(ulong);
	/*
	 * Sometimes we do get rectangles that are off the
	 * screen to the negative axes, for example, when
	 * dragging around a window border in a Move operation.
	 */
	if(rectclip(&r, gscreen->r) == 0)
		return;

	if(step&3 != 0)
		panic("screenload: bad params %d", step);

	dx = r.max.x - r.min.x;
	dy = r.max.y - r.min.y;

	if(dx <= 0 || dy <= 0)
		return;

	if(depth == 24)
		delx = r.min.x % 4;
	else
		delx = r.min.x & (31/depth);

	SelectPalette(hdc, palette, 0);
	RealizePalette(hdc);

if(0){	/* DEBUG */
FillRect(hdc,(void*)&r, GetStockObject(BLACK_BRUSH));
GdiFlush();
Sleep(100);
}

	p = byteaddr(gscreen, ZP);
	p += r.min.y*step;
//	p += ((r.min.x-delx)*depth)/8;
	
	bmi->bmiHeader.biWidth = (step*8)/depth;
	bmi->bmiHeader.biHeight = -dy;	/* - => origin upper left */

	n = StretchDIBits(hdc, r.min.x, r.min.y, dx, dy,
		r.min.x, 0, dx, dy, p, bmi, dibtype, SRCCOPY);
	if(n == GDI_ERROR){
		oserror();
		dprint(~0, "StretchDIBits: %r\n");
	}else if(n != dy){
		dprint(~0, "asked for %d lines got %d (delx %d r.min.x %d hdc %p bmidy %d)\n", dy, n, delx, r.min.x, hdc, bmi->bmiHeader.biHeight);
		oserror();
		dprint(~0, "StretchDIBits: %r\n");
		if(!GetClientRect(window, &winr)){
			oserror();
			dprint(~0, "GetClientRect: %r\n");
		}else
			dprint(~0, "GetClientRect %R\n", winr);
	}
	GdiFlush();
}

void
flushmemscreen(Rectangle r)
{
	if(Dx(r) < 0 || Dy(r) < 0)
		return;
	InvalidateRect(window, (CONST RECT*)&r, 0);
}

static void
winproc(void *a)
{
	WNDCLASS wc;
	MSG msg;

	inst = GetModuleHandle(NULL);

	paletteinit();
	bmiinit();

	wc.style = 0;
	wc.lpfnWndProc = WindowProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = inst;
	wc.hIcon = LoadIcon(inst, NULL);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = 0;
	wc.lpszClassName = L"9pmgraphics";
	RegisterClass(&wc);

	window = CreateWindowEx(
		0,			/* extended style */
		L"9pmgraphics",		/* class */
		L"9pm screen",		/* caption */
		WS_OVERLAPPEDWINDOW,    /* style */
		CW_USEDEFAULT,		/* init. x pos */
		CW_USEDEFAULT,		/* init. y pos */
		CW_USEDEFAULT,		/* init. x size */
		CW_USEDEFAULT,		/* init. y size */
		NULL,			/* parent window (actually owner window for overlapped)*/
		NULL,			/* menu handle */
		inst,			/* program handle */
		NULL			/* create parms */
		);

	if(window == nil)
		panic("can't make window\n");

	ShowWindow(window, SW_SHOWDEFAULT);
	UpdateWindow(window);

	ready = 1;
	rendwakeup(&rend);

	while(GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
//	MessageBox(0, "winproc", "exits", MB_OK);
	ExitProcess(0);
}

int
col(int v, int n)
{
	int i, c;

	c = 0;
	for(i = 0; i < 8; i += n)
		c |= v << (16-(n+i));
	return c >> 8;
}


void
paletteinit(void)
{
	PALETTEENTRY *pal;
	int r, g, b, cr, cg, cb, v;
	int num, den;
	int i, j;

	logpal = smalloc(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY));
	if(logpal == nil)
		panic("out of memory");
	logpal->palVersion = 0x300;
	logpal->palNumEntries = 256;
	pal = logpal->palPalEntry;

	for(r=0,i=0; r<4; r++) {
		for(v=0; v<4; v++,i+=16){
			for(g=0,j=v-r; g<4; g++) {
				for(b=0; b<4; b++,j++){
					den=r;
					if(g>den)
						den=g;
					if(b>den)
						den=b;
					/* divide check -- pick grey shades */
					if(den==0)
						cr=cg=cb=v*17;
					else{
						num=17*(4*den+v);
						cr=r*num/den;
						cg=g*num/den;
						cb=b*num/den;
					}
					pal[i+(j&15)].peRed = cr;
					pal[i+(j&15)].peGreen = cg;
					pal[i+(j&15)].peBlue = cb;
					pal[i+(j&15)].peFlags = 0;
				}
			}
		}
	}
	palette = CreatePalette(logpal);
}


//void
//getcolor(ulong i, ulong *r, ulong *g, ulong *b)
//{
//	PALETTEENTRY *pal;
//
//	pal = logpal->palPalEntry;
//	*r = pal[i].peRed;
//	*g = pal[i].peGreen;
//	*b = pal[i].peBlue;
//}

void
bmiinit(void)
{
	ushort *p;
	int i;

	bmi = smalloc(sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD));
	if(bmi == 0)
		panic("out of memory");
	bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi->bmiHeader.biWidth = 0;
	bmi->bmiHeader.biHeight = 0;	/* - => origin upper left */
	bmi->bmiHeader.biPlanes = 1;
	bmi->bmiHeader.biBitCount = depth;
	bmi->bmiHeader.biCompression = BI_RGB;
	bmi->bmiHeader.biSizeImage = 0;
	bmi->bmiHeader.biXPelsPerMeter = 0;
	bmi->bmiHeader.biYPelsPerMeter = 0;
	bmi->bmiHeader.biClrUsed = 0;
	bmi->bmiHeader.biClrImportant = 0;	/* number of important colors: 0 means all */

	p = (ushort*)bmi->bmiColors;
	for(i = 0; i < 256; i++)
		p[i] = i;
}

LRESULT CALLBACK
WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	PAINTSTRUCT paint;
	HDC hdc;
	LONG x, y, b;
	int wid, ht;
	Rectangle r;

	switch(msg) {
	case WM_CREATE:
		break;
	case WM_SETCURSOR:
		/* User set */
		if(hcursor != NULL && mouseinside){
			SetCursor(hcursor);
			return 1;
		}else
			SetCursor(LoadCursor(nil, IDC_ARROW));
		return DefWindowProc(hwnd, msg, wparam, lparam);
	case WM_NCMOUSEMOVE:
	case WM_NCLBUTTONUP:
	case WM_NCMBUTTONUP:
	case WM_NCRBUTTONUP:
	case WM_NCLBUTTONDOWN:
	case WM_NCMBUTTONDOWN:
	case WM_NCRBUTTONDOWN:
		return DefWindowProc(hwd, msg, wparam, lparam);

	case WM_MOUSEMOVE:
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		x = LOWORD(lparam);
		y = HIWORD(lparam);
		b = 0;
		if(wparam & MK_LBUTTON)
			b = 1;
		if(wparam & MK_MBUTTON)
			b |= 2;
		if(wparam & MK_RBUTTON) {
			if(wparam & MK_SHIFT)
				b |= 2;
			else
				b |= 4;
		}
		mousetrack(Pt(x,y), b, 0);
		break;

	case WM_CHAR:
		/* repeat count is lparam & 0xf */
		switch(wparam){
		case '\n':
			wparam = '\r';
			break;
		case '\r':
			wparam = '\n';
			break;
		}
		kbdputc(wparam);
		break;

	case WM_SYSKEYUP:
		break;
	case WM_SYSKEYDOWN:
	case WM_KEYDOWN:
		switch(wparam) {
		case VK_MENU:
			kbdputc(Kalt);
			break;
		case VK_INSERT:
			kbdputc(Kins);
			break;
		case VK_DELETE:
			kbdputc(Kdelete);
			break;
		case VK_UP:
			kbdputc(Kup);
			break;
		case VK_DOWN:
			kbdputc(Kdown);
			break;
		case VK_LEFT:
			kbdputc(Kleft);
			break;
		case VK_RIGHT:
			kbdputc(Kright);
			break;
		}
		break;

	case WM_CLOSE:
		DestroyWindow(hwnd);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);
		break;

	case WM_PALETTECHANGED:
		if((HWND)wparam == hwnd)
			break;
	/* fall through */
	case WM_QUERYNEWPALETTE:
		hdc = GetDC(hwnd);
		SelectPalette(hdc, palette, 0);
		if(RealizePalette(hdc) != 0)
			InvalidateRect(hwnd, nil, 0);
		ReleaseDC(hwnd, hdc);
		break;

	case WM_PAINT:
		if(!gscreen)
			break;
		hdc = BeginPaint(hwnd, &paint);
		r.min.x = paint.rcPaint.left;
		r.min.y = paint.rcPaint.top;
		r.max.x = paint.rcPaint.right;
		r.max.y = paint.rcPaint.bottom;
		screenload(hdc, r);
		EndPaint(hwnd, &paint);
		break;

	case WM_SIZE:
		if(!gscreen)
			return DefWindowProc(hwnd, msg, wparam, lparam);
		
		if(wparam != SIZE_RESTORED && wparam != SIZE_MAXIMIZED)
			break;
		wid = lparam&0xFFFF;
		ht = (lparam>>16)&0xFFFF;
		if(wid != Dx(gscreen->r) || ht != Dy(gscreen->r))
			resizescreenimage();
		break;

	case WM_MOVE:		/* we don't care */
	case WM_DISPLAYCHANGE:	/* resolution changed */
	case WM_COMMAND:
	case WM_SETFOCUS:
	case WM_DEVMODECHANGE:
	case WM_WININICHANGE:
	case WM_INITMENU:
	default:
		return DefWindowProc(hwnd, msg, wparam, lparam);
	}
	return 0;
}

int
cursormove(Point xy)
{
	POINT pt;

	pt.x = xy.x;
	pt.y = xy.y;
	MapWindowPoints(window, 0, &pt, 1);
	SetCursorPos(pt.x, pt.y);
	return 0;
}

void
cursorload(Cursor *c)
{
	HCURSOR nh;
	int x, y, h, w;
	uchar *sp, *cp;
	uchar *and, *xor;

	h = GetSystemMetrics(SM_CYCURSOR);
	w = (GetSystemMetrics(SM_CXCURSOR)+7)/8;

	and = smalloc(h*w);
	memset(and, 0xff, h*w);
	xor = smalloc(h*w);
	
	for(y=0,sp=c->set,cp=c->clr; y<16; y++) {
		for(x=0; x<2; x++) {
			and[y*w+x] = ~(*sp|*cp);
			xor[y*w+x] = ~*sp & *cp;
			cp++;
			sp++;
		}
	}
	nh = CreateCursor(inst, -c->offset.x, -c->offset.y,
			GetSystemMetrics(SM_CXCURSOR), h,
			and, xor);
	if(nh != NULL) {
		SetCursor(nh);
		if(hcursor != NULL)
			DestroyCursor(hcursor);
		hcursor = nh;
	}

	free(and);
	free(xor);

	PostMessage(window, WM_SETCURSOR, (int)window, 0);
}

void
mousectl(Cmdbuf *c)
{
	USED(c);
	pm_error("no mouse ctls supported");
}

void
cursorarrow(void)
{
	if(hcursor != 0) {
		DestroyCursor(hcursor);
		hcursor = 0;
	}
	SetCursor(LoadCursor(0, IDC_ARROW));
	PostMessage(window, WM_SETCURSOR, (int)window, 0);
}

uchar*
clipreadunicode(HANDLE h)
{
	Rune *p;
	uchar *q;
	
	p = GlobalLock(h);
	q = win_wstr2utf(p);
	GlobalUnlock(h);
	return q;
}

uchar *
clipreadutf(HANDLE h)
{
	uchar *p;

	p = GlobalLock(h);
	p = strdup(p);
	GlobalUnlock(h);
	return p;
}


uchar*
clipread()
{
	HANDLE h;
	uchar *p;

	if(!OpenClipboard(window)) {
		oserror();
		return strdup("");
	}

	if(h = GetClipboardData(CF_UNICODETEXT))
		p = clipreadunicode(h);
	else if(h = GetClipboardData(CF_TEXT))
		p = clipreadutf(h);
	else {
		oserror();
		p = strdup("");
	}
	
	CloseClipboard();
	return p;
}

int
clipwrite(uchar *buf, int n)
{
	HANDLE h;
	char *p, *e;
	Rune *rp;

	if(!OpenClipboard(window)) {
		oserror();
		return -1;
	}

	if(!EmptyClipboard()) {
		oserror();
		CloseClipboard();
		return -1;
	}

	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(Rune));
	if(h == NULL)
		panic("out of memory");
	rp = GlobalLock(h);
	p = (char*)buf;
	e = p+n;
	while(p<e)
		p += chartorune(rp++, p);
	*rp = 0;
	GlobalUnlock(h);

	SetClipboardData(CF_UNICODETEXT, h);

	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, n+1);
	if(h == NULL)
		panic("out of memory");
	p = GlobalLock(h);
	memcpy(p, buf, n);
	p[n] = 0;
	GlobalUnlock(h);
	
	SetClipboardData(CF_TEXT, h);

	CloseClipboard();
	return n;
}
