// Author : Chris Tolley
// Date   : 8/26/2004
// main.cpp contains the main entry point for the game Survival.

#pragma warning ( disable : 4305 )
#pragma warning ( disable : 4786 )

// Trim the fat from a fat OS.
#define WIN32_LEAN_AND_MEAN


#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include <string>

#include "InputSystem.h"
#include "SurvivalEngine.h"
#include "ScreenWriter.h"
#include "logger.h"
#include "Menu.h"
#include "HiResTimer.h"

////// Global Variables
// The global device context
HDC g_HDC;
// A full screen flag.
bool fullScreen = false;
// The menu for this game
Menu g_Menu;
// The input system for this application
CInputSystem g_Input;
// The current state of the game
MenuChoice g_GameState;
// The high resolution timer that paces this game
HiResTimer g_Timer;

// The game engine
SurvivalEngine g_Engine;

// Sets up the pixel format for the device context
void SetupPixelFormat(HDC hDC)
{
	// The pixel format
	int nPixelFormat;

	static PIXELFORMATDESCRIPTOR pfd = {
		sizeof( PIXELFORMATDESCRIPTOR ),// size of structure
		1,								// default version
		PFD_DRAW_TO_WINDOW |			// window drawing support
		PFD_SUPPORT_OPENGL |			// OpenGL support
		PFD_DOUBLEBUFFER,				// double buffering support
		PFD_TYPE_RGBA,					// RGBA color mode
		32,								// 32 bit color mode
		0, 0, 0, 0, 0, 0,				// ignore color bits, non-palettized mode
		0,								// no alpha buffer
		0,								// ignore shift bit
		0,								// no accumulation buffer
		0, 0, 0, 0,						// ignore accumulation bits
		16,								// 16 bit z-buffer size
		0,								// no stencil buffer
		0,								// no auxiliary buffer
		PFD_MAIN_PLANE,					// main drawing plane
		0,								// reserved
		0, 0, 0 };						// layer masks ignored

	// Choose the best matching pixel format
	nPixelFormat = ChoosePixelFormat( hDC, &pfd );

	// Set the pixel format to the device context
	SetPixelFormat( hDC, nPixelFormat, &pfd );
}// End SetupPixelFormat

// the Windows Procedure event handler
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// The rendering context
	static HGLRC hRC;
	// The device context
	static HDC hDC;
	// The window width and height
	int width, height;

	switch( message )
	{
		// The window is being created
		case WM_CREATE:
			// Get the current window's device context
			hDC = GetDC( hwnd );
			g_HDC = hDC;

			// Call the pixel format setup function
			SetupPixelFormat( hDC );

			// create rendering context and make it current
			hRC = wglCreateContext( hDC );
			wglMakeCurrent( hDC, hRC );

			return 0;
			break;

		// Windows is closing
		case WM_CLOSE:

			// Deselect the rendering context and delete it
			wglMakeCurrent( hDC, NULL );
			wglDeleteContext( hRC );

			// Post a quit message to the message queue
			PostQuitMessage( 0 );

			// Return 0 to let windows know that everything is ok
			return 0;
			break;

		// The window is being resized
		case WM_SIZE:
			// Get the window's width and height
			height = HIWORD( lParam );
			width = LOWORD( lParam );

			// If height is zero
			if( height == 0 )
			{
				// Set height to 1 so we don't divide by zero
				height=1;					
			}

			// Reset the viewport to the new dimensions
			glViewport( 0, 0, width, height );
			// Select the projection matrix stack and reset it
			glMatrixMode( GL_PROJECTION );
			glLoadIdentity();

			// Calculate the aspect ratio of window
			gluPerspective( 45, (GLfloat)width / (GLfloat)height, 1, 1000 );

			// Select the modelview matrix stack and reset it
			glMatrixMode( GL_MODELVIEW );
			glLoadIdentity();

			return 0;
			break;

		// The default case, handles any messages we receive that 
		// we didn't handle above
		default:
			break;
	}// End switch( message )

	// Return the LRESULT
	return ( DefWindowProc( hwnd, message, wParam, lParam ) );
}// End WndProc


// Renders the splash screen to the screen
void RenderSplash()
{
	// Initialize the screen writer
	ScreenWriter::Initialize( 60, "Footlight MT Light" );

	// Clear the buffers
	glClearColor( 0, 0, 0, 0 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	// Draw a splash screen
	int args = 0;
	float red[3] = { 1, 0, 0 };
	ScreenWriter::Render( 70, 130, red, "Survival%n", &args );

	ScreenWriter::Initialize( 10, "Courier" );
	float white[3] = { 1, 1, 1 };
	ScreenWriter::Render( 75, 120, white, "By Chris Tolley%n", &args );
	ScreenWriter::Render( 72, 110, white, "neotolley@hotmail.com%n", &args );

	// Swap buffers so we can see the splash screen
	SwapBuffers( g_HDC );
}// End RenderSplash


// Sets up all the game related stuff
bool Setup( HWND hwnd, HINSTANCE hInstance )
{
	// Open the logger and log success
	logger::open( "SurvivalLog.xml" );
	logger::event( "Setup", "Logger opened successfully", 0 );

	// Initialize the DirectX stuff
	CoInitialize( NULL );

	// Initialize the input system and acquire the devices.  Return
	// false if there was a problem with this.
	if( ! g_Input.Initialize( hwnd, hInstance, false, IS_USEMOUSE |
													  IS_USEKEYBOARD) )
	{
		// Log the input failure
		logger::event( "Setup",
					   "Could not Initialize the input.", 0 );
		return true;
	}// End if
	else
	{
		g_Input.AcquireAll();

		// Log the success of the initialization of the input system
		logger::event( "Setup",
					   "Initialization of input successful.", 0 );
	}// End else

	// Initialize the menu
	g_Menu.SetInput( &g_Input );
	g_Menu.SetTimer( &g_Timer );

	// Set up all the menu options
	g_Menu.AddOption( "images/Play.bmp", GAME );
	g_Menu.AddOption( "images/Observe.bmp", SPECTATE );
	g_Menu.AddOption( "images/exit.bmp", EXIT );

	// Test for high resolution timer support and if it is supported
	// initialize the timer, else return false
	if( ! g_Timer.isSupported() )
	{
		// Log the error and return false
		logger::event( "Setup", 
					   "HiRes Timer not supported",  0 );
		return true;
	}// End if
	else
	{
		logger::event( "Setup",
					   "HiRes Timer Initialized.", 0 );
		g_Timer.Initialize();
	}// End else

	// Create the engine
	g_Engine.Initialize( hwnd, hInstance, &g_Input, &g_Timer );

	// Set the current state of this game
	g_GameState = MENU;

	// Set the screen writer for use in the game
	ScreenWriter::Initialize( 14, "Courier" );

	// Reset the timer
	g_Timer.getElapsedSeconds();

	// Return false to show that we are not done
	return false;
}// End Setup


// The main entry point for this application
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, 
				    LPSTR lpCmdLine, int nShowCmd )
{
	// The window class structure
	WNDCLASSEX windowClass;
	// The handle to the window
	HWND	   hwnd;
	// A message
	MSG		   msg;
	// True if this application is finished and ready to exit
	bool	   done;
	// The window's extended style data
	DWORD	   dwExStyle;
	// The windows style
	DWORD	   dwStyle;
	// The rectangle that this window is measured by
	RECT	   windowRect;

	// Declare some temp variables
	// The width of this window
	int width = 800;
	// The height of this window
	int height = 600;
	// The number of bits
	int bits = 32;

	// Set to TRUE to make the window go full screen
	fullScreen = FALSE;

	// Set up this window's rectangle properties
	windowRect.left   = ( long )0;
	windowRect.right  = ( long )width;
	windowRect.top    = ( long )0;
	windowRect.bottom = ( long )height;

	// Fill out this window's class structure
	windowClass.cbSize			= sizeof( WNDCLASSEX );
	windowClass.style			= CS_HREDRAW | CS_VREDRAW;
	windowClass.lpfnWndProc		= WndProc;
	windowClass.cbClsExtra		= 0;
	windowClass.cbWndExtra		= 0;
	windowClass.hInstance		= hInstance;
	// The application icon
	windowClass.hIcon			= LoadIcon( NULL, IDI_APPLICATION );
	// Basic arrow mouse icon
	windowClass.hCursor			= LoadCursor( NULL, IDC_ARROW );
	// No background
	windowClass.hbrBackground	= NULL;
	// No menu
	windowClass.lpszMenuName	= NULL;
	// The name of the window
	windowClass.lpszClassName	= "Survival";
	// The window logo icon
	windowClass.hIconSm			= LoadIcon( NULL, IDI_WINLOGO );

	// Register this window with windows
	if( ! RegisterClassEx( &windowClass ) )
	{
		return 0;
	}// End if

	// Query the user to see if they want to go full screen or not
	int BoxResult = MessageBox( NULL, "Do you want to run in full screen mode?",
								"Full Screen Mode?", MB_YESNO | MB_ICONQUESTION );

	// If the user selected yes
	if( BoxResult == IDYES )
	{
		// Set to full screen mode
		fullScreen = true;
	}// End if
							 

	// If fullScreen
	if( fullScreen )
	{
		// Create a device mode
		DEVMODE dmScreenSettings;
		memset( &dmScreenSettings,0,sizeof( dmScreenSettings ) );
		dmScreenSettings.dmSize = sizeof( dmScreenSettings );

		// Set the screen width and height
		dmScreenSettings.dmPelsWidth = width;
		dmScreenSettings.dmPelsHeight = height;

		// Set the number of bits per pixel
		dmScreenSettings.dmBitsPerPel = bits;
		dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

		// If the change in display setting is not successful
		if( ChangeDisplaySettings( &dmScreenSettings, CDS_FULLSCREEN ) != 
													DISP_CHANGE_SUCCESSFUL )
		{
			// Let the user know and set the display settings to windowed mode
			MessageBox( NULL, "Display mode failed", NULL, MB_OK );
			fullScreen = FALSE;	
		}// End if
	}// End if

	// If we are still in full screen mode
	if( fullScreen )
	{
		// Set up the extended style, the windows style and hide the mouse
		dwExStyle = WS_EX_APPWINDOW;
		dwStyle = WS_POPUP;
		ShowCursor( TRUE );
	}// End if
	else
	{
		// Otherwise, just set up the styles
		dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
		dwStyle = WS_OVERLAPPEDWINDOW;
	}// End else

	// Adjust the window to the requested size
	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);

	// Create the actual window now that it is registered
	hwnd = CreateWindowEx(
						  // The extended style
						  NULL,
						  // The name of this app
						  "Survival",
						  // The name of this class
						  "Survival",
						  dwStyle | WS_CLIPCHILDREN |
						  WS_CLIPSIBLINGS,
						  // The windows x,y starting coordintate
						  0, 0,
						  // The width and height of the window
						  windowRect.right - windowRect.left,
						  windowRect.bottom - windowRect.top,
						  NULL,
						  NULL,
						  hInstance,
						  NULL );

	// If hwnd == NULL
	if ( ! hwnd )
	{
		// Something went wrong and we need to bail out
		return 0;
	}// End if

	// Display the window and update it
	ShowWindow( hwnd, SW_SHOW );
	UpdateWindow( hwnd );

	// Set done to false
	done = false;

	// Set the device context in the screen writer
	ScreenWriter::SetHandle( g_HDC );

	// Render the splash screen
	RenderSplash();

	// Setup all the game related stuff
	done = Setup( hwnd, hInstance );

	// While we are not done
	while ( ! done )
	{
		// Get the next message out of the queue
		PeekMessage( &msg, hwnd, NULL, NULL, PM_REMOVE );

		// If it is a quit message
		if( msg.message == WM_QUIT )
		{
			// Set done to true to quit this application
			done = true;
		}// End if
		else
		{
			// Otherwise, do something depending on the state of the game
			switch( g_GameState )
			{
			// The user is looking at the menu
			case MENU:
				// Update the menu and the game state based on the menu update
				g_GameState = g_Menu.Update();

				// Render the menu
				g_Menu.Render();

				// A switch to handle a GameState change
				switch( g_GameState )
				{
				case GAME:
					g_Engine.SetupGame( PLAY );
					break;
				case SPECTATE:
					g_Engine.SetupGame( OBSERVE );
					break;
				default:
					break;
				};// End switch( g_GameState
				break;
			// The user if playing the game
			case GAME:
				// Update the engine
				g_GameState = g_Engine.Update();

				// Render the engine if we are still in Game mode
				if( g_GameState == GAME || g_GameState == SPECTATE )
				{
					g_Engine.Render();
				}// End if
				break;
			// The user wants to spectate only
			case SPECTATE:
				// Update the engine
				g_GameState = g_Engine.Update();

				// Render the engine if we are still in Game mode
				if( g_GameState == GAME || g_GameState == SPECTATE )
				{
					g_Engine.Render();
				}// End if
				break;
			// The user wants to exit the game
			case EXIT:
				// Set done to true
				done = true;
				break;
			// The default case
			default:
				break;
			};// End switch( g_GameState )

			// Update the engine
//			done = g_Engine.Update();

			// Render everything
//			g_Engine.Render();

			// Swap buffers so the back is the front and the front is the back
			SwapBuffers( g_HDC );

			// Translate and dispatch to the event queue
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}// End else
	}// End while

	// If we are in full screen mode
	if( fullScreen )
	{
		// Switch back to the desktop and show the mouse pointer
		ChangeDisplaySettings( NULL, 0 );
		ShowCursor( TRUE );
	}// End if

	// Release the input system
	g_Input.UnacquireAll();
	logger::event( "WinMain",
				   "Unacquired the input system successfully.", 0 );

	// Close the logger
	logger::event( "WinMain", "Closing the logger", 0 );
	logger::close();

	// Return the message
	return msg.wParam;
}// End WinMain
