#define _WIN32_WINNT 0x0500
#include	<windows.h>

#undef Rectangle
#define Rectangle _Rectangle

#include "u.h"
#include "lib.h"
#include "kern/dat.h"
#include "kern/fns.h"
#include "error.h"
#include "user.h"
#include <draw.h>
#include <memdraw.h>
#include "screen.h"
#include "keyboard.h"
#include "r16.h"

Memimage	*gscreen;
Screeninfo	screen;

static int depth;

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 int readybit;
static Rendez	rend;

Point	ZP;

static int
isready(void*a)
{
	return readybit;
}

void
screeninit(void)
{
	int fmt;
	int dx, dy;

	FreeConsole();
	memimageinit();
	if(depth == 0)
		depth = GetDeviceCaps(GetDC(NULL), BITSPIXEL);
	switch(depth){
	case 32:
		screen.dibtype = DIB_RGB_COLORS;
		screen.depth = 32;
		fmt = XRGB32;
		break;
	case 24:
		screen.dibtype = DIB_RGB_COLORS;
		screen.depth = 24;
		fmt = RGB24;
		break;
	case 16:
		screen.dibtype = DIB_RGB_COLORS;
		screen.depth = 16;
		fmt = RGB15;	/* [sic] */
		break;
	case 8:
	default:
		screen.dibtype = DIB_PAL_COLORS;
		screen.depth = 8;
		depth = 8;
		fmt = CMAP8;
		break;
	}
	dx = GetDeviceCaps(GetDC(NULL), HORZRES);
	dy = GetDeviceCaps(GetDC(NULL), VERTRES);

	gscreen = allocmemimage(Rect(0,0,dx,dy), fmt);
	gscreen->clipr = ZR;
	kproc("winscreen", winproc, 0);
	ksleep(&rend, isready, 0);
}

uchar*
attachscreen(Rectangle *r, ulong *chan, int *depth, int *width, int *softscreen)
{
	*r = gscreen->clipr;
	*chan = gscreen->chan;
	*depth = gscreen->depth;
	*width = gscreen->width;
	*softscreen = 1;

	return gscreen->data->bdata;
}

void
flushmemscreen(Rectangle r)
{
	screenload(r, gscreen->depth, byteaddr(gscreen, ZP), ZP,
		gscreen->width*sizeof(ulong));
}

void
screenload(Rectangle r, int depth, uchar *p, Point pt, int step)
{
	int dx, dy, delx;
	HDC hdc;

	if(depth != gscreen->depth)
		panic("screenload: bad ldepth");

	/*
	 * 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->clipr) == 0)
		return;
	if((step&3) != 0 || ((pt.x*depth)%32) != 0 || ((ulong)p&3) != 0)
		panic("screenload: bad params %d %d %ux", step, pt.x, p);

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

	p += (r.min.y-pt.y)*step;
	p += ((r.min.x-delx-pt.x)*depth)>>3;
	
	lock(&gdilock);

	hdc = GetDC(window);
	SelectPalette(hdc, palette, 0);
	RealizePalette(hdc);

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

	bmi->bmiHeader.biWidth = (step*8)/depth;
	bmi->bmiHeader.biHeight = -dy;	/* - => origin upper left */

	StretchDIBits(hdc, r.min.x, r.min.y, dx, dy,
		delx, 0, dx, dy, p, bmi, screen.dibtype, SRCCOPY);

	ReleaseDC(window, hdc);

	GdiFlush();
 
	unlock(&gdilock);
}

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, MAKEINTRESOURCE(101));
	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"drawterm 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);

	terminit();

	readybit = 1;
	wakeup(&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 = mallocz(sizeof(LOGPALETTE) + 256*sizeof(PALETTEENTRY), 1);
	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 = mallocz(sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD), 1);
	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;
}

void
togglefull(HWND hwnd)
{
	static int full;
	static LONG style, exstyle;
	static WINDOWPLACEMENT pl;
	MONITORINFO mi;
	
	full = !full;
	if(full){
		SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
		style = GetWindowLong(hwnd, GWL_STYLE);
		exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
		pl.length = sizeof(WINDOWPLACEMENT);
		GetWindowPlacement(hwnd, &pl);
		SetWindowLong(hwnd, GWL_STYLE, style & ~(WS_CAPTION | WS_THICKFRAME));
		SetWindowLong(hwnd, GWL_EXSTYLE, exstyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
		mi.cbSize = sizeof(MONITORINFO);
		GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &mi);
		SetWindowPos(hwnd, NULL, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
	}else{
		SetWindowLong(hwnd, GWL_STYLE, style);
		SetWindowLong(hwnd, GWL_EXSTYLE, exstyle);
		SetWindowPlacement(hwnd, &pl);
	}
}

Rune vk2rune[256] = {
[VK_CANCEL] Kbreak,
[VK_CAPITAL] Kcaps,
[VK_CONTROL] Kctl,
[VK_DELETE] Kdel,
[VK_DOWN] Kdown,
[VK_END] Kend,
[VK_F1] KF|1,KF|2,KF|3,KF|4,KF|5,KF|6,KF|7,KF|8,KF|9,KF|10,KF|11,KF|12,
[VK_HOME] Khome,
[VK_INSERT] Kins,
[VK_LEFT] Kleft,
[VK_MENU] Kalt,
[VK_NEXT] Kpgdown,
[VK_NUMLOCK] Knum,
[VK_PRINT] Kprint,
[VK_PRIOR] Kpgup,
[VK_RIGHT] Kright,
[VK_RMENU] Kaltgr,
[VK_SCROLL] Kscroll,
[VK_SHIFT] Kshift,
[VK_UP] Kup,
};
		

LRESULT CALLBACK
WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	static Rune scdown[256];
	PAINTSTRUCT paint;
	HDC hdc;
	LONG x, y, b;
	int i;
	RECT winr;
	Rectangle r;
	Rune k;

	b = 0;

	switch(msg) {
	case WM_CREATE:
		if(GetClientRect(hwnd, &winr) == 0)
			break;
		gscreen->clipr = Rect(0, 0, winr.right - winr.left, winr.bottom - winr.top);
		rectclip(&gscreen->clipr, gscreen->r);
		break;

	case WM_SETCURSOR:
		/* User set */
		if(hcursor != NULL) {
			SetCursor(hcursor);
			return 1;
		}
		return DefWindowProc(hwnd, msg, wparam, lparam);
	case WM_MOUSEWHEEL:
		if ((int)(wparam & 0xFFFF0000)>0)
			b |=8;
		else
			b |=16;
		mousetrack(0, 0, b, ticks());
		break;
	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);
		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;
		}
		absmousetrack(x, y, b, ticks());
		break;

	case WM_CHAR:
		k = wparam;
		if(k == '\n')
			k = '\r';
		else if(k == '\r')
			k = '\n';
		if(0){
	case WM_SYSKEYDOWN:
	case WM_KEYDOWN:
			k = vk2rune[wparam&0xFF];
		}
		if(k == 0)
			break;
		i = (lparam>>16)&0xFF;
		scdown[i] = k;
		kbdkey(k, 1);
		break;
	case WM_SYSKEYUP:
	case WM_KEYUP:
		if(wparam == VK_PAUSE)
			togglefull(hwnd);
		i = (lparam>>16)&0xFF;
		k = scdown[i];
		if(k != 0){
			scdown[i] = 0;
			kbdkey(k, 0);
		}
		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:
		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;
		flushmemscreen(r);
		EndPaint(hwnd, &paint);
		break;

	case WM_SIZE:
		if(GetClientRect(hwnd, &winr) == 0)
			break;
		screenresize(Rect(0, 0, winr.right - winr.left, winr.bottom - winr.top));
		break;

	case WM_COMMAND:
	case WM_SETFOCUS:
	case WM_DEVMODECHANGE:
	case WM_WININICHANGE:
	case WM_INITMENU:
	default:
		return DefWindowProc(hwnd, msg, wparam, lparam);
	}
	return 0;
}

void
mouseset(Point xy)
{
	POINT pt;

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

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

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

	and = mallocz(h*w, 1);
	memset(and, 0xff, h*w);
	xor = mallocz(h*w, 1);
	
	lock(&cursor.lk);
	for(y=0,sp=cursor.set,cp=cursor.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, -cursor.offset.x, -cursor.offset.y,
			GetSystemMetrics(SM_CXCURSOR), h,
			and, xor);
	if(nh != NULL) {
		SetCursor(nh);
		if(hcursor != NULL)
			DestroyCursor(hcursor);
		hcursor = nh;
	}
	unlock(&cursor.lk);

	free(and);
	free(xor);

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

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


void
setcolor(ulong index, ulong red, ulong green, ulong blue)
{
}


char*
clipreadunicode(HANDLE h)
{
	Rune16 *p;
	int n;
	char *q;

	p = GlobalLock(h);
	n = rune16nlen(p, runes16len(p)+1);
	q = malloc(n);
	runes16toutf(q, p, n);
	GlobalUnlock(h);

	return q;
}

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

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

char*
clipread(void)
{
	HANDLE h;
	char *p, *q, *r;

	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();
		return strdup("");
	}

	for(q = r = p; *q != 0; q++)
		if(*q != '\r')
			*r++ = *q;
	*r = 0;
	
	CloseClipboard();
	return p;
}

static char *
addcr(char *buf, int *lp)
{
	int nlen;
	char *r, *p, *q;

	nlen = 0;
	for(p = buf; *p != 0; p++){
		if(*p == '\n')
			nlen++;
		nlen++;
	}
	*lp = nlen;
	r = malloc(nlen + 1);
	if(r == nil)
		panic("malloc: %r");
	q = r;
	for(p = buf; *p != 0; p++){
		if(*p == '\n')
			*q++ = '\r';
		*q++ = *p;
	}
	*q = 0;
	return r;
}

int
clipwrite(char *buf)
{
	HANDLE h;
	char *p;
	Rune16 *rp;
	char *crbuf;
	int n;

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

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

	crbuf = addcr(buf, &n);

	h = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, (n+1)*sizeof(Rune));
	if(h == NULL)
		panic("out of memory");
	rp = GlobalLock(h);
	utftorunes16(rp, crbuf, n+1);
	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, crbuf, n);
	p[n] = 0;
	GlobalUnlock(h);
	
	SetClipboardData(CF_TEXT, h);

	CloseClipboard();
	free(crbuf);
	return n;
}

void
setterm(int raw)
{
	DWORD mode;
	HANDLE h;

	h = GetStdHandle(STD_INPUT_HANDLE);
	if(!GetConsoleMode(h, &mode))
		return;
	if(raw)
		mode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
	else
		mode |= (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
	SetConsoleMode(h, mode);
	FlushConsoleInputBuffer(h);
	_setmode(0, raw? _O_BINARY: _O_TEXT);
}
