/*
	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		This is the base class for all AI managers for the different types of enemies. This is a composite
///				game object which actually holds sub game objects underneath it (AIControlled objects). 
///				This base contains functionality common to all AI managers.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects\AIManager.h"
#include "Game\GameCore.h"
#include "Game\QuadTree.h"
#include "Game\QuadTreeAlgo.h"
#include "Game\EnemyBulletManager.h"
#include "GameObjects\ObstacleManager.h"
#include "Graphics\Camera2D.h"
#include "Graphics\BufferedLineRenderer.h"

using namespace Game;

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

/// This is the maximum number of bullets that can be fired by all enemies under an enemy manager

const unsigned AIManager::MAX_BULLETS = 2048;

//=====================================================================================================================
/// @brief		Constructor: creates the AI manager
//=====================================================================================================================

AIManager::AIManager()
{	
	m_ai_pool			= 0; 
	m_player_ship		= 0;
	m_obstacle_manager	= 0;
	m_quad_tree			= 0;
	m_num_updates		= 0;
	m_bullet_manager	= 0;
}

//=====================================================================================================================
/// @brief		Destructor. Destroys the AI object.
/// @pre		There should be no ai pool in existance at this stage
/// @pre		There should be no reference to the player ship at this stage
/// @pre		There should be no reference to the obstacle manager at this stage
//=====================================================================================================================

AIManager::~AIManager()
{
	// Make sure ok:
	
	ENSURE( m_ai_pool == 0			, "There should be no ai pool in existance at this stage"				);
	ENSURE( m_player_ship == 0		, "There should be no reference to the player ship at this stage"		);
	ENSURE( m_obstacle_manager == 0	, "There should be no reference to the obstacle manager at this stage"	);
}

//=====================================================================================================================
/// @brief		Update function. Updates all objects that are in the manager.
/// @param		time_delta - Amount of time between this frame and the last
/// @remarks	This function automatically removes dead objects from the used object pool, so derived 
///				classes needen't duplicate this functionality.
/// @pre		Each object should be in the AI manager that is allocated should be in the quad tree
///	@pre		The AIManager must be in the game
//=====================================================================================================================

void AIManager::OnUpdate( float time_delta )
{
	// Make sure ok:
	
	ENSURE( HasManager() == true , "The AIManager must be in the game" );

	// Update the player ship: if we haven't got it already
	
	if ( m_player_ship == 0 )
	{
		// Look for the player ship:

		m_player_ship = GetManager()->FindByType<PlayerShip>("PlayerShip");

		// Add a ref to it if there:
		
		if ( m_player_ship ) m_player_ship->AddReference();
	}
	else
	{
		// If we have a player ship then make sure it is still in the game:
		
		if ( m_player_ship->GetManager() == 0 )
		{
			// Done with this ship: no longer in the game
		
			m_player_ship->RemoveReference(); m_player_ship = 0;
		}						
	}	 
	
	// Update the obstacle manager: if we haven't got it already
	
	if ( m_obstacle_manager == 0 )
	{
		// Look for the player ship:

		m_obstacle_manager = GetManager()->FindByType<ObstacleManager>("ObstacleManager");

		// Add a ref to it if there:
		
		if ( m_obstacle_manager ) m_obstacle_manager->AddReference();
	}
	else
	{
		// If we have an obstacle manager then make sure it is still in the game:
		
		if ( m_obstacle_manager->GetManager() == 0 )
		{
			// Done with this manager: no longer in the game
		
			m_obstacle_manager->RemoveReference(); m_obstacle_manager = 0;
		}						
	}	

	// Do spawning or whatever derived classes want to do:
	
	OnAIManagerUpdate(time_delta);
	
	// We will use this to deterime which enemies to update: do every 2nd enemy
	
	unsigned full_update_determinator = m_num_updates & 1;
	
	// Store the number of enemies we have examined here:
	
	unsigned examined_count = 0;
	
	// Run through all the used objects in the pool:
	
	Pool<AIControlled>::Enumerator i = m_ai_pool->UsedObjects();
	Pool<AIControlled>::Enumerator e = m_ai_pool->ListEnd();
	
	while ( i != e )										
	{ 	
		// Get the object:
		
		AIControlled * ai = * i;
		
		// Move on in case the update function removes the object
		
		++i;
		
		// See if we are to do a full ai update or just a dead-reckoning like update:
		
		if ( ( examined_count & 1 ) == full_update_determinator )
		{
			// Do a full update for this enemy
					
			UpdateAIObject( ai , time_delta );
			
			// This should have a quad tree entry
			
			ENSURE( ai->GetQuadTreeEntry() , "Each object should be in the AI manager that is allocated should be in the quad tree" );

			// Update the object's quad tree entry:

			m_quad_tree->UpdateEntry( ai->GetQuadTreeEntry() );
			
			// If the object is dead and hasn't been removed then get rid of it and remove from the quad tree:
			
			if ( ai->IsDead() ) 
			{
				ReleasePooledObject(ai);
			}		
		}
		else
		{
			// Do a cheap hack update which is akin to dead reckoning: save the friction on the enemy first
			
			float lin_fric = ai->GetLinearFriction();
			float ang_fric = ai->GetAngularFriction();
			
			// Set to no friction: we don't want ai character slowing down whilst it is not in control
			
			ai->SetAngularFriction(1);
			ai->SetLinearFriction(1);
			
			// Only do a MoveableObject update
			
			ai->MoveableObject::OnUpdate(time_delta);
			
			// Restore previous friction:
			
			ai->SetAngularFriction(ang_fric);
			ai->SetLinearFriction(lin_fric);
		}
		
		// Increase the current enemy we're on:
		
		examined_count++;
	}

	// Update enemy bullets:

	if ( m_bullet_manager ) m_bullet_manager->UpdateAll(time_delta);
		
	// Increment the update number:
	
	m_num_updates++;
}

//=====================================================================================================================
/// @brief		Draw function. Draw all objects that are in the manager
/// @param		c - camera the scene is being viewed from
/// @pre		Given camera cannot be null
/// @pre		The game must exist
/// @pre		Each render function for each ship must draw at least one line
/// @pre		The reported bounding sphere radius of each ship should be greater than 0
/// @pre		The game must have a buffered line renderer
/// @post		The game's stack pointer should be in the same position as when this function started
//=====================================================================================================================				

void AIManager::OnDraw( Camera2D * c )
{
	// Make sure ok:
	
	ENSURE( c															, "Given camera cannot be null"						);
	ENSURE( GameCore::GetSingleton() != 0								, "The game must exist"								);
	ENSURE( GameCore::GetSingleton()->GetBufferedLineRenderer() != 0	, "The game must have a buffered line renderer"		);
	
	// Allow the derived class to do whatever preparation it needs to do
	
	OnAIManagerDraw(c);

	// Get the game

	GameCore * game = GameCore::GetSingleton();

	// Get the stack from the game

	Stack & stack = game->GetStack();

	// Get the secondary stack from the game

	Stack & secondary_stack = game->GetSecondaryStack();

	// Debug only:

	#ifdef DEBUG

		void * stack_pointer_original = stack.GetPointer();

	#endif

	// Get the radius of all the objects in this manager

	float obj_radius = GetShipBoundingCircleRadius();

	// Get the position of the camera

	Vec2f cam_pos = c->GetPosition();

	// Make sure ok:

	ENSURE( obj_radius > 0 , "The reported bounding sphere radius of each ship should be greater than 0" );
	
	// Save the number of visible objects here

	unsigned visible;	

	// Do a search in the quad tree for objects that are visible:
	
	{
		// Get the view area:

		Vec2f view_area = c->GetViewArea();

		// Do the search:

		visible = QuadTreeAlgo::FindInRectangle
		( 
			m_quad_tree , 
			stack , 
			( cam_pos - view_area * 0.5f ) - obj_radius , 
			view_area + obj_radius * 2.0f
		);
	}

	// Get the depth we should draw all objects at:

	float z = ( c->GetNearDist() + c->GetFarDist() ) * -0.5f;

	// Do nothing more if nothing is visible:

	if ( visible > 0 )
	{
		// Store the number of lines to draw here

		unsigned num_lines = 0;

		// Run through all of the visible objects

		for ( unsigned i = visible ; i > 0 ; --i )
		{
			// Get this object:

			AIControlled * ai = stack.Pop<AIControlled*>();

			// Get this object to put it's lines to be rendererd onto the stack

			#ifdef DEBUG

				// Get the number of lines

				unsigned count = ai->BufferedDraw(secondary_stack,cam_pos,z);

				// Make sure ok:

				ENSURE( count > 0 , "Each render function for each ship must draw at least one line" );

				// Add to line count

				num_lines += count;

			#else				

				num_lines += ai->BufferedDraw(secondary_stack,cam_pos,z);

			#endif
		}

		// Now draw everything that is on the stack:

		game->GetBufferedLineRenderer()->Draw( c->GetViewProjectionMatrix() , num_lines , secondary_stack );
	}
	
	// Draw all of our bullets if we have a bullet manager:
	
	if ( m_bullet_manager ) m_bullet_manager->DrawAll(c);

	// Debug only:

	#ifdef DEBUG

		ENSURE
		( 
			stack.GetPointer() == stack_pointer_original , 
			"The game's stack pointer should be in the same position as when this function started"
		)

	#endif
}

//=====================================================================================================================
/// @brief		Called when the object is added to the game. Creates the pool of enemy ships.
/// @post		Derived classes should create at least 1 object for use by the pool
//=====================================================================================================================

void AIManager::OnGameAdded()
{
	// Create the objects used by the pool:
	
	CreatePooledObjects( m_pooled_objects );
	
	// Make sure ok:
	
	ENSURE( m_pooled_objects.size() > 0 , "Derived classes should create at least 1 object for use by the pool" );
	
	// Make the pool itself:
	
	m_ai_pool = new Pool<AIControlled>( m_pooled_objects );

	// Make the quad tree:

	m_quad_tree = new QuadTree( Vec2f::Zero() , GameCore::MAP_AREA , 6 );
}				

//=====================================================================================================================
/// @brief		Called when the object is removed from the game. Removes reference to the player's ship.
/// @post		Derived classes should have removed all previously created pooled objects after this
///				function has finished.
/// @pre		All objects that have been allocated should be in the quad tree
//=====================================================================================================================

void AIManager::OnGameRemoved()
{
	// Get rid of ref to player ship:
	
	if ( m_player_ship ) m_player_ship->RemoveReference(); m_player_ship = 0;
	
	// Remove all used objects from the quad tree:
	
	{
		Pool<AIControlled>::Enumerator i = m_ai_pool->UsedObjects();
		Pool<AIControlled>::Enumerator e = m_ai_pool->ListEnd();
		
		// Remove them all:
		
		while ( i != e )
		{
			// Get this object
			
			QuadTreeEntry * entry = (*i)->GetQuadTreeEntry();
			
			// Make sure ok:
			
			ENSURE( entry != 0 , "All objects that have been allocated should be in the quad tree" );
			
			// Remove from the quad tree:
			
			if ( entry )
			{
				m_quad_tree->RemoveObject(entry);
			}
			
			// Done with this:
			
			(*i)->SetQuadTreeEntry(0); ++i;
		}
	}

	// Destroy the quad tree:

	m_quad_tree->RemoveReference(); m_quad_tree = 0;
	
	// Destroy the pool:
	
	m_ai_pool->RemoveReference(); m_ai_pool = 0;	
	
	// Destroy the objects used by the pool:
	
	DestroyPooledObjects( m_pooled_objects );
	
	// Make sure ok:
	
	ENSURE( m_pooled_objects.size() == 0 , "Derived classes should have removed all previously created pooled objects after this function has finished" );
	
	// Get rid of ref to player ship:
	
	if ( m_player_ship ) m_player_ship->RemoveReference(); m_player_ship = 0;
	
	// Get rid of ref to the obstacle manager:
	
	if ( m_obstacle_manager ) m_obstacle_manager->RemoveReference(); m_obstacle_manager = 0;
	
	// If there is a bullet manager for this enemy type then clean it up:
	
	if ( m_bullet_manager ) m_bullet_manager->RemoveReference();
}

//=====================================================================================================================
/// @brief		Spawns one AIControlled object from the pool and returns the object spawned.
/// @returns	The object allocated
/// @pre		There must be at least 1 free object in the pool to use
/// @pre		The AIManager must be in the game.
/// @pre		The object gotten from the object pool must not be in the quad tree
/// @remarks	This function does NOT add the object allocated into the quad tree.
///				The caller must add the object in manually after it has finished setting it's position etc.
///				If this does not happen however, the AI manager will trigger an error in debug mode on the 
///				next game loop.
//=====================================================================================================================

AIControlled * AIManager::AllocatePooledObject()
{
	// Make sure ok:
	
	ENSURE( m_ai_pool != 0 , "The AIManager must be in the game" );

	// Make sure ok:
	
	ENSURE( m_ai_pool->GetUsedCount() < m_ai_pool->GetSize() , "There must be at least 1 free object in the pool to use" );
	
	// Allocate:

	AIControlled * object = m_ai_pool->Alloc();
	
	// Make sure ok:
	
	ENSURE( object->GetQuadTreeEntry() == 0 , "The object gotten from the object pool must not be in the quad tree" );

	// Return 

	return object;
}

//=====================================================================================================================
/// @brief		Adds the given newly allocated object to the quad tree. Should be called after 
///				calling AllocatedPooledObject() and after the object has been positioned.				
/// @param		object - object to add into the quad tree
/// @pre		The AIManager must be in the game
/// @pre		Given object cannot be null
/// @pre		Given object must not be in the quad tree already
/// @pre		Given object must be allocated from the object pool
//=====================================================================================================================

void AIManager::InsertToQuadTree( AIControlled * object )
{
	// Debug assertions:
	
	ENSURE( HasManager()					, "The AIManager must be in the game"					);
	ENSURE( object							, "Given object cannot be null"							);
	ENSURE( object->GetQuadTreeEntry() == 0 , "Given object must not be in the quad tree already"	);
	ENSURE( object->IsAlloced() == true		, "Given object cannot be null"							);
	
	// Add into the quad tree:
	
	object->SetQuadTreeEntry( m_quad_tree->AddObject(object) );
}

//=====================================================================================================================
/// @brief		Kills one AIControlled object and removes it from the pool
/// @brief		object - object to release
/// @pre		The AIManager must be in the game
/// @pre		Given object cannot be null
/// @pre		The object should be in the quad tree
//=====================================================================================================================

void AIManager::ReleasePooledObject( AIControlled * object )
{
	// Make sure ok:
	
	ENSURE( m_ai_pool != 0 , "The AIManager must be in the game" );

	// Make sure ok:
	
	ENSURE( object != 0 , "Given object cannot be null" );
	
	// Make sure ok:
	
	ENSURE( object->GetQuadTreeEntry() , "The object should be in the quad tree" );
	
	// Remove it from the quad tree:
	
	m_quad_tree->RemoveObject( object->GetQuadTreeEntry() );
	
	// Clear it's quad tree entry
	
	object->SetQuadTreeEntry(0);
	
	// Release it from the pool:
	
	m_ai_pool->Dealloc(object);
}

//=====================================================================================================================
/// @brief		Picks a point on the map that is a certain distance away from the given point, and at least a certain
///				distance away from obstacles.
/// @param		p - point to pick another point away from
/// @param		dist - mininum distance that we
/// @param		obstacle_dist - minimum distance away we must be from obstacles
/// @returns	The chosen point away from the given point
/// @remarks	To prevent infinite loops if an invalid distance is given, this function bails out after 32 iterations.
/// @pre		Game must be initialised
/// @pre		Given distance must not be negative
/// @pre		Given obstacle distance must not be negative
//=====================================================================================================================

Vec2f AIManager::PickPointAwayfrom( const Vec2f & p , float dist , float obstacle_dist )
{
	// Assertions:
	
	ENSURE( GameCore::GetSingleton()	, "Game must be initialised"						);	
	ENSURE( dist >= 0					, "Given distance must not be negative"				);
	ENSURE( obstacle_dist >= 0			, "Given obstacle distance must not be negative"	);
	
	// Get the game:
	
	GameCore * game = GameCore::GetSingleton();		
	
	// Get the obstacle manager:
	
	ObstacleManager * obstacle_mgr = GetObstacleManager();
	
	// Get the random number generator the game uses
	
	Random & random = game->GetRandom();
	
	// This is the maximum number of iterations before we give up
	
	const unsigned MAX_ITERATIONS = 32;
	
	// Keep doing this until we get a good position:
	
	Vec2f saved_position; bool found_pos = false; unsigned iter_num = 0;
	
	// Wrap the given point:
	
	Vec2f p_wrapped = PositionalObject::WrapCoordinates(p);
	
	// Find the right position:
	
	while ( found_pos == false && iter_num < MAX_ITERATIONS )
	{
		// Pick a random direction away from the player
		
		Vec2f r_dir = Vec2f( random.NextFloat() -0.5f , random.NextFloat() -0.5f );
		
		// If the length of the direction is zero (highly unlikely) then set it to 1,0
		
		if ( r_dir.SqLength() == 0 ) 
		{
			// Zero length vector
		
			r_dir.x = 1;
		}
		else
		{
			// Normalise the vector
			
			r_dir.Normalize();
		}
		
		// Pick a random distance:
		
		float r_dist = random.NextFloat( dist , dist + GameCore::MAP_AREA.x + GameCore::MAP_AREA.y );
		
		// Pick the position from this and wrap:
		
		saved_position = PositionalObject::WrapCoordinates( p + r_dir * r_dist );
		
		// Make sure the distance between it and the given point is enough:
		
		if ( ( PositionalObject::UnwrapCoordinates(p_wrapped,saved_position) - p_wrapped ).SqLength() >= dist * dist )
		{			
			// Make sure there is an obstacle manager:
			
			if ( obstacle_mgr )
			{
				// Get the two stacks from the game:
				
				Stack & stack1 = game->GetStack();
				Stack & stack2 = game->GetSecondaryStack();
				
				// Save the current position in stack 1:
				
				void * stack1_ptr = stack1.GetPointer();			
			
				// Good: found a good position.. So far anyhow. We need to check against the obstacle manager also to see if we are near any obstacles
				
				unsigned num_results = obstacle_mgr->ObstacleCollide(saved_position,obstacle_dist,stack1,stack2);
				
				// See if we hit anything:
				
				if ( num_results == 0 )
				{
					// Found a good position: use
				
					found_pos = true;
				}
				
				// Restore the previous stack pointer
				
				stack1.SetPointer(stack1_ptr);				
			}
			else
			{
				// This is a good position: there are no obstacles
				
				found_pos = true;
			}
		}
		
		// Next iteration
		
		iter_num++;
	}
	
	// Return the picked position
	
	return saved_position;	
}
