// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 1998-2000 by DooM Legacy Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
/// \file
/// \brief Zak Larue-Buckley's GX and GAPI library v1.0

#include "../doomdef.h"
//#define WIN32_LEAN_AND_MEAN
#define RPC_NO_WINDOWS_H
#include <windows.h>
#include <windowsx.h>
#include "../i_system.h"
#include "dx_error.h"

#include "fabdxlib.h"

#define NT4COMPAT   //always defined, always compatible


// globals

IDirectDraw2*              DDr2 = NULL;
IDirectDrawSurface*        ScreenReal = NULL;    // DirectDraw primary surface
IDirectDrawSurface*        ScreenVirtual = NULL; // DirectDraw back surface
IDirectDrawPalette*        DDPalette = NULL;     // The primary surface palette
static IDirectDrawClipper *windclip = NULL;      // clipper for windowed mode

BOOL                       bAppFullScreen;       // true for fullscreen exclusive mode,

int                        windowPosX = 0;       // current position in windowed mode
int                        windowPosY = 0;

int                        ScreenWidth;
int                        ScreenHeight;
BOOL                       ScreenLocked;         // Screen surface is being locked
int                        ScreenPitch;          // offset from one line to the next
LPBYTE                     ScreenPtr;            // memory of the surface


//
// CreateNewSurface
//
static inline IDirectDrawSurface* CreateNewSurface(int dwWidth,
                                                   int dwHeight,
                                                   int dwSurfaceCaps)
{
	DDSURFACEDESC       ddsd;
	HRESULT             hr;
	LPDIRECTDRAWSURFACE psurf;

	ZeroMemory(&ddsd, sizeof (ddsd));
	ddsd.dwSize = sizeof (ddsd);
	ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;

	ddsd.ddsCaps.dwCaps = dwSurfaceCaps;

	ddsd.dwHeight = dwHeight;
	ddsd.dwWidth = dwWidth;

	hr = IDirectDraw2_CreateSurface (DDr2, &ddsd, &psurf, NULL);

	if (hr == DD_OK)
	{
		//DDCOLORKEY ddck;
		IDirectDrawSurface_Restore(psurf);

		//hr = IDirectDrawSurface_GetColorKey(DDCKEY_SRCBLT, &ddck);
		//psurf->SetColorKey(DDCKEY_SRCBLT, &ddck);
	}
	else
		psurf = NULL;

	return psurf;
}

//
// wow! from 320x200x8 up to 1600x1200x32 thanks Banshee! :)
//
static HRESULT WINAPI myEnumModesCallback (LPDDSURFACEDESC surf, LPVOID lpContext)
{
	APPENUMMODESCALLBACK pfnContext = lpContext;

	if (pfnContext) pfnContext(surf->dwWidth,
		surf->dwHeight,surf->ddpfPixelFormat.
#ifdef DUMMYUNIONNAMEN
		DUMMYUNIONNAMEN(1).
#endif
		dwRGBBitCount
		);

		/*CONS_Printf ("%dx%dx%d bpp %d refresh\n",
		surf->dwWidth,
		surf->dwHeight,
		surf->ddpfPixelFormat.dwRGBBitCount,
	surf->dwRefreshRate);*/

	return  DDENUMRET_OK;
}


//
// Application call here to enumerate display modes
//
BOOL EnumDirectDrawDisplayModes (APPENUMMODESCALLBACK appFunc)
{
	LPVOID lpappFunc = appFunc;

	if (DDr2 == NULL)
		return FALSE;

	// enumerate display modes
	// Carl: DirectX 3.x apparently does not support VGA modes. Who cares. :)
	// faB: removed DDEDM_REFRESHRATES, detects too many modes, plus we don't care of refresh rate.
	if (bDX0300)
		IDirectDraw2_EnumDisplayModes (DDr2, 0 /*| DDEDM_REFRESHRATES*/,
		                               NULL, lpappFunc, myEnumModesCallback);
	else
		IDirectDraw2_EnumDisplayModes (DDr2, DDEDM_STANDARDVGAMODES /*| DDEDM_REFRESHRATES*/,
		                               NULL, lpappFunc, myEnumModesCallback);
	return TRUE;
}


//
// Create the DirectDraw object for later
//
BOOL CreateDirectDrawInstance (VOID)
{
	HRESULT hr;
	IDirectDraw* DDr;
	IDirectDraw** rp = &DDr;
	IDirectDraw2** rp2 = &DDr2;
	LPVOID *tp = (LPVOID *)rp2;

	//
	// create an instance of DirectDraw object
	//
	if (FAILED(hr = DirectDrawCreate(NULL, rp, NULL)))
		I_Error("DirectDrawCreate FAILED: %s", DXErrorToString(hr));

	// change interface to IDirectDraw2
	if (FAILED(hr = IDirectDraw_QueryInterface (DDr, &IID_IDirectDraw2, tp)))
		I_Error("Failed to query DirectDraw2 interface: %s", DXErrorToString(hr));

	// release the interface we don't need
	IDirectDraw_Release (DDr);
	return TRUE;
}


//
// - returns true if DirectDraw was initialized properly
//
int  InitDirectDrawe (HWND appWin, int width, int height, int bpp, int fullScr)
{
	DDSURFACEDESC ddsd; // DirectDraw surface description for allocating
	DDSCAPS       ddscaps;
	HRESULT       ddrval;

	DWORD         dwStyle;
	RECT          rect;

	// enumerate directdraw devices
	//if (FAILED(DirectDrawEnumerate (myEnumDDDevicesCallback, NULL)))
	//      I_Error("Error with DirectDrawEnumerate");

	if (!DDr2)
		CreateDirectDrawInstance();

	// remember what screen mode we are in
	bAppFullScreen = fullScr;
	ScreenHeight = height;
	ScreenWidth = width;

	if (bAppFullScreen)
	{
		// Change window attributes
		dwStyle = WS_POPUP | WS_VISIBLE;
		SetWindowLong (appWin, GWL_STYLE, dwStyle);
		SetWindowPos(appWin, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE |
			SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);

		// Get exclusive mode
		ddrval = IDirectDraw2_SetCooperativeLevel(DDr2, appWin, DDSCL_EXCLUSIVE |
		                                          DDSCL_FULLSCREEN |
		                                          DDSCL_ALLOWREBOOT);
		if (ddrval != DD_OK)
			I_Error("SetCooperativeLevel FAILED: %s\n", DXErrorToString(ddrval));

		// Switch from windows desktop to fullscreen

#ifdef NT4COMPAT
		ddrval = IDirectDraw2_SetDisplayMode(DDr2, width, height, bpp, 0, 0);
#else
		ddrval = IDirectDraw2_SetDisplayMode(DDr2, width, height, bpp, 0, DDSDM_STANDARDVGAMODE);
#endif
		if (ddrval != DD_OK)
			I_Error("SetDisplayMode FAILED: %s\n", DXErrorToString(ddrval));

		// This is not really needed, except in certain cases. One case
		// is while using MFC. When the desktop is initally at 16bpp, a mode
		// switch to 8bpp somehow causes the MFC window to not fully initialize
		// and a CreateSurface will fail with DDERR_NOEXCLUSIVEMODE. This will
		// ensure that the window is initialized properly after a mode switch.

		ShowWindow(appWin, SW_SHOW);

		// Create the primary surface with 1 back buffer. Always zero the
		// DDSURFACEDESC structure and set the dwSize member!

		ZeroMemory(&ddsd, sizeof (ddsd));
		ddsd.dwSize = sizeof (ddsd);
		ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;

		// for fullscreen we use page flipping, for windowed mode, we blit the hidden surface to
		// the visible surface, in both cases we have a visible (or 'real') surface, and a hidden
		// (or 'virtual', or 'backbuffer') surface.
		ddsd.dwBackBufferCount = 1;

		ddrval = IDirectDraw2_CreateSurface(DDr2,&ddsd, &ScreenReal, NULL);
		if (ddrval != DD_OK)
			I_Error("CreateSurface Primary Screen FAILED");

		// Get a pointer to the back buffer

		ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
		ddrval = IDirectDrawSurface_GetAttachedSurface(ScreenReal,&ddscaps, &ScreenVirtual);
		if (ddrval != DD_OK)
			I_Error("GetAttachedSurface FAILED");
	}
	else
	{
		rect.top = 0;
		rect.left = 0;
		rect.bottom = height-1;
		rect.right = width-1;

		// Change window attributes

		dwStyle = GetWindowStyle(appWin);
		dwStyle &= ~WS_POPUP;
		dwStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION;

		SetWindowLong(appWin, GWL_STYLE, dwStyle);

		// Resize the window so that the client area is the requested width/height

		AdjustWindowRectEx(&rect, GetWindowStyle(appWin), GetMenu(appWin) != NULL,
		                   GetWindowExStyle(appWin));

		// Just in case the window was moved off the visible area of the
		// screen.

		SetWindowPos(appWin, NULL, 0, 0, rect.right-rect.left,
		             rect.bottom-rect.top, SWP_NOMOVE | SWP_NOZORDER |
		             SWP_NOACTIVATE);

		SetWindowPos(appWin, HWND_NOTOPMOST, 0, 0, 0, 0,
		             SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

		// Exclusive mode is normal since it's in windowed mode and needs
		// to cooperate with GDI

		ddrval = IDirectDraw2_SetCooperativeLevel(DDr2,appWin, DDSCL_NORMAL);
		if (ddrval != DD_OK)
			I_Error("SetCooperativeLevel FAILED");

		// Always zero the DDSURFACEDESC structure and set the dwSize member!

		ZeroMemory(&ddsd, sizeof (ddsd));
		ddsd.dwSize = sizeof (ddsd);
		ddsd.dwFlags = DDSD_CAPS;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

		// Create the primary surface

		ddrval = IDirectDraw2_CreateSurface(DDr2,&ddsd, &ScreenReal, NULL);
		if (ddrval != DD_OK)
			I_Error("CreateSurface Primary Screen FAILED");

		// Create a back buffer for offscreen rendering, this will be used to
		// blt to the primary

		ScreenVirtual = CreateNewSurface(width, height, DDSCAPS_OFFSCREENPLAIN |
		                                 DDSCAPS_SYSTEMMEMORY);
		if (ScreenVirtual == NULL)
			I_Error("CreateSurface Secondary Screen FAILED");

		/// \todo get the desktop bit depth, and build a lookup table
		/// for quick conversions of 8bit color indexes 0-255 to desktop colors
		/// eg: 256 entries of equivalent of palette colors 0-255 in 15,16,24,32 bit format
		/// when blit virtual to real, convert pixels through lookup table..

		// Use a clipper object for clipping when in windowed mode
		// (make sure our drawing doesn't affect other windows)

		ddrval = IDirectDraw2_CreateClipper (DDr2, 0, &windclip, 0);
		if (ddrval != DD_OK)
			I_Error("CreateClipper FAILED");

		// Associate the clipper with the window.
		ddrval = IDirectDrawClipper_SetHWnd (windclip,0, appWin);
		if (ddrval != DD_OK)
			I_Error("Clipper -> SetHWnd  FAILED");

		// Attach the clipper to the surface.
		ddrval = IDirectDrawSurface_SetClipper (ScreenReal,windclip);
		if (ddrval != DD_OK)
			I_Error("PrimaryScreen -> SetClipperClipper  FAILED");
	}

	return TRUE;
}


//
// Free all memory
//
VOID CloseDirectDraw (VOID)
{
	ReleaseChtuff();
	if (DDr2)
	{
		IDirectDraw2_Release(DDr2);
		DDr2 = NULL;
	}
}


//
// Release DirectDraw stuff before display mode change
//
VOID ReleaseChtuff (VOID)
{
	if (!DDr2)
		return;
	if (windclip)
	{
		IDirectDrawClipper_Release(windclip);
		windclip = NULL;
	}
	if (DDPalette)
	{
		IDirectDrawPalette_Release(DDPalette);
		DDPalette = NULL;
	}
	// If the app is fullscreen, the back buffer is attached to the
	// primary. Releasing the primary buffer will also release any
	// attached buffers, so explicitly releasing the back buffer is not
	// necessary.

	if (!bAppFullScreen && ScreenVirtual)
	{
		IDirectDrawSurface_Release(ScreenVirtual);   // release hidden surface
		ScreenVirtual = NULL;
	}
	if (ScreenReal)
	{
		IDirectDrawSurface_Release(ScreenReal);                      // and attached backbuffers for bAppFullScreen mode
		ScreenReal = NULL;
	}
}


//
// Clear the surface to color
//
VOID ClearSurface(IDirectDrawSurface* surface, int color)
{
	DDBLTFX ddbltfx;

	// Use the blter to do a color fill to clear the back buffer
	ddbltfx.dwSize = sizeof (ddbltfx);
	ddbltfx.
#ifdef DUMMYUNIONNAMEN
	 DUMMYUNIONNAMEN(5).
#endif
	 dwFillColor = color;
	IDirectDrawSurface_Blt(surface,NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);

}

//
// Flip the real page with virtual page
// - in bAppFullScreen mode, do page flipping
// - in windowed mode, copy the hidden surface to the visible surface
//
// waitflip : if not 0, wait for page flip to end
BOOL ScreenFlip(int waitflip)
{
	HRESULT hr;
	RECT rect;

	waitflip = 0;
	if (bAppFullScreen)
	{
		//hr = IDirectDrawSurface_GetFlipStatus (ScreenReal, DDGFS_);

		// In full-screen exclusive mode, do a hardware flip.
		hr = IDirectDrawSurface_Flip(ScreenReal, NULL, DDFLIP_WAIT);   //return immediately

		// If the surface was lost, restore it.
		if (hr == DDERR_SURFACELOST)
		{
			IDirectDrawSurface_Restore(ScreenReal);

			// The restore worked, so try the flip again.
			hr = IDirectDrawSurface_Flip(ScreenReal, 0, DDFLIP_WAIT);
		}
	}
	else
	{
		rect.left = windowPosX;
		rect.top = windowPosY;
		rect.right = windowPosX + ScreenWidth - 1;
		rect.bottom = windowPosY + ScreenHeight - 1;

		// Copy the back buffer to front.
		hr = IDirectDrawSurface_Blt(ScreenReal, &rect, ScreenVirtual, 0, DDBLT_WAIT, 0);

		if (hr != DD_OK)
		{
			// If the surfaces were lost, restore them.
			if (IDirectDrawSurface_IsLost(ScreenReal) == DDERR_SURFACELOST)
				IDirectDrawSurface_Restore(ScreenReal);

			if (IDirectDrawSurface_IsLost(ScreenVirtual) == DDERR_SURFACELOST)
				IDirectDrawSurface_Restore(ScreenVirtual);

			// Retry the copy.
			hr = IDirectDrawSurface_Blt(ScreenReal,&rect, ScreenVirtual, 0, DDBLT_WAIT, 0);
		}
	}

	if (hr != DD_OK)
		I_Error("ScreenFlip() : couldn't Flip surfaces");

	return FALSE;
}

//
// Print a text to the surface
//
VOID TextPrint(int x, int y, LPCSTR message)
{
	HRESULT hr;
	HDC hdc = NULL;

	// Get the device context handle.
	hr = IDirectDrawSurface_GetDC(ScreenVirtual,&hdc);
	if (hr != DD_OK)
		return;

	// Write the message.
	SetBkMode(hdc, TRANSPARENT);
	SetTextColor(hdc, RGB(255, 255, 255));
	TextOutA(hdc, x, y, message, (int)strlen(message));

	// Release the device context.
	hr = IDirectDrawSurface_ReleaseDC(ScreenVirtual,hdc);
}

//
// Lock surface before multiple drawings by hand, for speed
//
boolean LockScreen(VOID)
{
	DDSURFACEDESC ddsd;
	HRESULT ddrval;

	ZeroMemory(&ddsd, sizeof (ddsd));
	ddsd.dwSize = sizeof (ddsd);

	// attempt to Lock the surface
	ddrval = IDirectDrawSurface_Lock(ScreenVirtual, NULL, &ddsd, DDLOCK_WAIT, NULL);

	// Always, always check for errors with DirectX!
	// If the surface was lost, restore it.
	if (ddrval == DDERR_SURFACELOST)
	{
		ddrval = IDirectDrawSurface_Restore(ScreenReal);

		// now retry to get the lock
		ddrval = IDirectDrawSurface_Lock(ScreenVirtual, NULL, &ddsd, DDLOCK_WAIT, NULL);
	}

	if (ddrval == DD_OK)
	{
		ScreenLocked = TRUE;
		ScreenPtr    = (LPBYTE)ddsd.lpSurface;
		ScreenPitch = ddsd.
#ifdef DUMMYUNIONNAMEN
		 DUMMYUNIONNAMEN(1).
#endif
		 lPitch;
	}
	else
	{
		ScreenLocked = FALSE;
		ScreenPtr = NULL;
		ScreenPitch = 0;
		//I_Error("LockScreen() : couldn't restore the surface.");
		return false;
	}
	return true;
}

//
// Unlock surface
//
VOID UnlockScreen(VOID)
{
	if (DD_OK != IDirectDrawSurface_Unlock(ScreenVirtual,NULL))
		I_Error("Couldn't UnLock the renderer!");

	ScreenLocked = FALSE;
	ScreenPtr    = NULL;
	ScreenPitch = 0;
}

// Blit virtual screen to real screen
//faB: note: testing 14/03/1999, see if it is faster than memcopy of virtual to
/*
static LPDIRECTDRAWSURFACE lpDDS = NULL;
VOID BlitScreen(VOID)
{
	HRESULT hr;

	if (!lpDDS)
		I_Error("lpDDS NULL");

	hr = IDirectDrawSurface_BltFast(ScreenVirtual,
	                                    0, 0,    // Upper left xy of destination
	                                    lpDDS, // Source surface
	                                    NULL,        // Source rectangle = entire surface
	                                    DDBLTFAST_WAIT | DDBLTFAST_NOCOLORKEY);
	if (FAILED(hr))
		I_Error("BltFast FAILED");
}

VOID MakeScreen(int width, int height, BYTE* lpSurface)
{
	HRESULT hr;
	DDSURFACEDESC ddsd;

	// Initialize the surface description.
	ZeroMemory (&ddsd, sizeof ddsd);
	ZeroMemory (&ddsd.ddpfPixelFormat, sizeof (DDPIXELFORMAT));
	ddsd.dwSize = sizeof ddsd;
	ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | //DDSD_LPSURFACE |
	               DDSD_PITCH | DDSD_PIXELFORMAT | DDSD_CAPS;
	ddsd.dwWidth = width;
	ddsd.dwHeight= height;
	ddsd.lPitch  = width;
	ddsd.lpSurface = lpSurface;
	ddsd.ddsCaps.dwCaps = DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN;

	// Set up the pixel format for 8-bit
	ddsd.ddpfPixelFormat.dwSize = sizeof (DDPIXELFORMAT);
	ddsd.ddpfPixelFormat.dwFlags= DDPF_RGB | DDPF_PALETTEINDEXED8;
	ddsd.ddpfPixelFormat.dwRGBBitCount = 8;

	//
	ddsd.ddpfPixelFormat.dwRGBBitCount = (DWORD)DEPTH*8;
	ddsd.ddpfPixelFormat.dwRBitMask    = 0x00FF0000;
	ddsd.ddpfPixelFormat.dwGBitMask    = 0x0000FF00;
	ddsd.ddpfPixelFormat.dwBBitMask    = 0x000000FF;

	// Create the surface
	hr = IDirectDraw2_CreateSurface(DDr2, &ddsd, &lpDDS, NULL);
	if (FAILED(hr))
		I_Error("MakeScreen FAILED: %s",DDError(hr));
	//ddsd.lpSurface = lpSurface;
}
*/

//
// Create a palette object
//
VOID CreateDDPalette (PALETTEENTRY* colorTable)
{
	HRESULT  ddrval;
	ddrval = IDirectDraw2_CreatePalette(DDr2,DDPCAPS_8BIT|DDPCAPS_ALLOW256, colorTable, &DDPalette, NULL);
	if (ddrval != DD_OK)
		I_Error("couldn't CreatePalette");
};


//
// Free the palette object
//
VOID DestroyDDPalette (VOID)
{
	if (DDPalette)
	{
		IDirectDrawPalette_Release(DDPalette);
		DDPalette = NULL;
	}
}

//
// Set a a full palette of 256 PALETTEENTRY entries
//
VOID SetDDPalette(PALETTEENTRY* pal)
{
	// create palette first time
	if (DDPalette == NULL)
		CreateDDPalette(pal);
	else
		IDirectDrawPalette_SetEntries(DDPalette, 0, 0, 256, pal);
	// setting the same palette to the same surface again does not increase
	// the reference count
	IDirectDrawSurface_SetPalette(ScreenReal, DDPalette);
}

//
// Wait for vsync, gross
//
VOID WaitVbl(VOID)
{
	IDirectDraw2_WaitForVerticalBlank(DDr2, DDWAITVB_BLOCKBEGIN, NULL);
}
