/*
	Trig Wars.
	Copyright (c) 2008, 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		Represents the player's ship.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects\PlayerShip.h"
#include "GameObjects\PlayerBullet.h"
#include "GameObjects\ObstacleManager.h"
#include "Game\GameCore.h"
#include "Game\PlayerBulletManager.h"
#include "Windows\Window.h"
#include "Graphics\BufferedLineRenderer.h"
#include "Graphics\Camera2D.h"
#include "Graphics\ParticleEmitter.h"

using namespace Game;

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

/// Amount of acceleration the player ship has per second

const float PlayerShip::LIN_ACCELERATION = 1350.0f;

/// Amount of acceleration from the directional thrusters

const float PlayerShip::DIRECTIONAL_THRUST_LIN_ACCELERATION = 1050.0f;

/// Amount of angular acceleration the player ship has per second

const float PlayerShip::ANG_ACCELERATION = 6.5f;

/// Maximum number of bullets the player can fire at any one time

const unsigned PlayerShip::MAX_BULLETS = 64;

/// Color of the player's bullets

const Color PlayerShip::BULLET_COLOR = Color(0,1.0f,0.0f);

/// Length of the player's bullets

const float PlayerShip::BULLET_LENGTH = 10.0f;

/// Interval between when the player can fire bullets

const float PlayerShip::BULLET_FIRE_INTERVAL = 0.06f;

/// How quickly to smooth the ships current direction into the movement direction when using directional thrust: higher values mean more smoothing

const float PlayerShip::DIRECTIONAL_THRUST_TURN_SMOOTH = 0.825f;

/// Size of the player ship

const Vec2f PlayerShip::SIZE = Vec2f(15,15);

/// Points for the player's ship

const Vec3f PlayerShip::SHAPE_POINTS[24] = 
{
	Vec3f( -SIZE.x * 0.75f	, + SIZE.y * 1.00f , - 500.0f )	,	// p1
	Vec3f( -SIZE.x * 0.25f	, + SIZE.y * 1.00f , - 500.0f )	,	// p2
	Vec3f( -SIZE.x * 0.25f	, + SIZE.y * 0.50f , - 500.0f )	,	// p3
	Vec3f( +SIZE.x * 0.25f	, + SIZE.y * 0.50f , - 500.0f )	,	// p4
	Vec3f( +SIZE.x * 0.25f	, + SIZE.y * 1.00f , - 500.0f )	,	// p5
	Vec3f( +SIZE.x * 0.75f	, + SIZE.y * 1.00f , - 500.0f )	,	// p6
	Vec3f( +SIZE.x * 1.00f	, + SIZE.y * 0.75f , - 500.0f )	,	// p7
	Vec3f( +SIZE.x * 1.00f	, + SIZE.y * 0.25f , - 500.0f )	,	// p8
	Vec3f( +SIZE.x * 0.50f	, + SIZE.y * 0.25f , - 500.0f )	,	// p9
	Vec3f( +SIZE.x * 0.50f	, - SIZE.y * 0.25f , - 500.0f )	,	// p10
	Vec3f( +SIZE.x * 1.00f	, - SIZE.y * 0.25f , - 500.0f )	,	// p11
	Vec3f( +SIZE.x * 1.00f	, - SIZE.y * 0.75f , - 500.0f )	,	// p12
	Vec3f( +SIZE.x * 0.75f	, - SIZE.y * 1.00f , - 500.0f )	,	// p13
	Vec3f( +SIZE.x * 0.25f	, - SIZE.y * 1.00f , - 500.0f )	,	// p14
	Vec3f( +SIZE.x * 0.25f	, - SIZE.y * 0.50f , - 500.0f )	,	// p15
	Vec3f( -SIZE.x * 0.25f	, - SIZE.y * 0.50f , - 500.0f )	,	// p16
	Vec3f( -SIZE.x * 0.25f	, - SIZE.y * 1.00f , - 500.0f )	,	// p17
	Vec3f( -SIZE.x * 0.75f	, - SIZE.y * 1.00f , - 500.0f )	,	// p18
	Vec3f( -SIZE.x * 1.00f	, - SIZE.y * 0.75f , - 500.0f )	,	// p19
	Vec3f( -SIZE.x * 1.00f	, - SIZE.y * 0.25f , - 500.0f )	,	// p20
	Vec3f( -SIZE.x * 0.50f	, - SIZE.y * 0.25f , - 500.0f )	,	// p21
	Vec3f( -SIZE.x * 0.50f	, + SIZE.y * 0.25f , - 500.0f )	,	// p22
	Vec3f( -SIZE.x * 1.00f	, + SIZE.y * 0.25f , - 500.0f )	,	// p23
	Vec3f( -SIZE.x * 1.00f	, + SIZE.y * 0.75f , - 500.0f )		// p24
};

/// Hexidecimal color of the ship

const unsigned PlayerShip::SHIP_COLOR = 0xffffffff;

/// How quickly the ship turns to face the ships current velocity from 0-1

const float PlayerShip::TURN_SMOOTHING = 0.99f;

/// Amount of smoothing for gamepad input from 0-1

const float PlayerShip::GAMEPAD_SMOOTHING = 0.85f;

/// Amount of smoothing for keyboard input from 0-1

const float PlayerShip::KEYBOARD_SMOOTHING = 0.75f;

//=====================================================================================================================
/// @brief		Constructor: Creates the player's ship
/// @param		lin_vel - linear velocity of the object
/// @param		ang_vel - angular velocity of the object
/// @param		pos - position of the object
/// @param		ang - angle in which the object is facing
/// @pre		Game must exist
/// @remarks	size is the half extents of the ship
/// @post		Game stack pointer should be at the same position as when the function started
//=====================================================================================================================

PlayerShip::PlayerShip
( 
	Vec2f lin_vel	,
	float ang_vel	,
	Vec2f pos		,
	float ang
) 
: MoveableObject(0.98f,0.98f,pos,ang,lin_vel,ang_vel) , DamageableObject(500)
{
	// Make sure ok:
	
	ENSURE( GameCore::GetSingleton() , "Game must exist" );
	
	// Get the stack:
	
	Stack & stack = GameCore::GetSingleton()->GetStack();
	
	// Debug only:
	
	#ifdef DEBUG
	
		void * stack_ptr = stack.GetStackPointer();
	
	#endif
	
	// Make the number of bullets we need and push their pointers onto the stack:
	
	for ( int i = 0 ; i < MAX_BULLETS ; ++i )
	{
		stack.Push( new PlayerBullet() );
	}
	
	// Make our bullet manager
	
	m_bullet_mgr = new PlayerBulletManager
	(
		this,
		MAX_BULLETS,
		stack,		
		BULLET_COLOR,
		BULLET_LENGTH
	);

	// Allow a fire

	m_time_till_bullet = 0;
	
	// Clear this
	
	m_obstacle_mgr = 0;
	
	// Debug only:
	
	#ifdef DEBUG
	
		ENSURE( stack.GetStackPointer() == stack_ptr , "Game stack pointer should be at the same position as when the function started" );
	
	#endif
}

//=====================================================================================================================
/// @brief		Destroys the player ship:
/// @pre		There should be no reference to the obstacle manager at this point
//=====================================================================================================================

PlayerShip::~PlayerShip()
{
	// Get rid of our bullet manager
	
	m_bullet_mgr->RemoveReference();
	
	// Make sure ok:
	
	ENSURE( m_obstacle_mgr == 0 , "There should be no reference to the obstacle manager at this point" );
}

//=====================================================================================================================
/// @brief		Called when the player ship is removed from the game
//=====================================================================================================================

void PlayerShip::OnGameRemoved()
{
	// Remove ref to obstacle manager if there:
	
	if ( m_obstacle_mgr ) m_obstacle_mgr->RemoveReference(); m_obstacle_mgr = 0;
}

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

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

//=====================================================================================================================
/// @brief		Draws the ship
/// @param		c - camera we are viewing from
/// @pre		Given camera cannot be null
/// @pre		Game must exist
/// @pre		Buffered line renderer must exist on the game
//=====================================================================================================================

void PlayerShip::OnDraw( Camera2D * c )
{
	// Make sure ok:
	
	ENSURE( c													, "Given camera cannot be null"						);
	ENSURE( GameCore::GetSingleton()							, "Game must exist"									);
	ENSURE( GameCore::GetSingleton()->GetBufferedLineRenderer()	, "Buffered line renderer must exist on the game"	);
	
	// Let our bullet manager do it's drawing:
	
	m_bullet_mgr->DrawAll(c);
	
	// Get the game
	
	GameCore * game = GameCore::GetSingleton();
	
	// Get the buffered line renderer from the game:
	
	BufferedLineRenderer * r = game->GetBufferedLineRenderer();
	
	// Get the stack from the game:
	
	Stack & stack = game->GetStack();
	
	// Create a rotation matrix to rotate by our orientation

	Matrix2f rot = Matrix2f::Rotation(GetAngle());
	
	// Get our position:
	
	Vec3f pos = Vec3f( GetPosition() );
	
	// Use this to cache transforms for shared points:
	
	Vec3f p = rot.Transform(SHAPE_POINTS[0]) + pos;
	
	// Draw the first line:
	
	stack.Push(p);
	stack.Push(SHIP_COLOR);
	
	p = rot.Transform(SHAPE_POINTS[1]) + pos;
	
	stack.Push(p);
	stack.Push(SHIP_COLOR);	
	
	// Draw the rest of the lines except the last:
	
	for ( int i = 1 ; i < 23 ; ++i )
	{
		// Put the previous point on the stack:
		
		stack.Push(p);
		stack.Push(SHIP_COLOR);
		
		// Figure out next point
		
		p = rot.Transform(SHAPE_POINTS[i+1]) + pos;
		
		// Put on the stack:
		
		stack.Push(p);
		stack.Push(SHIP_COLOR);
	}
	
	// Draw the last line:
	
	stack.Push(p);
	stack.Push(SHIP_COLOR);
	
	p = rot.Transform(SHAPE_POINTS[0]) + pos;
	
	stack.Push(p);
	stack.Push(SHIP_COLOR);
	
	// Draw the ship using the primitives renderer:
	
	r->Draw( c->GetViewProjectionMatrix() , 24 , stack );
}

//=====================================================================================================================
/// @brief		Does ship controls and that.
/// @param		time_delta - time since the last frame.
/// @pre		Game must exist
/// @pre		Game must have a valid camera
/// @pre		This object must have a valid game object manager
/// @pre		Time delta must be >= 0
/// @pre		The ship must have a bullet manager
//=====================================================================================================================

void PlayerShip::OnUpdate( float time_delta )
{
	// Make sure ok:
	
	ENSURE( GameCore::GetSingleton()				, "Game must exist"										);
	ENSURE( GameCore::GetSingleton()->GetCamera()	, "Game must have a valid camera"						);
	ENSURE( time_delta >= 0							, "Time delta must be >= 0"								);
	ENSURE( GetManager() != 0						, "This object must have a valid game object manager"	);
	ENSURE( m_bullet_mgr							, "The ship must have a bullet manager"					);
	
	// Base class update

	MoveableObject::OnUpdate(time_delta);
	
	// Get the game:
	
	GameCore * game = GameCore::GetSingleton();
	
	// Do controls:
	
	DoControls(time_delta);
	
	// If we don't have a reference to a obstacle manager then search for it:
	
	if ( m_obstacle_mgr == 0 ) 
	{
		// Try to find it:
		
		m_obstacle_mgr = GetManager()->FindByType<ObstacleManager>("ObstacleManager");
		
		// See if found:
		
		if ( m_obstacle_mgr )
		{
			// Found: add a reference to it
			
			m_obstacle_mgr->AddReference();			
		}
	}
	else
	{
		// See if it was removed from the game:
		
		if ( m_obstacle_mgr->HasManager() == false )
		{
			// Removed from the game: clean up our reference to it
			
			m_obstacle_mgr->RemoveReference(); m_obstacle_mgr = 0;
		}
	}
	
	// Set the camera position to our position:
	
	game->GetCamera()->SetPosition( GetPosition() );
	
	// Update all of our bullets:
	
	m_bullet_mgr->UpdateAll(time_delta);
	
	// Update the time till when we can fire the next bullet:
	
	m_time_till_bullet -= time_delta; 
	
	// Keep at zero
	
	if ( m_time_till_bullet < 0 ) m_time_till_bullet = 0;	
	
	// If we have an obstacle manager then do collision with it:
	
	if ( m_obstacle_mgr )
	{
		// Do collision:
		
		bool was_collision = ObstacleCollide( m_obstacle_mgr , Float::Max( SIZE.x , SIZE.y ) );
		
		// If there was a collision then kill us
		
		if ( was_collision ) 
		{
			// Kill us
		
			Kill();
			
			// Get the particle texture we will use to emit particles
			
			Texture & tex  = m_bullet_mgr->GetParticleTexture();
			
			// Only if valid:
			
			if ( tex.IsValid() )
			{
				// Make a particle burst:
				
				GameCore::GetSingleton()->GetParticleEmitter()->CreateBurst
				(
					512								,
					tex								,
					GetPosition()					,
					0.5f							,	// live time 1
					1.0f							,	// live time 2
					2.0f							,	// size 1
					10.0f							,	// size 2
					Color(1.0f,0.35f,0.25f,0.7f)	,	// color 1
					Color(1.0f,1.00f,0.25f,0.4f)	,	// color 2
					5.0f							,	// speed 1
					150.0f							,	// speed 2
					false								// alpha blend
				);			
			}
		}
	}
	
	// If we are dead then we should probably remove ourselves from the game:
	
	if ( IsDead() )
	{
		GetManager()->RemoveObject(this); 
	}	
}

//=====================================================================================================================
/// @brief		Does controls for the player
/// @param		time_delta - time between this frame and the last one
/// @pre		Time delta must be >= 0 
//=====================================================================================================================

void PlayerShip::DoControls( float time_delta )
{
	// Make sure ok:
	
	ENSURE( time_delta >= 0 , "Time delta must be >= 0" );

	// Get the gamepad state:
	
	Gamepad::GamepadState gps = Gamepad::GetState( Gamepad::PLAYER_ONE );
	
	//=============================================================================================
	// Do either keyboard or gamepad input
	//=============================================================================================
	
	// Smooth values
	
	m_ctrl_move_dir *= GAMEPAD_SMOOTHING;
	m_ctrl_fire_dir *= GAMEPAD_SMOOTHING;	
	
	// See if gamepad is connected:
	
	if ( gps.m_connected )
	{		
		// Add to current movement dir:
		
		m_ctrl_move_dir.x += gps.m_lstick_x * ( 1.0f - GAMEPAD_SMOOTHING );
		m_ctrl_move_dir.y += gps.m_lstick_y * ( 1.0f - GAMEPAD_SMOOTHING );
		
		// Add to current fire dir:
		
		m_ctrl_fire_dir.x += gps.m_rstick_x * ( 1.0f - GAMEPAD_SMOOTHING );
		m_ctrl_fire_dir.y += gps.m_rstick_y * ( 1.0f - GAMEPAD_SMOOTHING );
	}
	
	// Ordinary keyboard input
	
	{
		// Add to current movement dir:
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_D ) )
		{
			m_ctrl_move_dir.x += 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_A ) )
		{
			m_ctrl_move_dir.x -= 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_W) )
		{
			m_ctrl_move_dir.y += 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_S ) )
		{
			m_ctrl_move_dir.y -= 1.0f - KEYBOARD_SMOOTHING;
		}
		
		// Add to current fire dir:
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_RIGHT ) )
		{
			m_ctrl_fire_dir.x += 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_LEFT ) )
		{
			m_ctrl_fire_dir.x -= 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_UP ) )
		{
			m_ctrl_fire_dir.y += 1.0f - KEYBOARD_SMOOTHING;
		}
		
		if ( Keyboard::IsKeyPressed( Keyboard::KB_DOWN ) )
		{
			m_ctrl_fire_dir.y -= 1.0f - KEYBOARD_SMOOTHING;
		}
	}
	
	//=============================================================================================
	// Ensure movement amounts are in range
	//=============================================================================================
	
	m_ctrl_move_dir.x = Float::Clamp( m_ctrl_move_dir.x , -1.0f , 1.0f );
	m_ctrl_move_dir.y = Float::Clamp( m_ctrl_move_dir.y , -1.0f , 1.0f );
	m_ctrl_fire_dir.x = Float::Clamp( m_ctrl_fire_dir.x , -1.0f , 1.0f );
	m_ctrl_fire_dir.y = Float::Clamp( m_ctrl_fire_dir.y , -1.0f , 1.0f );	
	
	//=============================================================================================
	// Turn ship to current direction: if moving above a certain speed
	//=============================================================================================
	
	{
		// If we are below a certain speed then begin slowing it down: otherwise we might get erratic changes
		
		float slowdown = 1;
		
		// Get the speed we are moving at:
		
		float speed = GetLinearVelocity().SqLength();
		
		// See if below a certain threshold:
		
		if ( speed < 1000.0f )
		{
			// Figure out slowdown
			
			slowdown = speed / 1000.0f;
		}
	
		// Get the direction we are moving:
		
		float angle = GetLinearVelocity().GetAngle();
		
		// Figure out the amount of turning required from our current angle
		
		float turning = Angle::Difference( angle , GetAngle() );		
		
		// Do the turning:
		
		float new_angle = Angle::Wrap( GetAngle() ) + ( 1.0f - TURN_SMOOTHING ) * turning * slowdown;
		
		// Do it:
		
		SetAngle(new_angle);
	}
	
	// Apply thrust:
	
	Acclerate( m_ctrl_move_dir * LIN_ACCELERATION , time_delta );
	
	//=============================================================================================
	// Fire if the length of the fire vector is above a certain amount
	//=============================================================================================	
	
	{
		// Get the length of the input for the fire action:
	
		float len = m_ctrl_fire_dir.Length();
		
		// If above a certain threshold then fire:
		
		if ( len > 0.25f ) Fire(m_ctrl_fire_dir);
	}
}

//=====================================================================================================================
/// @brief		Returns true if the player has the buttons pressed for using directional thrusters instead of the 
///				normal thrusters
/// @param		s - current state of the gamepad
/// @returns	True if the buttons for using side thrusters are pressed
//=====================================================================================================================

bool PlayerShip::UsingDirectionalThrusters( const Gamepad::GamepadState & s ) const
{
	// See if the keyboard key for side thrust is pressed:
	
	if ( Keyboard::IsKeyPressed( Keyboard::KB_A ) ) return true;
	
	// See if the left trigger is down enough for thrust:
	
	if ( s.m_connected && s.m_ltrigger >= 0.5f ) return true;
	
	// Not using side thrusters:
	
	return false;
}
