///////////////////////////////////////////////////////////////////////////////
/// @file DOpenGL-Windows.cpp
///
/// @brief Windows Specific Functions for OpenGL driver
///
/// @author JesusFreak
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Feb 2002
/// @author JesusFreak
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#define GLH_EXT_SINGLE_FILE
#include "DOpenGL.hpp"
#include "glext.h"

typedef BOOL (APIENTRY * PFWGLSWAPINTERVALEXT) (int interval);

PFWGLSWAPINTERVALEXT wglSwapIntervalEXT = NULL;
PFNGLDRAWRANGEELEMENTSPROC glDrawRangeElementsEXT = NULL;

// A flag to indicate if the adapter information has been logged
deBoolean AdapterInfoLogged = deFALSE;

void deOpenGL::Present()
{
	// Update the screen
	SwapBuffers(DC);
}

deBoolean deOpenGL::SetDisplay(IdeDriver::deDisplay *Display)
{
	// Set our current display to the display we were passed.
	m_CurrentDisplay = *Display;

	LogInfo("Initializing Display Adapter %d:\n\tWidth: %d\tHeight: %d\n\n", Display->Adapter, Display->Width, Display->Height);

	// If we aren't setting the display to a windowed mode.
	if(!Display->Windowed)
	{
		// Get the display device we want to use
		DISPLAY_DEVICE DisplayDevice;
		DisplayDevice.cb = sizeof(DisplayDevice);
		if(!EnumDisplayDevices(NULL,Display->Adapter,&DisplayDevice,0))
		{
			return deFALSE;
		}
		
		 // Create a DEVMODE object for the display mode we want to change to
		DEVMODE DisplayMode;

		// Clear the struct
		memset(&DisplayMode,0,sizeof(DisplayMode));

		// Set the struct's variables
		DisplayMode.dmSize = sizeof(DisplayMode);
		DisplayMode.dmPelsWidth = Display->Width;
		DisplayMode.dmPelsHeight = Display->Height;
		DisplayMode.dmBitsPerPel = GetBPP(Display->BackBpp); // The number of bits per pixel
		DisplayMode.dmDisplayFrequency = Display->RefreshRate;
		DisplayMode.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;
			
		// Change the display mode
		if(ChangeDisplaySettingsEx(DisplayDevice.DeviceName,&DisplayMode,NULL,CDS_FULLSCREEN,0) != DISP_CHANGE_SUCCESSFUL)
		{
			return deFALSE;
		}

		// Change the window's style, size and position.
		SetWindowLong(m_CurWindowHandle,GWL_STYLE,WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
		SetWindowPos(m_CurWindowHandle,HWND_TOP,0,0,Display->Width,Display->Height,SWP_FRAMECHANGED | SWP_SHOWWINDOW);		
	}

	// Get a Device Context for our window.
	DC = GetDC(m_CurWindowHandle);
	if(DC == NULL)						
	{
		// Error
		LogInfo("Error getting DC for window.\n");
		return deFALSE;
	}	

	// Create a Pixel Format Descriptor where we can tell windows what we want
	static PIXELFORMATDESCRIPTOR pfd=
	{
		sizeof(PIXELFORMATDESCRIPTOR),// Size Of This Pixel Format Descriptor
		1, // Version Number
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, 
		PFD_TYPE_RGBA, 
		GetBPP(Display->BackBpp), 
		0,0,0,0,0,0, 
		0,0,0,0,0,0, 
		16,	// 16 bit Alpha
		16, // 16 bit Z-Buffer
		1, // 16 bit Stencil
		0, // No Auxiliary Buffer
		0,
		0,0,0,0
	};

	// Get the pixel format we want.
	GLuint PixelFormat = ChoosePixelFormat(DC,&pfd);
	if(PixelFormat == NULL)	
	{
		LogInfo("Error selecting Pixel Format\n");
		return deFALSE;
	}

	// Set the Pixel Format to the one we want.
	if(!SetPixelFormat(DC,PixelFormat,&pfd))		
	{
		LogInfo("Error setting Pixel Format\n");
		return deFALSE;	
	}

	// Create our OpenGL rendering context.
	RC = wglCreateContext(DC);
	if(RC == NULL)	
	{
		LogInfo("Error creating OpenGL rendering context\n");
		return deFALSE;	
	}

	// Activate our Rendering Context.
	if(!wglMakeCurrent(DC,RC))
	{
		LogInfo("Error activating OpenGL rendering context\n");
		return deFALSE;
	}

	// Set our default render states
	SetDefaultRenderstates();

	// Turn on vertex arrays
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	// Get our extensions
	const char * Extensions = (const char*)glGetString(GL_EXTENSIONS);
	
	if(glh_init_extension("GL_NV_vertex_program"))
	{ 
		NVVertexProgramExt = deTRUE;
	} 

	if(glh_init_extension("GL_NV_register_combiners"))
	{ 

	} 
	
	if(strstr(Extensions,"GL_EXT_draw_range_elements"))
	{
		glDrawRangeElementsEXT = (PFNGLDRAWRANGEELEMENTSPROC)wglGetProcAddress("glDrawRangeElementsEXT");
	}

	if(!glDrawRangeElementsEXT)
	{
		LogInfo("The extension:GL_EXT_draw_range_elements was not found. Please update your video card drivers.\n");
	}

	if(strstr(Extensions,"GL_ARB_multitexture"))
	{
		glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
	}

	if((!glClientActiveTextureARB) || (!strstr(Extensions,"GL_ARB_texture_env_combine")))
	{
		MessageBox(m_CurWindowHandle,"One or more OpenGL extensions could not be found. Please update your video card's drivers.","Error",0);
		return deFALSE;
	}
	
	// See if we can do mirrored texture wrapping
	if(strstr(Extensions,"GL_ARB_texture_mirrored_repeat"))
	{
		MirroredTextureWrapFlag = 0x8370;
		LogInfo("The extension:GL_ARB_texture_mirrored_repeat found.\n");
	}
	else
	{ 
		LogInfo("The extension: GL_ARB_texture_mirrored_repeat was not found.\n");
	}

	// Try to disable V-Sync
	wglSwapIntervalEXT = (PFWGLSWAPINTERVALEXT)wglGetProcAddress("wglSwapIntervalEXT");
	if(wglSwapIntervalEXT)
	{
		wglSwapIntervalEXT(0);
		LogInfo("VSync Disabled.\n");
	}
	else
	{
		LogInfo("VSync could not be disabled.\n");
	}

	// Update the window's rect
	UpdateWindowRect();
	return deTRUE;
}

deBoolean deOpenGL::Reset(deDisplay *Display)
{
	Display;
	return deFALSE;
}

void deOpenGL::Shutdown()
{
	// Get back to the desktop
	if(!m_CurrentDisplay.Windowed)
	{
		ChangeDisplaySettings(NULL,0);				
		ShowCursor(TRUE);
	}
	
	// Destroy our device and Rendering Contexts.
	if(RC)
	{
		wglMakeCurrent(NULL,NULL);
		wglDeleteContext(RC);
		
		if(DC)
		{
			ReleaseDC(m_CurWindowHandle,DC);
		}
	}

}

deBoolean deOpenGL::EnumerateDisplayModes()
{
	int EnumAdapterCount = 0; // For enumerating the display devices

	m_AdapterCount = 0;
	DISPLAY_DEVICE DisplayDevice;
	DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
	while(EnumDisplayDevices(NULL,EnumAdapterCount,&DisplayDevice,0))
	{
		if(!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
		{
			++m_AdapterCount;
		}
		
		++EnumAdapterCount;
	}	
		
	//our list of what adapters can do windowed mode
	m_WindowPossible = new deBoolean[m_AdapterCount];
	if(!m_WindowPossible)
		return deFALSE;
	memset(m_WindowPossible, 0, sizeof(deBoolean) * m_AdapterCount);

	//our list of number of modes for the adapters
	m_ModeCount = new int[m_AdapterCount];
	if(!m_ModeCount)
		return deFALSE;
	memset(m_ModeCount,0,sizeof(int) * m_AdapterCount);

	// Our List of Modes for the Adapters
	m_Modes = new deDisplay*[m_AdapterCount];
	if(!m_Modes)
		return deFALSE;
	memset(m_Modes,0,sizeof(deDisplay*) * m_AdapterCount);

	m_Caps = new deDriverCaps[m_AdapterCount];
	if(!m_Caps)
		return deFALSE;
	memset(m_Caps,0,sizeof(deDriverCaps) * m_AdapterCount);


	EnumAdapterCount = 0;
	int i = 0;
	DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
	while(EnumDisplayDevices(NULL,EnumAdapterCount,&DisplayDevice,0))
	{
		if(!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
		{
			strcpy(m_Caps[i].AdapterDescription,DisplayDevice.DeviceString);
			_strupr(m_Caps[i].AdapterDescription);
			i++;

		}
		
		++EnumAdapterCount;
	}

	for(i = 0;i < m_AdapterCount;++i)
	{
		// Get the number of modes the adapter has
		DEVMODE mode;
		mode.dmSize = sizeof(DEVMODE);
		while(EnumDisplaySettings(NULL,m_ModeCount[i],&mode))
		{
			m_ModeCount[i]++;
		}
		
		// Store the specs of the adapter's modes
		m_Modes[i] = new deDisplay[m_ModeCount[i]];
		for(int j = 0;j < m_ModeCount[i];j++)
		{
			if(!EnumDisplaySettings(NULL,j,&mode))
			{
				break;
			}

			POINT point;
			point.x = mode.dmPosition.x;
			point.y = mode.dmPosition.y;
			m_Modes[i][j].Adapter = i;
			m_Modes[i][j].Width = mode.dmPelsWidth;
			m_Modes[i][j].Height = mode.dmPelsHeight;
			m_Modes[i][j].MonitorHandle = MonitorFromPoint(point,MONITOR_DEFAULTTOPRIMARY);
			m_Modes[i][j].BackBpp = GetDestinyBPP(mode.dmBitsPerPel);	
			m_Modes[i][j].FrontBpp = GetDestinyBPP(mode.dmBitsPerPel);	
			m_Modes[i][j].Zpp = ZPP_D24S8; // Can we get this sort of info from somwhere?
			m_Modes[i][j].ZDepth = 24; // This good?
			m_Modes[i][j].StencilDepth = 8;
			m_Modes[i][j].RefreshRate = mode.dmDisplayFrequency; // Probably should check if this is 0 or 1
			m_Modes[i][j].FSAASamples = 0;;
			m_Modes[i][j].TripleBuffer = deFALSE;
			m_Modes[i][j].Windowed = deFALSE;
			m_Modes[i][j].IgnoreVSync = deFALSE;

		}

#pragma message("Figure out which adapters can do windowed mode and which ones can't.")
		m_WindowPossible[i] = deTRUE;

		/*
		char		AdapterDescription[255];	///< name of the video card, as it identifies itself
		deBoolean	HWTnL;				///< does the card support hardware transform and lighting?
		deBoolean	Dot3Support;		///< does the card support dot-product3 for textures?
		long		MaxTextureBitDepth;	///< highest texture bitdepth (16 for old cards, 32 or higher for new ones)
		long		SimulTextures;		///< number of textures that can be used at once to render
		long		TexWidth;			///< maximum texture width
		long		TexHeight;			///< maximum texture height
		long		NumStreams;			///< number of vertex streams supported
		long		MaxVert;			///< highest index value for a vertex
		long		MaxPrim;			///< maximum number of primitives renderable in a call
		long		MaxLights;			///< limit of lights in hardware fixed-function pipeline
		long		VertCacheSize;		///< number of vertices stored in post-transform cache (if known)
		deFloat		VShader;			///< supported version number of vertex shader/program
		deFloat		PShader;			///< supported version number of pixel/fragment shader/program
		*/
		// Write out how many modes for the current adapter
		if(!AdapterInfoLogged)
			LogInfo("Adapter %d:\n\tMode Count: %d\n\n", i, m_ModeCount[i]);

	}

	AdapterInfoLogged = deTRUE;
	return deTRUE;
}

deBoolean deOpenGL::GetCurrentDisplay(long Adapter, deDisplay * Display) const
{
	// Get the name of the adapter we want
	DISPLAY_DEVICE DisplayDevice;
	DisplayDevice.cb = sizeof(DisplayDevice);
	if(!EnumDisplayDevices(NULL,Adapter,&DisplayDevice,0))
	{
		return deFALSE;
	}

	DEVMODE mode;
	mode.dmSize = sizeof(DEVMODE);
	mode.dmDriverExtra = 0;
	EnumDisplaySettings(DisplayDevice.DeviceName,ENUM_CURRENT_SETTINGS,&mode);

	POINT point;
	point.x = mode.dmPosition.x;
	point.y = mode.dmPosition.y;

	Display->MonitorHandle = MonitorFromPoint(point,MONITOR_DEFAULTTOPRIMARY);
	Display->Adapter = Adapter;
	Display->Width = mode.dmPelsWidth;
	Display->Height = mode.dmPelsHeight;
	Display->BackBpp = GetDestinyBPP(mode.dmBitsPerPel);	
	Display->FrontBpp = GetDestinyBPP(mode.dmBitsPerPel);	
	Display->Zpp = ZPP_D24S8; // Can we get this sort of info from somwhere?
	Display->ZDepth = 24; // This good?
	Display->StencilDepth = 8;
	Display->RefreshRate = mode.dmDisplayFrequency; // Probably should check if this is 0 or 1
	Display->FSAASamples = 0;
	Display->TripleBuffer = deFALSE;
	Display->Windowed = deFALSE;
	Display->IgnoreVSync = deFALSE;

	return deFALSE;
}

deBoolean deOpenGL::GetDisplayMode(long Adapter, long Mode, IdeDriver::deDisplay *Display) const
{
	*Display = m_Modes[Adapter][Mode];
	return deTRUE;
}
