/*
	Trampoline Sim.
	Copyright (c) 2009, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//=====================================================================================================================
/// @brief		This is the core game class. Handles the high level game logic.
//=====================================================================================================================

#include "Precompiled.h"
#include "Game\GameCore.h"
#include "Game\GameObjectManager.h"
#include "Windows\Window.h"
#include "Graphics\Display.h"
#include "Graphics\Camera3D.h"
#include "Graphics\BufferedLineRenderer.h"
#include "Resources\TextureManager.h"
#include "Resources\PixelShaderManager.h"
#include "Resources\VertexShaderManager.h"
#include "GameObjects\GamePlane.h"
#include "GameObjects\GameBox.h"
#include "GameObjects\GameTrampoline.h"
#include "Physics\SimulationWorld.h"
#include "Physics\RaycastResult.h"
#include "Physics\Ray.h"
#include "Physics\RigidBody.h"

using namespace Game;

//=====================================================================================================================
// Constants
//=====================================================================================================================

/// Title for the game

const char * GameCore::GAME_TITLE = "Trampoline Sim V 1.01 By Darragh Coy";

/// Near plane value for the camera

const float GameCore::CAM_Z_NEAR = 1.0f;

/// Far plane value for the camera

const float GameCore::CAM_Z_FAR = 10000.0f;
				
/// Target refresh interval in between game ticks - in seconds.

const float GameCore::FRAME_INTERVAL = 0.01666666666f;

/// Maximum number of frames that we can try to make up from frame skipping if we are behind real time

const unsigned GameCore::MAX_CATCHUP_FRAMES = 4;

//=====================================================================================================================
// Statics
//=====================================================================================================================

/// Pointer to an instance of the game

GameCore * GameCore::s_this = 0;

//=====================================================================================================================
/// @brief		Constructor. Creates the game.
/// @param		window - window used by the game
/// @pre		There must not be another game in existance
/// @pre		Given window must not be null 
/// @pre		Window display must be valid
/// @remarks	The game does NOT assume ownership of this window. This is to be managed by the app.
//=====================================================================================================================

GameCore::GameCore( Window * window ) : m_stack(0x100000) , m_secondary_stack(0x100000)
{
	// Make sure ok:
	
	ENSURE( s_this == 0 , "There must not be another game in existance" );

	// Make sure ok:
	
	ENSURE( window , "Given window must not be null" );
	
	// Make sure the display is valid:
	
	ENSURE( window->GetDisplay() && window->GetDisplay()->IsValid() , "Window display must be valid" )

	// Save a static ref to us:

	s_this = this;

	// Not pressed yet

	m_tab_pressed = false;
	
	// Save the window:
	
	m_window = window;
	
	// Make this the current window listener
	
	m_window->SetEventListener(this);
	
	// Do an immediate update
	
	m_time_since_update = FRAME_INTERVAL;
	
	// Didn't render a frame yet
	
	m_rendered_last_frame = false;
	
	// Create all the managers:
		
	m_texture_manager		= new TextureManager		( m_window->GetDisplay() );
	m_pixel_shader_manager	= new PixelShaderManager	( m_window->GetDisplay() );
	m_vertex_shader_manager = new VertexShaderManager	( m_window->GetDisplay() );
	
	// Make the simulation world:
	
	m_sim_world = new SimulationWorld();
	
	// Game object manager:
	
	m_game_object_manager = new GameObjectManager();

	// Make the buffered line renderer
	
	m_buffered_line_renderer = new BufferedLineRenderer
	(
		m_window->GetDisplay()	, 
		m_vertex_shader_manager , 
		m_pixel_shader_manager		
	);

	// See if left mouse is clicked

	m_left_mouse_clicked = Mouse::IsButtonPressed(Mouse::BTN_LEFT);

	// Make the camera

	m_camera = new Camera3D( m_window, Vec3f(0,0,500), CAM_Z_NEAR, CAM_Z_FAR );
	
	// Populate the scene
	
	PopulateScene();
	
	// Center the mouse
	
	Mouse::CenterInWindow(m_window);
}

//=====================================================================================================================
/// @brief		Destructor. Destroys the game.
//=====================================================================================================================

GameCore::~GameCore()
{
	// If we are the current event listener then remove us:
	
	if ( m_window->GetEventListener() == this ) m_window->SetEventListener(0);
	
	// Get rid of the camera

	m_camera->RemoveReference();

	// Be sure to clear all the game objects:
	
	m_game_object_manager->Clear();

	// Done with these buffered renderers

	m_buffered_line_renderer->RemoveReference();
	
	// Done with all our managers:
	
	m_game_object_manager->RemoveReference();
	m_texture_manager->RemoveReference();
	m_pixel_shader_manager->RemoveReference();
	m_vertex_shader_manager->RemoveReference();
	
	// Get rid of simulation world:
	
	delete m_sim_world;

	// Remove static ref to us:

	s_this = 0;
	
	// Unrestrict the mouse if restricted
	
	Mouse::Unrestrict();
}

//=====================================================================================================================
/// @brief		Annouces the type name for this object.
/// @returns	Type name of this object
//=====================================================================================================================

const char * GameCore::GetTypeName() const { return "GameCore"; }

//=====================================================================================================================
/// @brief		Window redraw event - does the game loop. 
//=====================================================================================================================

void GameCore::OnWindowRedrawing()
{
	// If we are out of focus then keep reseting the timing values in the timer and pause the game:
	
	if ( m_window->HasFocus() == false )
	{
		// Reset timing deltas:
		
		HighResolutionTimer::Reset();
		
		// If we are not mimized then refresh the display:
		
		if ( m_window->IsMinimized() == false ) OnRender();
		
		// Give up a bit of processor time
		
		Sleep(10);
	}
	else
	{
		// Update the timing values in the timer:
		
		HighResolutionTimer::Update();
		
		// Add the time delta to time elapsed:
		
		m_time_since_update += HighResolutionTimer::GetTimeDelta();
		
		// See if time for another frame:
		
		if ( m_time_since_update >= FRAME_INTERVAL )
		{
			// See if we are more than one frame behind:
			
			if ( m_time_since_update >= FRAME_INTERVAL * 2.0f )
			{
				// Two or more frames behind: if we didn't skip the last frame skip this one
				
				if ( m_rendered_last_frame == true )
				{
					// Only do an update:
					
					OnUpdate();
					
					// Didn't render this frame:
					
					m_rendered_last_frame = false;
					
					// Simulated this much time:
					
					m_time_since_update -= FRAME_INTERVAL;					
				}
				else
				{
					// Do an update and render:
					
					OnUpdate();
					OnRender();
					
					// Rendered this frame:
					
					m_rendered_last_frame = true;
					
					// Simulated this much time:
					
					m_time_since_update -= FRAME_INTERVAL;					
				}
				
				// If we are falling way behind real time then limit the number of frames we try to catch up with:
				
				if ( m_time_since_update >= FRAME_INTERVAL * float(MAX_CATCHUP_FRAMES) )
				{
					// Figure out how much time we are going to discount:
					
					float discount_time = m_time_since_update - ( FRAME_INTERVAL * float(MAX_CATCHUP_FRAMES) );
					
					// Discount the time: we can't keep trying to catch up in an impossible situation
					
					m_time_since_update -= discount_time;
				}
			}
			else
			{
				// Not behind: draw and render as usual
				
				OnRender();
				OnUpdate();
				
				// Rendered this frame:
				
				m_rendered_last_frame = true;
				
				// Simulated this much time:
				
				m_time_since_update -= FRAME_INTERVAL;
			}
		}
		else
		{
			// Not time for an update: give up this processor time slice
			
			Sleep(0);
		}
	}
}

//=====================================================================================================================
/// @brief		Called when the game is to do an update
//=====================================================================================================================

void GameCore::OnUpdate()
{
	// Do camera update
	
	DoCameraMovement();

	// Switch wireframe:

	if ( Keyboard::IsKeyPressed(Keyboard::KB_TAB) )
	{
		static bool wireframe = false;

		if ( m_tab_pressed == false )
		{
			if ( wireframe )
			{
				m_window->GetDisplay()->GetDevice()->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
			}
			else
			{
				m_window->GetDisplay()->GetDevice()->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
			}

			wireframe = ! wireframe;
		}
		
		m_tab_pressed = true;
	}
	else
	{
		m_tab_pressed = false;
	}

	// Do mouse input

	DoMouseInput();
	
	// Update simulation:
	
	m_sim_world->StepSimulation( FRAME_INTERVAL , 2 );

	// Update all objects:
		
	m_game_object_manager->Update(FRAME_INTERVAL);	
}

//=====================================================================================================================
// @brief		Do mouse input
//=====================================================================================================================

void GameCore::DoMouseInput()
{
	// Do not show the mouse:

	m_window->GetDisplay()->GetDevice()->ShowCursor(false); Mouse::SetMouseCursor(m_window,Mouse::CURSOR_NONE);

	// See if mouse is currently pressed:

	if ( Mouse::IsButtonPressed(Mouse::BTN_LEFT) )
	{
		// See if not clicked before

		if ( m_left_mouse_clicked == false )
		{
			// Ok: get all of the boxes in the scene

			std::vector<GameObjectBase*> boxes; m_game_object_manager->FindByType<GameBox>("GameBox",boxes);

			// Store the closest box hit by the raycast here

			RaycastResult closest_hit; GameBox * closest_box = 0;

			// Just clicked: apply an impulse to the box: apply it in the direction of the camera's forward vector

			Vec3f camera_forward = -m_camera->GetOrientation().GetCol(2).ToVec3();

			// We need to do a raycast against the box to see if we hit it: otherwise we won't do anything

			Ray ray(m_camera->GetPosition(),camera_forward);

			// Raycast against all the boxes:

			for ( unsigned i = 0 ; i < boxes.size() ; ++i )
			{
				// Get this box

				GameBox & box = * ((GameBox*)(boxes[i]));

				// Do the raycast against the box:

				RaycastResult raycast_result = box.GetBody().GetShape().Raycast(ray);

				// See if there was a hit:

				if ( raycast_result.m_hit )
				{
					// See if no current hit or if closer:

					if ( closest_box == 0 )
					{
						// Save:

						closest_hit = raycast_result; closest_box = & box;
					}
					else if ( raycast_result.m_hit_distance < closest_hit.m_hit_distance )
					{
						// Save:

						closest_hit = raycast_result; closest_box = & box;
					}
				}
			}

			// See if we got a hit: if so then apply an impulse to the box

			if ( closest_hit.m_hit && closest_box )
			{
				closest_box->GetBody().ApplyWorldImpulse( camera_forward * 2500.0f , closest_hit.m_hit_point );
			}
		}

		// Now clicked

		m_left_mouse_clicked = true;
	}
	else
	{
		// Not pressed

		m_left_mouse_clicked = false;
	}
}

//=====================================================================================================================
/// @brief		Called when the game is to do a render
//=====================================================================================================================

void GameCore::OnRender()
{
	// Clear the display:

	m_window->GetDisplay()->Clear( Color::Black() , 0 , 1 );
	
	// Begin rendering:

	m_window->GetDisplay()->BeginDraw();	

	// Draw all game objects:

	m_game_object_manager->Draw( m_camera );

	// End rendering:

	m_window->GetDisplay()->EndDraw();

	// Present the rendered frame:
	
	m_window->GetDisplay()->Present();
}

//=====================================================================================================================
///	@brief		Called when the keyboard is pressed
/// @param		key_code - Virtual key code of the key pressed
//=====================================================================================================================

void GameCore::OnKeyboardReleased( Keyboard::Key key_code )
{
	// Quit if escape is pressed:
	
	if ( key_code == Keyboard::KB_ESCAPE ) m_window->Close();
}

//=====================================================================================================================
/// @brief		Does camera movement
//=====================================================================================================================

void GameCore::DoCameraMovement()
{
	// Only if the window is in focus:
	
	if ( m_window->HasFocus() && Mouse::InClientArea(m_window) )
	{
		// Speed of movement:
		
		const float MOVE_SPEED = 0.5f;
		const float TURN_SPEED = 0.005f;

		// See what keys are pressed:
		
		if ( Keyboard::IsKeyPressed(Keyboard::KB_Q) ) m_camera->MoveLocal( + Vec3f::UnitY() * MOVE_SPEED );
		if ( Keyboard::IsKeyPressed(Keyboard::KB_E) ) m_camera->MoveLocal( - Vec3f::UnitY() * MOVE_SPEED );
		if ( Keyboard::IsKeyPressed(Keyboard::KB_A) ) m_camera->MoveLocal( - Vec3f::UnitX() * MOVE_SPEED );
		if ( Keyboard::IsKeyPressed(Keyboard::KB_D) ) m_camera->MoveLocal( + Vec3f::UnitX() * MOVE_SPEED );	
		if ( Keyboard::IsKeyPressed(Keyboard::KB_W) ) m_camera->MoveLocal( - Vec3f::UnitZ() * MOVE_SPEED );
		if ( Keyboard::IsKeyPressed(Keyboard::KB_S) ) m_camera->MoveLocal( + Vec3f::UnitZ() * MOVE_SPEED );
		
		// Get the current position the mouse on the screen:
		
		Vec2i mouse_pos; Mouse::GetClientPosition( m_window , mouse_pos.x, mouse_pos.y );
		
		// Get the center of the window on the screen
		
		Vec2i win_center = m_window->GetClientSize() / 2;
		
		// Get the amount of movement:
		
		Vec2f move_amount = mouse_pos - win_center; move_amount *= TURN_SPEED;
		
		// Amount of rotation:
		
		static float x_rot = 0; x_rot += move_amount.y;
		static float y_rot = 0; y_rot += move_amount.x;
		
		// Clamp:
		
		x_rot = Float::Clamp( x_rot , - Float::PI() * 0.5f , + Float::PI() * 0.5f );
		
		// Set camera orientation:
		
		m_camera->SetOrientation( Matrix4f::RotationY(y_rot) * Matrix4f::RotationX(x_rot) );
		
		// Center the mouse again
		
		Mouse::CenterInWindow( GameCore::GetSingleton()->GetWindow() );
	}
}

//=====================================================================================================================
/// @brief		Populates the scene with the plane and the box
//=====================================================================================================================

void GameCore::PopulateScene()
{
	// Size of the trampoline
	
	const float TS = 64;

	// Set the initial camera position
	
	m_camera->SetPosition( Vec3f(0,64,48) );
	
	{
		// Make the planes
		
		GamePlane * plane1 = new GamePlane(Vec3f(0  ,-64 ,0  ),Vec3f(+0,+1,+0),"Textures\\Floor.dds");
		GamePlane * plane2 = new GamePlane(Vec3f(0  ,2048 ,0 ),Vec3f(+0,-1,-0.15f),"Textures\\Floor.dds");
		GamePlane * plane3 = new GamePlane(Vec3f(0  ,0  ,+TS),Vec3f(+0,+0,-1),"Textures\\Wall.dds");
		GamePlane * plane4 = new GamePlane(Vec3f(0  ,0  ,-TS),Vec3f(+0,+0,+1),"Textures\\Wall.dds");
		GamePlane * plane5 = new GamePlane(Vec3f(-TS,0  ,0  ),Vec3f(+1,+0,+0),"Textures\\Wall2.dds");
		GamePlane * plane6 = new GamePlane(Vec3f(+TS,0  ,0  ),Vec3f(-1,+0,+0),"Textures\\Wall2.dds");
		
		// Add to the world and remove reference: now done with it
		
		m_game_object_manager->AddObject(plane1); plane1->RemoveReference();	
		m_game_object_manager->AddObject(plane2); plane2->RemoveReference();
		m_game_object_manager->AddObject(plane3); plane3->RemoveReference();
		m_game_object_manager->AddObject(plane4); plane4->RemoveReference();
		m_game_object_manager->AddObject(plane5); plane5->RemoveReference();
		m_game_object_manager->AddObject(plane6); plane6->RemoveReference();
	}
	
	{
		// Make a number of boxes:
		
		const unsigned NUM_BOXES = 8;
		
		// Randomnly position with this:
		
		Random r(4242);
		
		// Do it:
		
		for ( unsigned i = 0 ; i < NUM_BOXES ; ++i )
		{
			// Pick xyz position:
			
			float x = ( r.NextFloat() * 128.0f   ) - 64;
			float y = ( r.NextFloat() * 128.0f   ) + 24;
			float z = ( r.NextFloat() * 128.0f	 ) - 64;
			
			// Make the box:
			
			GameBox * b = new GameBox( Vec3f(x,y,z) , Vec3f(4,4,4) );
			
			// Add:
			
			m_game_object_manager->AddObject(b); b->RemoveReference();
		}
	}

	// Make the trampoline
	
	GameTrampoline * trampoline = new GameTrampoline
	(
		Vec3f(0,32,0)		,	// position
		Vec2f(TS,TS)		,	// size	
		8					,	// number of squares in it
		4.00f					// mass
	);
	
	// Add to the world and remove reference: now done with it
	
	m_game_object_manager->AddObject(trampoline); trampoline->RemoveReference();
	
	// Hide the mouse since we are using mouse control:
	
	Mouse::SetMouseCursor( m_window , Mouse::CURSOR_NONE );
}
