#include <windows.h>
#include "win_vga.h"

#define	SCREEN_MAGIC	0xBEDA0BED

typedef struct screen_settings_s
{
	unsigned int	magic;
	int				shift, pitch, width, height;
	void			*backbuffer;
	void			**vtab;
	char			cls[256];		/* window class name  */
	char			title[256];		/* window title */
	DWORD			rendering_thread_id;
	LONG			locked;			/* !=0 mean that backbuffer is in use */
	CRITICAL_SECTION	blit_progress;	/* entering befor blitting bb to fb */
	WNDCLASS		wc;				/* window class */
	ATOM			wndclass;		/* return value of RegisterClass() */
	HINSTANCE		inst;			/* executable file handle (really, needless) */
	HWND			wnd;			/* window handle as returned by CreateWindow  */
	BOOL			wndactive;		/* activity flag */
	BITMAPINFOHEADER header;		/* parameters of backbuffer */
	int				fullscreen;
}	screen_settings_t;

static	HANDLE				rendering_thread=NULL;
DWORD						parent_thread_id;
static	screen_settings_t	sset;

LRESULT CALLBACK
dib_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	PAINTSTRUCT ps;
	switch(msg)
	{
		case WM_ACTIVATE:
		{
			sset.wndactive=LOWORD(wparam) != WA_INACTIVE;
			break;
		}
		case WM_CLOSE:
		{
			return 0;
		}
		case WM_PAINT:
		{
			if(!sset.locked)
			{
				EnterCriticalSection(&sset.blit_progress);
				BeginPaint(wnd,&ps);
				SetDIBitsToDevice(ps.hdc,0,0,sset.header.biWidth,
					sset.header.biHeight,0,0,0,sset.header.biHeight,sset.backbuffer,
					(LPBITMAPINFO)&sset.header,DIB_RGB_COLORS);
				if(!sset.wndactive)
					Sleep(10);
				EndPaint(wnd,&ps);
				LeaveCriticalSection(&sset.blit_progress);
			}
			return 0;
			/* If window is not active, reduce CPU overhead */
			if(!sset.wndactive)
				Sleep(33);	/* Maximum framerate limited to 30 FPS */
		}
	}
	return DefWindowProc(wnd,msg,wparam,lparam);
}

DWORD WINAPI RenderingThread(LPVOID param)
{
	/*
	 *	1. Initialization.
	 */
	RECT rc;
	int rw,rh,i;
	MSG msg;
	int width, height;
	width = LOWORD(param);
	height = HIWORD(param);
	memset(&sset,0,sizeof(screen_settings_t));
	sset.rendering_thread_id=GetCurrentThreadId();
	/*
	 *	BUGBUG: only true-color backbuffers currently supported
	 */
	#define bpp 32
	if(bpp!=32)
	{
		/*
		printf("graphInitWinDIB(%d,%d,%d): wrong bpp value '%d'.\n",
			width,height,bpp,bpp);
		*/
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	strcpy(sset.cls,"TESTgClass");
	sset.inst=GetModuleHandle(NULL);
	sset.width=width;
	sset.height=height;
	sset.fullscreen=(width==GetSystemMetrics(SM_CXSCREEN))&&
		(height==GetSystemMetrics(SM_CYSCREEN));
	if(bpp==8)
	{
		sset.shift=0;
		sset.pitch=width;
	}
	else if (bpp==32)
	{
		sset.shift=2;
		sset.pitch=width<<2;
	}
	InitializeCriticalSection(&sset.blit_progress);
	sset.wc.lpszClassName=sset.cls;
	sset.wc.hInstance=sset.inst;
	sset.wc.style = 0;
	sset.wc.lpfnWndProc=dib_window_proc;
	sset.wc.cbClsExtra = 0;
	sset.wc.cbWndExtra = 0;
	sset.wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	sset.wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	sset.wc.hbrBackground = GetStockObject(HOLLOW_BRUSH);
	sset.wc.lpszMenuName=NULL;
	sset.wndclass=RegisterClass(&sset.wc);
	strcpy(sset.title,"Initialization of DIB engine ...");
	if(!sset.wndclass) 
	{
		/*
		printf("graphInitWinDIB(): can't RegisterClass() '%s'\n",
			sset.wc.lpszClassName);
		*/
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	if(sset.fullscreen)
	{
		sset.wnd=CreateWindow(sset.cls,sset.title,WS_POPUP,
			0,0,sset.width,sset.height,NULL,NULL,sset.inst,NULL);
	}
	else
	{
		sset.wnd=CreateWindow(sset.cls,sset.title,WS_POPUP|WS_CAPTION,
			0,0,sset.width,sset.height,NULL,NULL,sset.inst,NULL);
	}
	if(!sset.wnd)
	{
		/*
		puts("graphInitWinDIB(): can't CreateWindow().\n");
		*/
		UnregisterClass(sset.wc.lpszClassName,sset.inst);
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	ShowWindow(sset.wnd,SW_SHOWNORMAL);
	if(!sset.fullscreen)
	{
		GetClientRect(sset.wnd,&rc);
		rw=2*sset.width-rc.right;
		rh=2*sset.height-rc.bottom;
		SetWindowPos(sset.wnd,HWND_NOTOPMOST,0,0,rw,rh,SWP_NOZORDER|SWP_NOMOVE);
	}
	/* setup backbuffer */
	sset.header.biSize=sizeof(sset.header);
	sset.header.biWidth=width;
	sset.header.biHeight=height;
	sset.header.biPlanes=1;
	sset.header.biBitCount=32;
	sset.backbuffer=(void*)malloc(sset.pitch*sset.height);
	sset.vtab=(void**)malloc(sset.height*sizeof(void*));
	if(!sset.backbuffer || !sset.vtab)
	{
		if(sset.backbuffer)
			free(sset.backbuffer);
		if(sset.vtab)
			free(sset.backbuffer);
		DestroyWindow(sset.wnd);
		UnregisterClass(sset.wc.lpszClassName,sset.inst);
		DeleteCriticalSection(&sset.blit_progress);
		memset(&sset,0,sizeof(screen_settings_t));
		return 0;
	}
	memset(sset.backbuffer,255,sset.pitch*sset.height);
	sset.vtab[0]=sset.backbuffer;
	for(i=1;i<sset.height;i++)
		sset.vtab[i]=(unsigned char*)sset.vtab[i-1]+sset.pitch;
	sset.magic=SCREEN_MAGIC;
	/*
	 *	2. Message loop.
	 */
	while(GetMessage(&msg,NULL,0,0))
	{
		PostThreadMessage(parent_thread_id,msg.message,msg.wParam,msg.lParam);
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	/*
	 *	2. Collecting garbage.
	 */
	if(sset.backbuffer)
		free(sset.backbuffer);
	if(sset.vtab)
		free(sset.vtab);
	DestroyWindow(sset.wnd);
	UnregisterClass(sset.wc.lpszClassName,sset.inst);
	DeleteCriticalSection(&sset.blit_progress);
	memset(&sset,0,sizeof(screen_settings_t));
	return 1;
	/* BUGBUG */ 
	#undef bpp
}

/*
**	graphInitWinDIB()
*/
int
graphInitWinDIB(int width,int height)
{
	DWORD thr;
	LPVOID param;
	if(rendering_thread)
		return 0;
	parent_thread_id=GetCurrentThreadId();
	param=(LPVOID)MAKELPARAM(width,height);
	rendering_thread=CreateThread(NULL,0,RenderingThread,param,0,&thr);
	/*
	 *	Wait some time: we assume that thread initialization takes not more
	 *	then 250 milliseconds.
	 */
	if(WaitForSingleObject(rendering_thread,250)!=WAIT_TIMEOUT)
	{
		/*
		 *	Thread failed to initialize.
		 */
		/*
		printf("graphInitWinDIB(): rendering thread failed to initialize.")
		*/
		return 0;
	}
	return 1;
}	

/*
**	graphSetWindowTitle()
*/
void
graphSetWindowTitle(char *title)
{
	strncpy(sset.title,title,255);
	SendMessage(sset.wnd,WM_SETTEXT,0,(LPARAM)sset.title);
}

/*
**	graphShutdownWinDIB()
*/
void
graphShutdownWinDIB(void)
{
	if(rendering_thread && rendering_thread!=INVALID_HANDLE_VALUE &&
		sset.magic==SCREEN_MAGIC)
	{
		/*
		 *	Break RenderingThread's message loop
		 */
		PostThreadMessage(sset.rendering_thread_id,WM_QUIT,0,0);
		/*
		 *	Wait for garbage collection completion.
		 */
		WaitForSingleObject(rendering_thread,INFINITE);
		rendering_thread=NULL;
	}
}

/*
**	graphLockBackbuffer()
*/
void
**graphLockBackbuffer(void)
{
	InterlockedIncrement(&sset.locked);
	return sset.vtab;
}

/*
**	graphUnlockBackbuffer()
*/
void
graphUnlockBackbuffer(void)
{
	InterlockedDecrement(&sset.locked);
}

/*
**	graphSwapBuffers()
*/
void
graphSwapBuffers(void)
{
	HDC dc;
	if(!sset.locked)
	{
		EnterCriticalSection(&sset.blit_progress);
		dc=GetDC(sset.wnd);
		SetDIBitsToDevice(dc,0,0,sset.header.biWidth,
			sset.header.biHeight,0,0,0,sset.header.biHeight,sset.backbuffer,
			(LPBITMAPINFO)&sset.header,DIB_RGB_COLORS);
		if(!sset.wndactive)
			Sleep(10);
		ReleaseDC(sset.wnd,dc);
		LeaveCriticalSection(&sset.blit_progress);
	}
}

/*
**	graphClear()
*/
void
graphClear(unsigned int color)	/* FOR 32BPP RGB backbuffers only */
{
#if _MSC_VER	/* we have DOS - style inline assembly */
	__asm
	{
		push	ebx
		push	edi
		
		mov		eax, sset.header.biWidth
		mov		ebx, sset.header.biHeight
		imul	ebx
		mov		edi, sset.backbuffer
		mov		ecx, eax
		mov		eax, color

		rep  stosd
		
		pop  edi
		pop  ebx
	}
#else
	unsigned int *bb;
	int i, size;
	size=sset.width*sset.height;
	bb=(unsigned int *)sset.backbuffer;
	for(i=0;i<size;i++)
		bb[i]=color;
#endif
}

/*
**	graphSetPixel32()
*/
void
graphSetPixel32(int x,int y,unsigned int color)
{
	*((unsigned int*)sset.vtab[y]+x)=color;
}
