#include "include.h"

/*** globals ***/
defglobal int GnDirectDrawLocks;
defglobal List<DisplayAdapter> G_DisplayAdapterList;
defglobal DisplayAdapter G_DisplayAdapter;
defglobal Position G_PrimaryDisplayAdapter;
defglobal IDirectDraw2* G_DirectDraw;
defglobal HWND GhWnd;
defglobal int GnWindowPosX,GnWindowPosY;
defglobal int GnWindowWidth,GnWindowHeight;
defglobal int GnAppReturnValue;
defglobal bool GtWindowClosed;

extern int fmv_lock;

/*** locals ***/
local IDirectDraw* L_DirectDraw1;
local bool LtInExclusiveMode;
local bool LtWM_QUIT;
local bool LtActive;
local bool LtIgnoreWM_SIZE;
local bool LtMinimized,LtMaximized;
local bool LtWindowInitialized;
local bool LtFullScreen;
local bool LtWindowShowed;
local bool LtTempRestrictSize;
local bool LtSizingOrMoving;
local int LnOldWindowPosX,LnOldWindowPosY;
local ScreenMode L_FullScreenMode;

local int LnMinWindowWidth,LnMinClientWidth;
local int LnMinWindowHeight,LnMinClientHeight;
local bool LtMinWindowSizeSet; 
local int LnMaxWindowWidth,LnMaxClientWidth;
local int LnMaxWindowHeight,LnMaxClientHeight;
local bool LtMaxWindowSizeSet;

DefLocalString(DDWndClassName,"Dude:TombRaiderII:DDWndClass");
DefLocalString(DDWndWindowName,"Tomb Raider II");

/*** code ***/
bool DD_Create(GUID* pGUID)
	{
	Log(LT_Enter,"DD_Create");
	if (DX_TRY(DirectDrawCreate(pGUID,&L_DirectDraw1,NULL)))
		{
		Log(LT_Error,"DirectDrawCreate failed");
		return false;
		}
	if (DX_TRY(L_DirectDraw1->QueryInterface(IID_IDirectDraw2,reinterpret_cast<void**>(&G_DirectDraw))))
		{
		Log(LT_Error,"QueryInterface(IID_IDirectDraw2) failed");
		return false;
		}
	DX_TRY(G_DirectDraw->SetCooperativeLevel(GhWnd,DDSCL_NORMAL));
	return true;
	}

void DD_Release()
	{
	Log(LT_Enter,"DD_Release");
	RELEASE(G_DirectDraw);
	RELEASE(L_DirectDraw1);
	}

local HRESULT WINAPI DD_EnumModesCallback(DDSURFACEDESC* pDDSD,void* pContext)
	{
	DisplayAdapter* pAdapter=static_cast<DisplayAdapter*>(pContext);
	//Log("DD_EnumModesCallback");
	if (!((pDDSD->dwFlags&DDSD_HEIGHT) &&
		  (pDDSD->dwFlags&DDSD_WIDTH) &&
		  (pDDSD->dwFlags&DDSD_PIXELFORMAT) &&
		  //(pDDSD->dwFlags&DDSD_CAPS) &&		// funny, this is not set...
		  (pDDSD->ddpfPixelFormat.dwFlags&DDPF_RGB)))
		return DDENUMRET_OK;
	int nWidth=int(pDDSD->dwWidth);
	int nHeight=int(pDDSD->dwHeight);
	int nBPP=int(pDDSD->ddpfPixelFormat.dwRGBBitCount);
	bool tPaletted=(pDDSD->ddpfPixelFormat.dwFlags&DDPF_PALETTEINDEXED8) &&
				   (pDDSD->ddpfPixelFormat.dwRGBBitCount==8);
	int nType;
	if (tPaletted)
		{
		if (pDDSD->ddsCaps.dwCaps&DDSCAPS_MODEX)
			nType=MODETYPE_MODEX;
		else if (pDDSD->ddsCaps.dwCaps&DDSCAPS_STANDARDVGAMODE)
			nType=MODETYPE_STANDARDVGA;
		else
			nType=MODETYPE_PALETTED;

		if (nWidth==320 && nHeight==200 && (!pAdapter->tInternalCanDoFullScreen320 || nType<pAdapter->Lores_Mode.nType))
			{
			pAdapter->Lores_Mode.Set(nWidth,nHeight,nBPP,nType);
			pAdapter->tInternalCanDoFullScreen320=true;
			}
		if (nWidth==640 && nHeight==480 && (!pAdapter->tInternalCanDoFullScreen640 || nType<pAdapter->Hires_Mode.nType))
			{
			pAdapter->Hires_Mode.Set(nWidth,nHeight,nBPP,nType);
			pAdapter->tInternalCanDoFullScreen640=true;
			}
		}
	else
		nType=MODETYPE_RGB;

	ScreenMode new_mode(nWidth,nHeight,nBPP,nType);
	DWORD dwDDBD=DD_BPPToDDBD(nBPP);
	if (pAdapter->tHAL && (dwDDBD&pAdapter->HAL_Desc.dwDeviceRenderBitDepth))
		pAdapter->HAL_Modes.Insert(new_mode);
	if (tPaletted)
		pAdapter->Internal_Modes.Insert(new_mode);
	return DDENUMRET_OK;
	}

local BOOL WINAPI DD_EnumCallback(GUID* pGUID,char* lpDriverDescription,char* lpDriverName,void* pContext)
	{
	Log(LT_Enter,"DD_EnumCallback: %s (%s)",lpDriverDescription,lpDriverName);
	List<DisplayAdapter>* pList=static_cast<List<DisplayAdapter>*>(pContext);
	if (!DD_Create(pGUID))
		return DDENUMRET_OK;
	DDCAPS DriverCaps,HELCaps;
	InitDXStruct(DriverCaps);
	InitDXStruct(HELCaps);
	if (DX_TRY(G_DirectDraw->GetCaps(&DriverCaps,&HELCaps)))
		{
		DD_Release();
		return DDENUMRET_OK;
		}
	DisplayAdapter& adapter=pList->NewTail();
	adapter.SetGUID(pGUID);
	strcpy(adapter.strDescription,lpDriverDescription);
	strcpy(adapter.strName,lpDriverName);
	adapter.DriverCaps=DriverCaps;
	adapter.HELCaps=HELCaps;

	adapter.tInternalCanDoWindowed=false;
	adapter.tHALCanDoWindowed=false;	// until DX_DetermineWindowCapabilites is called, assume nothing
	adapter.tInternalCanDoFullScreen320=false;
	adapter.tInternalCanDoFullScreen640=false;
	adapter.nMaxWindowWidth=0;

	adapter.tHAL=false;
	D3D_FindDrivers(adapter);

	DD_Release();
	return DDENUMRET_OK;
	}

local bool DD_EnumerateDevices(List<DisplayAdapter>& rDeviceList)
	{
	Log(LT_Enter,"DD_EnumerateDevices");
	if (DX_TRY(DirectDrawEnumerate(DD_EnumCallback,&rDeviceList)))
		{
		Log(LT_Error,"DirectDrawEnumerate failed");
		return false;
		}
	return true;
	}

local bool DD_MakeAdapterList()
	{
	Log(LT_Enter,"DD_MakeAdapterList");
	G_DisplayAdapterList.RemoveAll();
	if (!DD_EnumerateDevices(G_DisplayAdapterList))
		return false;
	for_all(p,G_DisplayAdapterList)
		{
		DisplayAdapter& adapter=G_DisplayAdapterList[p];
		if (!adapter.pGUID)
			{
			G_PrimaryDisplayAdapter=p;
			return true;
			}
		}
	Log(LT_Error,"No primary adapter found");
	return false;
	}

void DD_ClientToWindowPos(int& nX,int& nY)
	{
	RECT rc;
	rc.left=rc.right=nX;
	rc.top=rc.bottom=nY;
	AdjustWindowRectEx(&rc,GetWindowLong(GhWnd,GWL_STYLE),false,GetWindowLong(GhWnd,GWL_EXSTYLE));
	nX=rc.left;
	nY=rc.top;
	}

void DD_ClientToWindowSize(int& nWidth,int& nHeight)
	{
	RECT rc;
	rc.left=rc.top=0;
	rc.right=nWidth;
	rc.bottom=nHeight;
	AdjustWindowRectEx(&rc,GetWindowLong(GhWnd,GWL_STYLE),false,GetWindowLong(GhWnd,GWL_EXSTYLE));
	nWidth=rc.right-rc.left;
	nHeight=rc.bottom-rc.top;
	}

void DD_WindowToClientSize(int& nWidth,int& nHeight)
	{
	RECT rc;
	rc.left=rc.top=rc.right=rc.bottom=0;
	AdjustWindowRectEx(&rc,GetWindowLong(GhWnd,GWL_STYLE),false,GetWindowLong(GhWnd,GWL_EXSTYLE));
	nWidth-=(rc.right-rc.left);
	nHeight-=(rc.bottom-rc.top);
	}

void DD_SetMinWindowSize(int nWidth,int nHeight)
	{
	Log(LT_Enter,"DD_SetMinWindowSize");
	LnMinClientWidth=nWidth;
	LnMinClientHeight=nHeight;
	DD_ClientToWindowSize(nWidth,nHeight);
	LnMinWindowWidth=nWidth;
	LnMinWindowHeight=nHeight;
	LtMinWindowSizeSet=true;
	Log("Min Client: %dx%d; Min Window: %dx%d",LnMinClientWidth,LnMinClientHeight,LnMinWindowWidth,LnMinWindowHeight);
	}

void DD_ClearMinWindowSize()
	{
	LtMinWindowSizeSet=false;
	}

void DD_SetMaxWindowSize(int nWidth,int nHeight)
	{
	Log(LT_Enter,"DD_SetMaxWindowSize");
	LnMaxClientWidth=nWidth;
	LnMaxClientHeight=nHeight;
	DD_ClientToWindowSize(nWidth,nHeight);
	LnMaxWindowWidth=nWidth;
	LnMaxWindowHeight=nHeight;
	LtMaxWindowSizeSet=true;
	Log("Max Client: %dx%d; Max Window: %dx%d",LnMaxClientWidth,LnMaxClientHeight,LnMaxWindowWidth,LnMaxWindowHeight);
	}

void DD_ClearMaxWindowSize()
	{
	LtMaxWindowSizeSet=false;
	}

int DD_CalcAspectAdjustedWidth(int nWidth,int nHeight)
	{
	switch (G_AppSettings.nAspectMode)
		{
		case ASPECT_4_3:
			return (nHeight<<2)/3;

		case ASPECT_16_9:
			return (nHeight<<4)/9;

		case ASPECT_ANY:
		default:
			return nWidth;
		}
	}

int DD_CalcAspectAdjustedHeight(int nWidth,int nHeight)
	{
	switch (G_AppSettings.nAspectMode)
		{
		case ASPECT_4_3:
			return (nWidth*3)>>2;

		case ASPECT_16_9:
			return (nWidth*9)>>4;

		case ASPECT_ANY:
		default:
			return nHeight;
		}
	}

local void DD_HandleWM_SIZING(HWND hWnd,int nSizeType,RECT* prc)
	{
	//Log(LT_Enter,"DD_HandleWM_SIZING");
	if (LtFullScreen)
		{
		prc->left=0;
		prc->top=0;
		prc->right=L_FullScreenMode.nWidth;
		prc->bottom=L_FullScreenMode.nHeight;
		return;
		}

	int nWidth,nHeight,nNewWidth,nNewHeight;
	bool tQuantize=(GetAsyncKeyState(VK_SHIFT)<0);

	nWidth=prc->right-prc->left;
	nHeight=prc->bottom-prc->top;
	DD_WindowToClientSize(nWidth,nHeight);
	if (nSizeType==WMSZ_TOP || nSizeType==WMSZ_BOTTOM)
		{
		nNewHeight=nHeight;
		if (tQuantize)
			Align(nNewHeight,32);
		nNewWidth=DD_CalcAspectAdjustedWidth(nWidth,nNewHeight);
		}
	else
		{
		nNewWidth=nWidth;
		if (tQuantize)
			Align(nNewWidth,32);
		nNewHeight=DD_CalcAspectAdjustedHeight(nNewWidth,nHeight);
		}
	if (LtMinWindowSizeSet)
		{
		if (nNewHeight<LnMinClientHeight)
			nNewHeight=LnMinClientHeight;
		if (LtMinWindowSizeSet && nNewWidth<LnMinClientWidth)
			nNewWidth=LnMinClientWidth;
		}
	if (LtMaxWindowSizeSet)
		{
		if (nNewHeight>LnMaxClientHeight)
			nNewHeight=LnMaxClientHeight;
		if (LtMaxWindowSizeSet && nNewWidth>LnMaxClientWidth)
			nNewWidth=LnMaxClientWidth;
		}
//	Log("%d x %d",nNewWidth,nNewHeight);
	DD_ClientToWindowSize(nNewWidth,nNewHeight);
	switch (nSizeType)
		{
		case WMSZ_TOPLEFT:
			prc->left=prc->right-nNewWidth;
			prc->top=prc->bottom-nNewHeight;
			break;

		case WMSZ_RIGHT:
		case WMSZ_BOTTOMRIGHT:
		case WMSZ_BOTTOM:
			prc->right=prc->left+nNewWidth;
			prc->bottom=prc->top+nNewHeight;
			break;

		case WMSZ_LEFT:
		case WMSZ_BOTTOMLEFT:
			prc->bottom=prc->top+nNewHeight;
			prc->left=prc->right-nNewWidth;
			break;

		case WMSZ_TOP:
		case WMSZ_TOPRIGHT:
			prc->top=prc->bottom-nNewHeight;
			prc->right=prc->left+nNewWidth;
			break;
		}
	}

bool DD_HandleWM_GETMINMAXINFO(MINMAXINFO* pMMI)
	{
	Log(LT_Enter,"DD_HandleWM_GETMINMAXINFO");
	if (!LtWindowInitialized)
		return false;
	if (LtFullScreen)
		{
		pMMI->ptMaxTrackSize.x=L_FullScreenMode.nWidth;
		pMMI->ptMaxTrackSize.y=L_FullScreenMode.nHeight;
		pMMI->ptMinTrackSize.x=L_FullScreenMode.nWidth;
		pMMI->ptMinTrackSize.y=L_FullScreenMode.nHeight;
		pMMI->ptMaxSize.x=L_FullScreenMode.nWidth;
		pMMI->ptMaxSize.y=L_FullScreenMode.nHeight;
		return true;
		}
	if (LtMinWindowSizeSet)
		{
		pMMI->ptMinTrackSize.x=LnMinWindowWidth;
		pMMI->ptMinTrackSize.y=LnMinWindowHeight;
		}
	if (LtTempRestrictSize)
		{
		int nWidth=GnWindowWidth;
		int nHeight=GnWindowHeight;
		Log(LT_Info,"Maximizing: Maintaining window size %dx%d",nWidth,nHeight);
		DD_ClientToWindowSize(nWidth,nHeight);
		pMMI->ptMaxTrackSize.x=pMMI->ptMaxSize.x=nWidth;
		pMMI->ptMaxTrackSize.y=pMMI->ptMaxSize.y=nHeight;
		}
	else if (LtMaxWindowSizeSet)
		{
		pMMI->ptMaxTrackSize.x=LnMaxWindowWidth;
		pMMI->ptMaxTrackSize.y=LnMaxWindowHeight;
		pMMI->ptMaxSize.x=LnMaxWindowWidth;
		pMMI->ptMaxSize.y=LnMaxWindowHeight;
		}
	return LtMinWindowSizeSet||LtMaxWindowSizeSet;
	}

local bool IsPaletteCool(HWND hWnd)
	{
	Log(LT_Enter,"IsPaletteCool");

	uint8 abDestPal[256*3],*pPal;
	PALETTEENTRY apeSysPal[256],*pPE;

	HDC hDC=GetDC(hWnd);
	if (!hDC)
		{
		Log("-> can't get DC!");
		return false;
		}
	GetSystemPaletteEntries(hDC,0,256,apeSysPal);
	ReleaseDC(hWnd,hDC);

	pPE=apeSysPal;
	pPal=abDestPal;
	for (int nCol=0;nCol<256;++nCol)
		{
		*pPal++=pPE->peRed;
		*pPal++=pPE->peGreen;
		*pPal++=pPE->peBlue;
		++pPE;
		}
	abDestPal[8*3]=abDestPal[9*3]=abDestPal[246*3]=
	abDestPal[8*3+1]=abDestPal[9*3+1]=abDestPal[246*3+1]=
	abDestPal[8*3+2]=abDestPal[9*3+2]=abDestPal[246*3+2]=0;
	return memcmp(abDestPal,game_palette,256*3)==0;
	}

local LRESULT WINAPI DD_WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
	{
	HDC hdc;
	PAINTSTRUCT ps;

	if (fmv_lock)
		{
		switch (msg)
			{
			case WM_MOVE:
				GnWindowPosX=(int)(short)LOWORD(lParam);
				GnWindowPosY=(int)(short)HIWORD(lParam);
				break;

			case WM_ACTIVATEAPP:
				LtActive=bool(wParam);
				break;

			case WM_SYSCOMMAND:
				if (wParam==SC_KEYMENU)
					return 0;
				break;

			case WM_DESTROY:
				LtWindowInitialized=false;
				GhWnd=0;
				PostQuitMessage(0);
				break;
			}
		return DefWindowProc(hWnd,msg,wParam,lParam);
		}

	switch (msg)
		{
		case WM_CREATE:
			LtWindowInitialized=true;
			break;

		case WM_MOVE:
			GnWindowPosX=(int)(short)LOWORD(lParam);
			GnWindowPosY=(int)(short)HIWORD(lParam);
			Log(LT_WindowMessage,"WM_MOVE: %d,%d",GnWindowPosX,GnWindowPosY);
			break;

		case WM_SIZE:
			switch (wParam)
				{
				case SIZE_MINIMIZED:
					Log(LT_WindowMessage,"WM_SIZE: Minimized");
					LtMinimized=true;
					break;

				case SIZE_MAXIMIZED:
					LtMaximized=true;
					goto resized;

				case SIZE_RESTORED:
					LtMaximized=false;
resized:			LtMinimized=false;
					if (!LtFullScreen)
						{
						int nNewWidth=(int)(short)LOWORD(lParam);
						int nNewHeight=(int)(short)HIWORD(lParam);
						if (nNewWidth!=GnWindowWidth || nNewHeight!=GnWindowHeight)
							{
							Log(LT_WindowMessage,"WM_SIZE: %s: %dx%d->%dx%d :%s",(wParam==SIZE_MAXIMIZED)?"Maximized":(LtMinimized?"Restored":"Resized"),GnWindowWidth,GnWindowHeight,nNewWidth,nNewHeight,LtIgnoreWM_SIZE?"Ignoring":"Calling DX_WindowResized");
							GnWindowWidth=nNewWidth;
							GnWindowHeight=nNewHeight;
							if (!LtIgnoreWM_SIZE)
								DX_WindowResized();
							}
						else
							Log(LT_WindowMessage,"WM_SIZE: Size unchanged, %dx%d",GnWindowWidth,GnWindowHeight);
						}
					break;

				default:
					break;
				}
			break;

		case WM_MOVING:
			//Log(LT_WindowMessage,"WM_MOVING");
			if (LtFullScreen || LtMaximized)
				{
				GetWindowRect(hWnd,(LPRECT)lParam);	// don't allow window to resize if it's full screen
				return true;
				}
			break;

		case WM_SIZING:
			DD_HandleWM_SIZING(hWnd,int(wParam),reinterpret_cast<RECT*>(lParam));
			break;

		case WM_GETMINMAXINFO:
			if (DD_HandleWM_GETMINMAXINFO(reinterpret_cast<MINMAXINFO*>(lParam)))
				return 0;
			break;

		case WM_DISPLAYCHANGE:
			Log(LT_WindowMessage,"Display changed to %dx%dx%d",int(LOWORD(lParam)),int(HIWORD(lParam)),int(wParam));
			// should handle this a bit better
			break;

		case WM_ERASEBKGND:
			return 1;

		case WM_PAINT:
			hdc=BeginPaint(hWnd,&ps);
			if (!LtFullScreen && G_FrontBuffer && (G_AppSettings.nRenderMode==RENDERER_INTERNAL?G_RenderBuffer:G_BackBuffer))
				{
				if (G_AppSettings.nRenderMode==RENDERER_INTERNAL && !IsPaletteCool(hWnd) && G_RenderBuffer)
					DD_ClearSurface(G_RenderBuffer);
				Log(LT_WindowMessage,"WM_PAINT: Refreshing window");
				DX_UpdateFrame(false,0);	// now blat the last back buffer to the screen...
				}						// and don't bother looking for messages...
			else
				{	// full screen (non primary?) or buffer(s) not available
				Log(LT_WindowMessage,"WM_PAINT: Clearing window to black");
				FillRect(hdc,&ps.rcPaint,(HBRUSH)GetStockObject(BLACK_BRUSH));
				}
			EndPaint(hWnd,&ps);
//			Sleep(1000);
			return 0;

		case WM_NCPAINT:
		case WM_NCLBUTTONDOWN:
		case WM_NCMBUTTONDOWN:
		case WM_NCRBUTTONDOWN:
		case WM_NCLBUTTONDBLCLK:
		case WM_NCMBUTTONDBLCLK:
		case WM_NCRBUTTONDBLCLK:
			if (LtFullScreen)
				return 0;
			break;

		case WM_SETCURSOR:
			if (LtFullScreen)
				{
				//Log(LT_WindowMessage,"WM_SETCURSOR: Not displaying cursor");
				SetCursor(0);
				return true;
				}
			break;

		case WM_ACTIVATE:
			if ((LOWORD(wParam)!=WA_INACTIVE) && G_Palette && G_FrontBuffer)
				G_FrontBuffer->SetPalette(G_Palette);
			break;

		case WM_ACTIVATEAPP:
			LogWindowsMessage(hWnd,msg,wParam,lParam);
			if (wParam && !LtActive && G_AppSettings.nRenderMode==RENDERER_HAL)
				{
				if (LtFullScreen)
					{
					Log(LT_Info,"Activated, requested full screen clear");
					GtFullScreenClearNeeded=true;
					}
				}
			LtActive=bool(wParam);
			break;

		case WM_ENTERSIZEMOVE:
			LtSizingOrMoving=true;
			break;

		case WM_EXITSIZEMOVE:
			LtSizingOrMoving=false;
			break;

		case WM_PALETTECHANGED:
			if ((hWnd!=(HWND)wParam) &&
				!LtFullScreen &&
				G_Palette)
				InvalidateRect(hWnd,NULL,FALSE);
			break;

        case WM_SYSCOMMAND:
			if (wParam==SC_KEYMENU)
				return 0;
			break;

		case WM_DESTROY:
			LtWindowInitialized=false;
			GhWnd=0;
			PostQuitMessage(0);
			break;

		case MM_MCINOTIFY:
//			Log("MM_MCINOTIFY: wFlags=%x, lDevID=%x",wParam,lParam);
			break;

		default:
			LogWindowsMessage(hWnd,msg,wParam,lParam);
			break;
		}
	return DefWindowProc(hWnd,msg,wParam,lParam);
	}

local bool DD_RegisterWindowClass()
	{
	Log(LT_Enter,"DD_RegisterWindowClass");
	WNDCLASSEX wc;
	Zero(wc);
	wc.cbSize=sizeof(WNDCLASSEX);
	wc.style=0;//CS_VREDRAW|CS_HREDRAW;
	wc.lpfnWndProc=DD_WndProc;
	wc.hInstance=GhInstance;
	wc.hIcon=LoadIcon(GhInstance,MAKEINTRESOURCE(IDI_APP));
	wc.hCursor=LoadCursor(NULL,IDC_ARROW);
	//wc.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
	wc.lpszClassName=LocalString(DDWndClassName);
	if (!RegisterClassEx(&wc))
		{
		Log(LT_Error,"Couldn't register window class");
		return false;
		}
	return true;
	}

local void DD_UnregisterWindowClass()
	{
	UnregisterClass(LocalString(DDWndClassName),GhInstance);
	}

local bool DD_CreateWindow()
	{
	Log(LT_Enter,"DD_CreateWindow");
	LtInExclusiveMode=false;
	LtActive=true;
	LtWM_QUIT=false;
	LtIgnoreWM_SIZE=false;
	LtMinimized=false;
	LtMaximized=false;
	LtWindowInitialized=false;
	LtFullScreen=false;
	LtWindowShowed=true;
	LtTempRestrictSize=false;
	LtSizingOrMoving=false;
	DD_ClearMinWindowSize();
	DD_ClearMaxWindowSize();
	GhWnd=CreateWindowEx(WS_EX_APPWINDOW,					// extended style
							LocalString(DDWndClassName),	// class name
							LocalString(DDWndWindowName),	// window name
							WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX,	// style
							CW_USEDEFAULT,CW_USEDEFAULT,	// x,y
							CW_USEDEFAULT,CW_USEDEFAULT,	// w,h
							NULL,							// parent window (desktop)
							NULL,							// menu
							GhInstance,						// module
							NULL);							// ptr to creation parameters
	if (!GhWnd)
		{
		Log(LT_Error,"Couldn't create window");
		return false;
		}
	Log(LT_Info,"GhWnd=%x",(DWORD)GhWnd);
	RECT rc;
	GetWindowRect(GhWnd,&rc);
	LnOldWindowPosX=rc.left;
	LnOldWindowPosY=rc.top;
	DD_HideWindow();
	return true;
	}

local void DD_DestroyWindow()
	{
	Log(LT_Enter,"DD_DestroyWindow");
	if (GhWnd && IsWindow(GhWnd))
		{
		PostMessage(GhWnd,WM_CLOSE,0,0);
		while (DD_SpinMessageLoop())
			;
		GhWnd=0;
		}
	}

HWND DD_FindWindow()
	{
	return FindWindow(LocalString(DDWndClassName),LocalString(DDWndWindowName));
	}

bool DD_SpinMessageLoop(bool tWait)
	{
	static int nReEntry=0;
	if (nReEntry)
		Log(LT_Error,"Re-entry #%d into DD_SpinMessageLoop (bad)",nReEntry);
	++nReEntry;
	if (LtWM_QUIT)
		{
		Log(LT_Warning,"WM_QUIT already happened");
		--nReEntry;
		return false;
		}
	if (tWait)
		WaitMessage();
	MSG msg;
#ifdef DBLOG
	bool tLogged=false;
#endif
	for (;;)
		{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
			{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
			if (msg.message==WM_QUIT)
				break;
			}
		else if (LtActive && !LtMinimized)
			{
#ifdef DBLOG
			if (tLogged)
				Log(LT_Info,"Reactivated");
#endif
			--nReEntry;
			return true;
			}
		else
			{
#ifdef DBLOG
			if (!tLogged)
				{
				Log(LT_Info,"Inactive in DD_SpinMessageLoop...");
				tLogged=true;
				}
#endif
			WaitMessage();
			}
		}
	Log(LT_Info,"DD_SpinMessageLoop: WM_QUIT");
	LtWM_QUIT=true;
	GtWindowClosed=true;
	game_closedown=true;		// for Gav presumably
	GnAppReturnValue=msg.wParam;
	--nReEntry;
	return false;
	}

void DD_ShowWindow(int nShow)
	{
	Log(LT_Enter,"DD_ShowWindow");
	if (nShow==SW_SHOW && LtWindowShowed)
		return;
	LtIgnoreWM_SIZE=true;
	ShowWindow(GhWnd,nShow);
	UpdateWindow(GhWnd);
	LtIgnoreWM_SIZE=false;
	LtWindowShowed=true;
	}

void DD_HideWindow()
	{
	Log(LT_Enter,"DD_HideWindow");
	if (LtWindowShowed)
		{
		LtIgnoreWM_SIZE=true;
		ShowWindow(GhWnd,SW_HIDE);
		UpdateWindow(GhWnd);
		LtIgnoreWM_SIZE=false;
		LtWindowShowed=false;
		}
	}

void DD_MoveWindow(int nX,int nY)
	{
	Log(LT_Enter,"DD_MoveWindow");
	DD_ClientToWindowPos(nX,nY);
	SetWindowPos(GhWnd,0,nX,nY,0,0,SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOSIZE|SWP_NOZORDER);
	}

void DD_SizeWindow(int nWidth,int nHeight)
	{
	Log(LT_Enter,"DD_SizeWindow");
	DD_ClientToWindowSize(nWidth,nHeight);
	SetWindowPos(GhWnd,0,0,0,nWidth,nHeight,SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOMOVE|SWP_NOZORDER);
	}

bool DD_SetExclusiveMode(bool tAllowWindowChanges)
	{
	Log(LT_Enter,"DD_SetExclusiveMode");
	if (!GhWnd || !G_DirectDraw)
		return false;
	if (!LtInExclusiveMode)
		{
		RECT rc;
		GetWindowRect(GhWnd,&rc);
		LnOldWindowPosX=rc.left;
		LnOldWindowPosY=rc.top;
		if (tAllowWindowChanges)
			{
			DD_ShowWindow();
			//Log(LT_Info,"ShowWindow returned");
			}
		LtIgnoreWM_SIZE=true;
		DWORD dwFlags=DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_ALLOWMODEX|DDSCL_ALLOWREBOOT;
		if (!tAllowWindowChanges)
			dwFlags|=DDSCL_NOWINDOWCHANGES;
		if (DX_TRY(G_DirectDraw->SetCooperativeLevel(GhWnd,dwFlags)))
			{
			Log(LT_Error,"SetCooperativeLevel failed");
			LtIgnoreWM_SIZE=false;
			return false;
			}
		//Log(LT_Info,"SetCooperativeLevel returned");
		LtIgnoreWM_SIZE=false;
		LtInExclusiveMode=true;
		}
	return true;
	}

bool DD_ClearExclusiveMode()
	{
	Log(LT_Enter,"DD_ClearExclusiveMode");
	if (!GhWnd || !G_DirectDraw)
		return false;
	if (LtInExclusiveMode)
		{
		DD_HideWindow();
		LtIgnoreWM_SIZE=true;
		if (DX_TRY(G_DirectDraw->SetCooperativeLevel(GhWnd,DDSCL_NORMAL)))
			{
			Log(LT_Error,"SetCooperativeLevel failed");
			LtIgnoreWM_SIZE=false;
			return false;
			}
		LtInExclusiveMode=false;
		SetWindowPos(GhWnd,0,LnOldWindowPosX,LnOldWindowPosY,0,0,SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_NOSIZE|SWP_NOZORDER);
		LtIgnoreWM_SIZE=false;
		}
	return true;
	}

HRESULT DD_CreateSurface(DDSURFACEDESC& ddsd,IDirectDrawSurface3*& rpSurf)
	{
	IDirectDrawSurface* pSurf;
	HRESULT err=G_DirectDraw->CreateSurface(&ddsd,&pSurf,NULL);
	if (DX_TRY(err))
		return err;
	err=pSurf->QueryInterface(IID_IDirectDrawSurface3,(void**)&rpSurf);
	pSurf->Release();
	return err;
	}

HRESULT DD_EnsureSurfaceAvailable(IDirectDrawSurface3* pSurf,IDirectDrawSurface3* pTopSurf,bool tClearIfLost)
	{
	HRESULT err=pSurf->IsLost();
	if (err==DDERR_SURFACELOST)
		{
		Log(LT_Warning,"Surface lost... trying to restore");
		if (!pTopSurf)
			pTopSurf=pSurf;
		err=pTopSurf->Restore();
		Log(LT_Warning,"Restore %s",SUCCEEDED(err)?"succeeded":"failed");
		if (tClearIfLost && SUCCEEDED(err))
			{
			if (!DD_ClearSurface(pSurf))
				Log(LT_Error,"Can't clear restored surface");
			}
		}
	return err;
	}

bool DD_ClearSurface(IDirectDrawSurface3* pSurf,RECT* pRect,DWORD dwColour)
	{
	DDBLTFX bltfx;
	InitDXStruct(bltfx);
	bltfx.dwFillColor=dwColour;
	if (DX_TRY(pSurf->Blt(pRect,NULL,NULL,DDBLT_COLORFILL|DDBLT_WAIT,&bltfx)))
		return false;
	return true;
	}

bool DD_ClearZBuffer(IDirectDrawSurface3* pSurf,RECT* pRect)
	{
	DDBLTFX bltfx;
	InitDXStruct(bltfx);
	bltfx.dwFillDepth=0xffffffff;
	if (DX_TRY(pSurf->Blt(pRect,NULL,NULL,DDBLT_DEPTHFILL|DDBLT_WAIT,&bltfx)))
		return false;
	return true;
	}

// DD_LockSurface: Gives access to the hard bytez of the surface.
// Takes global Win16Mutex semaphore to enable Log() buffering (so DBLog IPC doesn't crash)
// Args:
//	pSurf:		ptr to surface you want to access
//	pDDSD:		ptr to a DDSURFACEDESC structure, to receive lpSurface, lPitch & all other surface info
//	dwFlags:	flags to pass to IDirectDrawSurface::Lock.  Defaults to DDLOCK_WAIT|DDLOCK_WRITEONLY
// Returns:
//	DD_OK or DDERR_whatever
// Notes:
//	This could fail, and if it does you're out of luck.  Try going for the next frame...
HRESULT DD_LockSurface(IDirectDrawSurface3* pSurf,DDSURFACEDESC& rDDSD,DWORD dwFlags)
	{
	InitDXStruct(rDDSD);
	HRESULT err=pSurf->Lock(NULL,&rDDSD,dwFlags,NULL);
	if (DX_TRY(err))
		Log(LT_Error,"DD_LockSurface failed");
	else
		{
		++GnDirectDrawLocks;
		//Log(LT_Info,"Locked: %08x,%d",rDDSD.lpSurface,rDDSD.lPitch);
		}
	return err;
	}

// DD_Unlock: Finish with hard byte access to the surface.
// You MUST call this when you've finished hitting the bitmap, or everything will crash.
// Args:
//	pSurf:		ptr to surface you want to access
//	pDDSD:		ptr to a DDSURFACEDESC structure, to receive lpSurface, lPitch & all other surface info
// Returns:
//	DD_OK or DDERR_whatever
// Notes:
//	This could fail, but I doubt if anyone cares.  It would be very wierd for it to fail...
HRESULT DD_UnlockSurface(IDirectDrawSurface3* pSurf,DDSURFACEDESC& rDDSD)
	{
	HRESULT err;
	err=pSurf->Unlock(rDDSD.lpSurface);
	if (!DX_TRY(err))	// needs log buffering
		if (--GnDirectDrawLocks==0)
			FlushLogStore();
	return err;
	}

bool DD_CopyBitmapToSurface(IDirectDrawSurface3* pSurf,void* pvSrc)
	{
	DDSURFACEDESC desc;
	if (!DX_FAILED(DD_LockSurface(pSurf,desc)))
		{
		int nWidth=desc.dwWidth;
		int nHeight=desc.dwHeight;
		char* pSrc=(char*)pvSrc;
		char* pDest=(char*)desc.lpSurface;
		while (nHeight--)
			{
			memcpy(pDest,pSrc,nWidth);
			pSrc+=nWidth;
			pDest+=desc.lPitch;
			}
		DD_UnlockSurface(pSurf,desc);
		return true;
		}
	return false;
	}

DWORD DD_BPPToDDBD(int bpp)
	{
    switch(bpp)
		{
		case 1:
			return DDBD_1;
		case 2:
			return DDBD_2;
		case 4:
			return DDBD_4;
		case 8:
			return DDBD_8;
		case 16:
			return DDBD_16;
		case 24:
			return DDBD_24;
		case 32:
			return DDBD_32;
		default:
			return 0;
		}
	}

local void CalcBitsAndShift(DWORD dwBitMask,int& rnBits,int& rnShift)
	{
	if (dwBitMask)
		{
		int nCount=0;
		while (!(dwBitMask&1))
			{
			dwBitMask>>=1;
			++nCount;
			}
		rnShift=nCount;
		nCount=0;
		while (dwBitMask)
			{
			dwBitMask>>=1;
			++nCount;
			}
		rnBits=nCount;
		}
	else	
		rnBits=rnShift=0;
	}

local inline DWORD CalcBitMask(int nBits,int nShift)
	{
	return ((1<<nBits)-1)<<nShift;
	}

void PixelFormat::SetFromDDPF(DDPIXELFORMAT& rDDPF)
	{
	dwRMask=rDDPF.dwRBitMask;
	dwGMask=rDDPF.dwGBitMask;
	dwBMask=rDDPF.dwBBitMask;
	dwAMask=rDDPF.dwRGBAlphaBitMask;
	CalcBitsAndShift(dwRMask,nRBits,nRShift);
	CalcBitsAndShift(dwGMask,nGBits,nGShift);
	CalcBitsAndShift(dwBMask,nBBits,nBShift);
	CalcBitsAndShift(dwAMask,nABits,nAShift);
	}

DWORD DD_PixelFromRGB(PixelFormat& rPF,int nR,int nG,int nB)
	{
	return ((nR>>(8-rPF.nRBits))<<rPF.nRShift)|((nG>>(8-rPF.nGBits))<<rPF.nGShift)|((nB>>(8-rPF.nBBits))<<rPF.nBShift);
	}

DWORD DD_PixelFromRGBA(PixelFormat& rPF,int nR,int nG,int nB,int nA)
	{
	return ((nR>>(8-rPF.nRBits))<<rPF.nRShift)|((nG>>(8-rPF.nGBits))<<rPF.nGShift)|((nB>>(8-rPF.nBBits))<<rPF.nBShift)|((nA>>(8-rPF.nABits))<<rPF.nAShift);
	}

// Fill in rMode with details about the current display mode
// Intended to be used in windowed mode.
// Returns false if something went wrong...
bool DD_GetDisplayMode(ScreenMode& rMode)
	{
	Log(LT_Enter,"DD_GetDisplayMode");
	DDSURFACEDESC ddsd;
	InitDXStruct(ddsd);
	if (DX_TRY(G_DirectDraw->GetDisplayMode(&ddsd)) ||
		!(ddsd.dwFlags&DDSD_WIDTH) ||
		!(ddsd.dwFlags&DDSD_HEIGHT) ||
		!(ddsd.dwFlags&DDSD_PIXELFORMAT) ||
		!(ddsd.ddpfPixelFormat.dwFlags&DDPF_RGB))
		return false;
	rMode.nWidth=ddsd.dwWidth;
	rMode.nHeight=ddsd.dwHeight;
	rMode.nBPP=ddsd.ddpfPixelFormat.dwRGBBitCount;
	if (ddsd.ddpfPixelFormat.dwFlags&DDPF_PALETTEINDEXED8)
		rMode.nType=MODETYPE_PALETTED;
	else
		rMode.nType=MODETYPE_RGB;
	return true;
	}

// Switch to full screen mode
bool DD_GoFullScreen(const ScreenMode& rMode)
	{
	Log(LT_Enter,"DD_GoFullScreen");
	L_FullScreenMode=rMode;
	if (!DD_SetExclusiveMode())
		return false;
	DWORD dwFlags=0;
	if (rMode.nType==MODETYPE_STANDARDVGA)
		{
		Log(LT_Info,"Using standard VGA mode");
		dwFlags=DDSDM_STANDARDVGAMODE;
		}
	LtIgnoreWM_SIZE=true;
	if (DX_TRY(G_DirectDraw->SetDisplayMode(rMode.nWidth,
											rMode.nHeight,
											rMode.nBPP,
											0,	// maybe I could support this.  there is possibly stress with leaving it at 0...
											dwFlags)))
		return false;
	LtIgnoreWM_SIZE=false;
	LtFullScreen=true;
	return true;
	}

// Switch to windowed mode
bool DD_GoWindowed(int nWidth,int nHeight,ScreenMode& rModeUsed)
	{
	Log(LT_Enter,"DD_GoWindowed");
	if (!DD_ClearExclusiveMode())
		return false;
	if (!DD_GetDisplayMode(rModeUsed))
		return false;
	int nMaxWidth,nMaxHeight;
	nMaxWidth=rModeUsed.nWidth;
	nMaxHeight=DD_CalcAspectAdjustedHeight(nMaxWidth,rModeUsed.nHeight);
	if (nMaxHeight>rModeUsed.nHeight)
		{
		nMaxHeight=rModeUsed.nHeight;
		nMaxWidth=DD_CalcAspectAdjustedWidth(rModeUsed.nWidth,nMaxHeight);
		}
	DD_SetMaxWindowSize(nMaxWidth,nMaxHeight);
	if (nWidth>nMaxWidth || nHeight>nMaxHeight)
		{
		nWidth=nMaxWidth;
		nHeight=nMaxHeight;
		}
	LtFullScreen=false;
	LtIgnoreWM_SIZE=true;
	DD_SizeWindow(nWidth,nHeight);
	LtIgnoreWM_SIZE=false;
	RECT rc;
	GetClientRect(GhWnd,&rc);
	MapWindowPoints(GhWnd,0,(POINT*)&rc,2);
	if ((rc.left<=0 && rc.right>=rModeUsed.nWidth) || (rc.top<=0 && rc.bottom>=rModeUsed.nHeight))
		DD_ShowWindow(SW_SHOWMAXIMIZED);
	else
		DD_ShowWindow();
	rModeUsed.nWidth=nWidth;
	rModeUsed.nHeight=nHeight;
	return true;
	}

// Determine if an adapter can run in a window, using a) software and b) hardware.
// Note that there still might not be enough video memory available.
// This can only be found out by trying to initialise the renderer.
// This should be called wherever necessary to update the status, because the user could change video mode at any time.
// Note that this function does a DD_Create/Cleanup!!!
void DD_DetermineWindowedCapabilities(DisplayAdapter& adapter)
	{
	Log(LT_Enter,"DD_DetermineWindowedCapabilities");
	adapter.tInternalCanDoWindowed=false;
	adapter.tHALCanDoWindowed=false;
	adapter.nMaxWindowWidth=0;
	if (adapter.pGUID)
		return;					// can only run in a window on the primary display adapter.

	if (!DD_Create(adapter.pGUID))
		return;					// oops, shouldn't happen!!!
	ScreenMode mode;
	bool tOK=DD_GetDisplayMode(mode);
	DD_Release();
	if (!tOK)
		return;

	adapter.tInternalCanDoWindowed=(mode.nType==MODETYPE_PALETTED);
	DWORD dwDDBD=DD_BPPToDDBD(mode.nBPP);
	adapter.tHALCanDoWindowed=(adapter.tHAL &&
								(adapter.HAL_Desc.dwFlags&D3DDD_DEVICERENDERBITDEPTH) &&
								(adapter.HAL_Desc.dwDeviceRenderBitDepth&dwDDBD));
	int nDisplayWidth=mode.nWidth;
	Align(nDisplayWidth,32);
	int nDisplayHeight=(nDisplayWidth*3)>>2;
	if (nDisplayHeight>mode.nHeight)
		{
		nDisplayWidth=(mode.nHeight<<2)/3;
		Align(nDisplayWidth,32);
		}
	adapter.nMaxWindowWidth=nDisplayWidth;
	}

bool ScreenMode::operator<(ScreenMode& r)
	{
	int nPixels=nWidth*nHeight;
	int nPixels_r=r.nWidth*r.nHeight;
	if (nPixels<nPixels_r)
		return true;
	if (nPixels==nPixels_r)
		{
		if (nBPP<r.nBPP)
			return true;
		if (nBPP==r.nBPP)
			{
			if (nType<r.nType)
				return true;
			}
		}
	return false;
	}

bool DD_GetDisplayModes()
	{
	Log(LT_Info,"Enumerating modes...");
	for_all(p,G_DisplayAdapterList)
		{
		DisplayAdapter& adapter=G_DisplayAdapterList[p];
		DD_Create(adapter.pGUID);
		DD_SetExclusiveMode(false);		// no window changes...
		G_DirectDraw->EnumDisplayModes(DDEDM_STANDARDVGAMODES,0,static_cast<void*>(&adapter),DD_EnumModesCallback);
		DD_ClearExclusiveMode();
		DD_Release();
		}
	return true;
	}

bool DD_Init()
	{
	Log(LT_Enter,"DD_Init");
	GnAppReturnValue=0;
	return DD_RegisterWindowClass() &&
		   DD_CreateWindow() &&
		   DD_MakeAdapterList() &&
		   G_DisplayAdapterList.GetCount()!=0 &&
		   DD_GetDisplayModes();
	}

void DD_Cleanup()
	{
	Log(LT_Enter,"DD_Cleanup");
	DD_DestroyWindow();
	DD_UnregisterWindowClass();
	// List<> destructor will empty adapter list
	}

Position DD_FindPreferredAdapter(GUID* pGUID)
	{
	Log(LT_Enter,"DD_FindPreferredAdapter");
	Position found=0;
	for_all(p,G_DisplayAdapterList)
		{
		DisplayAdapter& adapter=G_DisplayAdapterList[p];
		if (pGUID && adapter.AdapterGUID==*pGUID)
			{
			found=p;
			break;
			}
		}
	if (!found)
		{
		found=G_PrimaryDisplayAdapter;
		if (!found)
			{
			Log(LT_Error,"No primary display adapter");
			return 0;
			}
		}
	DisplayAdapter& adapter=G_DisplayAdapterList[found];
	Log(LT_Info,"Found adapter: %s (%s)",static_cast<char*>(adapter.strDescription),
											static_cast<char*>(adapter.strName));
	return found;
	}

void DD_Start()
	{
	Log(LT_Enter,"DD_Start");
	if (!G_AppSettings.DisplayAdapter)
		throw INIT_ERR_CantCreateDirectDraw;
	G_DisplayAdapter=G_DisplayAdapterList[G_AppSettings.DisplayAdapter];
	if (!DD_Create(G_DisplayAdapter.pGUID))	// Init DirectDraw on that card
		throw INIT_ERR_CantCreateDirectDraw;
	}

void DD_Finish()
	{
	Log(LT_Enter,"DD_Finish");
	if (LtInExclusiveMode)
		DD_ClearExclusiveMode();
	DD_Release();
	}
