#include "quakedef.h"
#ifdef SWQUAKE
#include "sw.h"

#include "winquake.h"
/*Fixup outdated windows headers*/
#ifndef WM_XBUTTONDOWN
   #define WM_XBUTTONDOWN      0x020B
   #define WM_XBUTTONUP      0x020C
#endif
#ifndef MK_XBUTTON1
   #define MK_XBUTTON1         0x0020
#endif
#ifndef MK_XBUTTON2
   #define MK_XBUTTON2         0x0040
#endif
// copied from DarkPlaces in an attempt to grab more buttons
#ifndef MK_XBUTTON3
   #define MK_XBUTTON3         0x0080
#endif
#ifndef MK_XBUTTON4
   #define MK_XBUTTON4         0x0100
#endif
#ifndef MK_XBUTTON5
   #define MK_XBUTTON5         0x0200
#endif
#ifndef MK_XBUTTON6
   #define MK_XBUTTON6         0x0400
#endif
#ifndef MK_XBUTTON7
   #define MK_XBUTTON7         0x0800
#endif
#ifndef WM_INPUT
	#define WM_INPUT 255
#endif



typedef struct dibinfo
{
	BITMAPINFOHEADER	header;
	RGBQUAD				acolors[256];
} dibinfo_t;

//struct
//{
	HBITMAP hDIBSection;
	qbyte *pDIBBase;

	HDC mainhDC;
	HDC hdcDIBSection;
	HGDIOBJ previously_selected_GDI_obj;

	int framenumber;
	void *screenbuffer;
	qintptr_t screenpitch;
	int window_x, window_y;
	unsigned int *depthbuffer;
struct
{
	qboolean isfullscreen;
} w32sw;
HWND mainwindow;

void DIB_Shutdown(void)
{
	if (depthbuffer)
	{
		BZ_Free(depthbuffer);
		depthbuffer = NULL;
	}

	if (hdcDIBSection)
	{
		SelectObject(hdcDIBSection, previously_selected_GDI_obj);
		DeleteDC(hdcDIBSection);
		hdcDIBSection = NULL;
	}

	if (hDIBSection)
	{
		DeleteObject(hDIBSection);
		hDIBSection = NULL;
		pDIBBase = NULL;
	}

	if (mainhDC)
	{
		ReleaseDC(mainwindow, mainhDC);
		mainhDC = 0;
	}
}
qboolean DIB_Init(void)
{
	dibinfo_t   dibheader;
	BITMAPINFO *pbmiDIB = ( BITMAPINFO * ) &dibheader;
	int i;

	memset( &dibheader, 0, sizeof( dibheader ) );

	/*
	** grab a DC
	*/
	if ( !mainhDC )
	{
		if ( ( mainhDC = GetDC( mainwindow ) ) == NULL )
			return false;
	}

	/*
	** fill in the BITMAPINFO struct
	*/
	pbmiDIB->bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
	pbmiDIB->bmiHeader.biWidth         = vid.pixelwidth;
	pbmiDIB->bmiHeader.biHeight        = vid.pixelheight;
	pbmiDIB->bmiHeader.biPlanes        = 1;
	pbmiDIB->bmiHeader.biBitCount      = 32;
	pbmiDIB->bmiHeader.biCompression   = BI_RGB;
	pbmiDIB->bmiHeader.biSizeImage     = 0;
	pbmiDIB->bmiHeader.biXPelsPerMeter = 0;
	pbmiDIB->bmiHeader.biYPelsPerMeter = 0;
	pbmiDIB->bmiHeader.biClrUsed       = 0;
	pbmiDIB->bmiHeader.biClrImportant  = 0;

	/*
	** fill in the palette
	*/
	for ( i = 0; i < 256; i++ )
	{
		dibheader.acolors[i].rgbRed   = ( d_8to24rgbtable[i] >> 0 )  & 0xff;
		dibheader.acolors[i].rgbGreen = ( d_8to24rgbtable[i] >> 8 )  & 0xff;
		dibheader.acolors[i].rgbBlue  = ( d_8to24rgbtable[i] >> 16 ) & 0xff;
	}

	/*
	** create the DIB section
	*/
	hDIBSection = CreateDIBSection( mainhDC,
		                                     pbmiDIB,
											 DIB_RGB_COLORS,
											 (void**)&pDIBBase,
											 NULL,
											 0 );

	if ( hDIBSection == NULL )
	{
		Con_Printf( "DIB_Init() - CreateDIBSection failed\n" );
		goto fail;
	}

	if (pbmiDIB->bmiHeader.biHeight < 0)
    {
		// bottom up
		screenbuffer	= pDIBBase + ( vid.pixelheight - 1 ) * vid.pixelwidth * 4;
		screenpitch		= -(int)vid.pixelwidth;
    }
    else
    {
		// top down
		screenbuffer	= pDIBBase;
		screenpitch		= vid.pixelwidth;
    }

	/*
	** clear the DIB memory buffer
	*/
	memset( pDIBBase, 0, vid.pixelwidth * vid.pixelheight * 4);

	if ( ( hdcDIBSection = CreateCompatibleDC( mainhDC ) ) == NULL )
	{
		Con_Printf( "DIB_Init() - CreateCompatibleDC failed\n" );
		goto fail;
	}
	if ( ( previously_selected_GDI_obj = SelectObject( hdcDIBSection, hDIBSection ) ) == NULL )
	{
		Con_Printf( "DIB_Init() - SelectObject failed\n" );
		goto fail;
	}

	depthbuffer = BZ_Malloc(vid.pixelwidth * vid.pixelheight * sizeof(*depthbuffer));
	if (depthbuffer)
		return true;

fail:
	DIB_Shutdown();
	return false;
}
void DIB_SwapBuffers(void)
{
//	extern float usingstretch;

//	if (usingstretch == 1)
		BitBlt( mainhDC,
				0, 0,
				vid.pixelwidth,
				vid.pixelheight,
				hdcDIBSection,
				0, 0,
				SRCCOPY );
/*	else
		StretchBlt( mainhDC,	//Why is StretchBlt not optimised for a scale of 2? Surly that would be a frequently used quantity?
			0, 0,
			vid.width*usingstretch,
			vid.height*usingstretch,
			hdcDIBSection,
			0, 0,
			vid.width, vid.height,
			SRCCOPY );
*/
}

extern int window_width;
extern int window_height;
void SWV_UpdateWindowStatus(void)
{
	POINT p;
	RECT nr;
	RECT WindowRect;
	GetClientRect(mainwindow, &nr);

	//if its bad then we're probably minimised
	if (nr.right <= nr.left)
		return;
	if (nr.bottom <= nr.top)
		return;

	WindowRect = nr;
	p.x = 0;
	p.y = 0;
	ClientToScreen(mainwindow, &p);
	window_x = p.x;
	window_y = p.y;
	window_width = WindowRect.right - WindowRect.left;
	window_height = WindowRect.bottom - WindowRect.top;

	window_rect.left = window_x;
	window_rect.top = window_y;
	window_rect.right = window_x + window_width;
	window_rect.bottom = window_y + window_height;
	window_center_x = (window_rect.left + window_rect.right) / 2;
	window_center_y = (window_rect.top + window_rect.bottom) / 2;

	INS_UpdateClipCursor ();
}


qboolean SWAppActivate(BOOL fActive, BOOL minimize)
/****************************************************************************
*
* Function:     AppActivate
* Parameters:   fActive - True if app is activating
*
* Description:  If the application is activating, then swap the system
*               into SYSPAL_NOSTATIC mode so that our palettes will display
*               correctly.
*
****************************************************************************/
{
	static BOOL	sound_active;

	if (ActiveApp == fActive && Minimized == minimize)
		return false;	//so windows doesn't crash us over and over again.

	ActiveApp = fActive;
	Minimized = minimize;

// enable/disable sound on focus gain/loss
	if (!ActiveApp && sound_active)
	{
		S_BlockSound ();
		sound_active = false;
	}
	else if (ActiveApp && !sound_active)
	{
		S_UnblockSound ();
		sound_active = true;
	}

	INS_UpdateGrabs(false, ActiveApp);

/*
	if (fActive)
	{
		if (modestate != MS_WINDOWED)
		{
			if (vid_canalttab && vid_wassuspended) {
				vid_wassuspended = false;
				ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN);
				ShowWindow(mainwindow, SW_SHOWNORMAL);
			}
		}
	}
	if (!fActive)
	{
		if (modestate != MS_WINDOWED)
		{
			if (vid_canalttab) {
				ChangeDisplaySettings (NULL, 0);
				vid_wassuspended = true;
			}
		}
	}
*/
	return true;
}

LONG WINAPI MainWndProc (
    HWND    hWnd,
    UINT    uMsg,
    WPARAM  wParam,
    LPARAM  lParam)
{
	LONG			lRet = 0;
	int				fActive, fMinimized, temp;
	HDC				hdc;
	PAINTSTRUCT		ps;
	extern unsigned int uiWheelMessage;
//	static int		recursiveflag;

	if ( uMsg == uiWheelMessage ) {
		uMsg = WM_MOUSEWHEEL;
		wParam <<= 16;
	}

	switch (uMsg)
	{
		case WM_CREATE:
			break;

		case WM_SYSCOMMAND:

		// Check for maximize being hit
			switch (wParam & ~0x0F)
			{
				case SC_MAXIMIZE:
					Cbuf_AddText("vid_fullscreen 1;vid_restart\n", RESTRICT_LOCAL);
				// if minimized, bring up as a window before going fullscreen,
				// so MGL will have the right state to restore
/*					if (Minimized)
					{
						force_mode_set = true;
						VID_SetMode (vid_modenum, vid_curpal);
						force_mode_set = false;
					}

					VID_SetMode ((int)vid_fullscreen_mode.value, vid_curpal);
*/					break;

                case SC_SCREENSAVE:
                case SC_MONITORPOWER:
					if (w32sw.isfullscreen)
					{
					// don't call DefWindowProc() because we don't want to start
					// the screen saver fullscreen
						break;
					}

				// fall through windowed and allow the screen saver to start

				default:
//				if (!vid_initializing)
//				{
//					S_BlockSound ();					
//				}

				lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);

//				if (!vid_initializing)
//				{
//					S_UnblockSound ();
//				}
			}
			break;

		case WM_MOVE:
			SWV_UpdateWindowStatus ();

//			if ((modestate == MS_WINDOWED) && !in_mode_set && !Minimized)
//				VID_RememberWindowPos ();

			break;

		case WM_SIZE:
			SWV_UpdateWindowStatus ();
/*			Minimized = false;
			
			if (!(wParam & SIZE_RESTORED))
			{
				if (wParam & SIZE_MINIMIZED)
					Minimized = true;
			}

			if (!Minimized && !w32sw.isfullscreen)
			{
				int nt, nl;
				int nw, nh;
				qboolean move = false;
				RECT r;
				GetClientRect (hWnd, &r);
				nw = (int)(r.right - r.left)&~3;
				nh = (int)(r.bottom - r.top)&~3;

				window_width = nw;
				window_height = nh;
				VID2_UpdateWindowStatus();

				if (nw < 320)
				{
					move = true;
					nw = 320;
				}
				if (nh < 200)
				{
					move = true;
					nh = 200;
				}
				if (nh > MAXHEIGHT)
				{
					move = true;
					nh = MAXHEIGHT;
				}
				if ((r.right - r.left) & 3)
					move = true;
				if ((r.bottom - r.top) & 3)
					move = true;

				GetWindowRect (hWnd, &r);
				nl = r.left;
				nt = r.top;
				r.left =0;
				r.top = 0;
				r.right = nw*usingstretch;
				r.bottom = nh*usingstretch;
				AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW, FALSE, 0);
				if (move)
					MoveWindow(hWnd, nl, nt, r.right - r.left, r.bottom - r.top, true);
				else
				{
					if (vid.width != nw || vid.height != nh)
					{
						M_RemoveAllMenus();	//can cause probs
						DIB_Shutdown();
						vid.conwidth = vid.width = nw;//vid_stretch.value;
						vid.conheight = vid.height = nh;///vid_stretch.value;
						
						DIB_Init( &vid.buffer, &vid.rowbytes );
						vid.conbuffer = vid.buffer;
						vid.conrowbytes = vid.rowbytes;	

						if (VID_AllocBuffers(vid.width, vid.height, r_pixbytes))
							D_InitCaches (vid_surfcache, vid_surfcachesize);

						SCR_UpdateWholeScreen();
					}
					else
						SCR_UpdateWholeScreen();
				}

			}
*/
			break;

		case WM_SYSCHAR:
		// keep Alt-Space from happening
			break;

		case WM_ACTIVATE:
			fActive = LOWORD(wParam);
			fMinimized = (BOOL) HIWORD(wParam);
			SWAppActivate(!(fActive == WA_INACTIVE), fMinimized);

		// fix the leftover Alt from any Alt-Tab or the like that switched us away
//			ClearAllStates ();

			break;

		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);

//			if (!in_mode_set && host_initialized)
//				SCR_UpdateWholeScreen ();

			EndPaint(hWnd, &ps);
			break;

		case WM_KEYDOWN:
		case WM_SYSKEYDOWN:
//			if (!vid_initializing)
				INS_TranslateKeyEvent(wParam, lParam, true, 0, false);
			break;

		case WM_KEYUP:
		case WM_SYSKEYUP:
//			if (!vid_initializing)
				INS_TranslateKeyEvent(wParam, lParam, false, 0, false);
			break;

	// this is complicated because Win32 seems to pack multiple mouse events into
	// one update sometimes, so we always check all states and look for events
		case WM_LBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_MBUTTONDOWN:
		case WM_MBUTTONUP:
		case WM_MOUSEMOVE:
		case WM_XBUTTONDOWN:
		case WM_XBUTTONUP:
//			if (!vid_initializing)
			{
				temp = 0;

				if (wParam & MK_LBUTTON)
					temp |= 1;

				if (wParam & MK_RBUTTON)
					temp |= 2;

				if (wParam & MK_MBUTTON)
					temp |= 4;

				// extra buttons
				if (wParam & MK_XBUTTON1)
					temp |= 8;

				if (wParam & MK_XBUTTON2)
					temp |= 16;

				if (wParam & MK_XBUTTON3)
					temp |= 32;

				if (wParam & MK_XBUTTON4)
					temp |= 64;

				if (wParam & MK_XBUTTON5)
					temp |= 128;

				if (wParam & MK_XBUTTON6)
					temp |= 256;

				if (wParam & MK_XBUTTON7)
					temp |= 512;

				INS_MouseEvent (temp);
			}
			break;
		// JACK: This is the mouse wheel with the Intellimouse
		// Its delta is either positive or neg, and we generate the proper
		// Event.
		case WM_MOUSEWHEEL: 
			{
				if ((short) HIWORD(wParam) > 0)
				{
					Key_Event(0, K_MWHEELUP, 0, true);
					Key_Event(0, K_MWHEELUP, 0, false);
				}
				else
				{
					Key_Event(0, K_MWHEELDOWN, 0, true);
					Key_Event(0, K_MWHEELDOWN, 0, false);
				}
			}
			break;
		case WM_INPUT:
			// raw input handling
			INS_RawInput_Read((HANDLE)lParam);
			break;
/*		case WM_DISPLAYCHANGE:
			if (!in_mode_set && (modestate == MS_WINDOWED) && !vid_fulldib_on_focus_mode)
			{
				force_mode_set = true;
				VID_SetMode (vid_modenum, vid_curpal);
				force_mode_set = false;
			}
			break;
*/
   	    case WM_CLOSE:
		// this causes Close in the right-click task bar menu not to work, but right
		// now bad things happen if Close is handled in that case (garbage and a
		// crash on Win95)
//			if (!vid_initializing)
			{
				if (MessageBox (mainwindow, "Are you sure you want to quit?", "Confirm Exit",
							MB_YESNO | MB_SETFOREGROUND | MB_ICONQUESTION) == IDYES)
				{
					Cbuf_AddText("\nquit\n", RESTRICT_LOCAL);
				}
			}
			break;

		case MM_MCINOTIFY:
            lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
			break;

		default:
            /* pass all unhandled messages to DefWindowProc */
            lRet = DefWindowProc (hWnd, uMsg, wParam, lParam);
	        break;
    }

    /* return 0 if handled message, 1 if not */
    return lRet;
}



/*
** VID_CreateWindow
*/
#define	WINDOW_CLASS_NAME FULLENGINENAME
#define	WINDOW_TITLE_NAME FULLENGINENAME


void VID_CreateWindow(int width, int height, qboolean fullscreen)
{
	WNDCLASS		wc;
	RECT			r;
	int				x, y, w, h;
	int				exstyle;
	int stylebits;


	if (fullscreen)
	{
		exstyle = WS_EX_TOPMOST;
		stylebits = WS_POPUP;
	}
	else
	{
		exstyle = 0;
		stylebits = WS_OVERLAPPEDWINDOW;
	}

	/* Register the frame class */
    wc.style         = 0;
    wc.lpfnWndProc   = (WNDPROC)MainWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = global_hInstance;
    wc.hIcon         = 0;
    wc.hCursor       = LoadCursor (NULL,IDC_ARROW);
	wc.hbrBackground = (void *)COLOR_GRAYTEXT;
    wc.lpszMenuName  = 0;
    wc.lpszClassName = WINDOW_CLASS_NAME;

    RegisterClass(&wc);

	r.left = 0;
	r.top = 0;
	r.right  = width;
	r.bottom = height;

	AdjustWindowRectEx (&r, stylebits, FALSE, exstyle);

	window_rect = r;

	w = r.right - r.left;
	h = r.bottom - r.top;
	x = 0;//vid_xpos.value;
	y = 0;//vid_ypos.value;

	mainwindow = CreateWindowEx(
		exstyle,
		 WINDOW_CLASS_NAME,
		 WINDOW_TITLE_NAME,
		 stylebits,
		 x, y, w, h,
		 NULL,
		 NULL,
		 global_hInstance,
		 NULL);

	if (!mainwindow)
		Sys_Error("Couldn't create window");
	
	ShowWindow(mainwindow, SW_SHOWNORMAL);
	UpdateWindow(mainwindow);
	SetForegroundWindow(mainwindow);
	SetFocus(mainwindow);

	SWV_UpdateWindowStatus();
}








void SW_VID_UpdateViewport(wqcom_t *com)
{
	com->viewport.cbuf = screenbuffer;
	com->viewport.dbuf = depthbuffer;
	com->viewport.width = vid.pixelwidth;
	com->viewport.height = vid.pixelheight;
	com->viewport.stride = screenpitch;
	com->viewport.framenum = framenumber;
}

void SW_VID_SwapBuffers(void)
{
	extern cvar_t vid_conautoscale;
	DIB_SwapBuffers();
	framenumber++;

	INS_UpdateGrabs(false, ActiveApp);

//	memset( pDIBBase, 0, vid.pixelwidth * vid.pixelheight * 4);


	if (window_width != vid.pixelwidth || window_height != vid.pixelheight)
	{
		DIB_Shutdown();
		vid.pixelwidth = window_width;
		vid.pixelheight = window_height;
		if (!DIB_Init())
			Sys_Error("resize reinitialization error");

		Cvar_ForceCallback(&vid_conautoscale);
	}
}

qboolean SW_VID_Init(rendererstate_t *info, unsigned char *palette)
{
	vid.pixelwidth = info->width;
	vid.pixelheight = info->height;

	if (info->fullscreen)	//don't do this with d3d - d3d should set it's own video mode.
	{	//make windows change res.
		DEVMODE gdevmode;
		gdevmode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
		if (info->bpp)
			gdevmode.dmFields |= DM_BITSPERPEL;
		if (info->rate)
			gdevmode.dmFields |= DM_DISPLAYFREQUENCY;
		gdevmode.dmBitsPerPel = info->bpp;
		if (info->bpp && (gdevmode.dmBitsPerPel < 15))
		{
			Con_Printf("Forcing at least 16bpp\n");
			gdevmode.dmBitsPerPel = 16;
		}
		gdevmode.dmDisplayFrequency = info->rate;
		gdevmode.dmPelsWidth = info->width;
		gdevmode.dmPelsHeight = info->height;
		gdevmode.dmSize = sizeof (gdevmode);

		if (ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
		{
			Con_SafePrintf((gdevmode.dmFields&DM_DISPLAYFREQUENCY)?"Windows rejected mode %i*%i*%i*%i\n":"Windows rejected mode %i*%i*%i\n", (int)gdevmode.dmPelsWidth, (int)gdevmode.dmPelsHeight, (int)gdevmode.dmBitsPerPel, (int)gdevmode.dmDisplayFrequency);
			return false;
		}
	}

	VID_CreateWindow(vid.pixelwidth, vid.pixelheight, info->fullscreen);

	if (!DIB_Init())
		return false;

	return true;
}
void SW_VID_DeInit(void)
{
	Image_Shutdown();

	DIB_Shutdown();
	DestroyWindow(mainwindow);

	ChangeDisplaySettings (NULL, 0);
}
qboolean SW_VID_ApplyGammaRamps		(unsigned short *ramps)
{
	return false;
}
char *SW_VID_GetRGBInfo(int prepad, int *truevidwidth, int *truevidheight)
{
	char *buf = NULL;
	char *src, *dst;
	int w, h;
	buf = BZ_Malloc(prepad + (vid.pixelwidth * vid.pixelheight * 3));
	dst = buf + prepad;
	for (h = 0; h < vid.pixelheight; h++)
	{
		for (w = 0, src = (char*)screenbuffer + (h * vid.pixelwidth*4); w < vid.pixelwidth; w++, dst += 3, src += 4)
		{
			dst[0] = src[2];
			dst[1] = src[1];
			dst[2] = src[0];
		}
	}
	*truevidwidth = vid.pixelwidth;
	*truevidheight = vid.pixelheight;
	return buf;
}
void SW_VID_SetWindowCaption(char *msg)
{
}
#endif