/*
	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 class is responsible for randomly generating a series of obstacles and for allowing collision
///				detection against those obstacles. Obstacles are deformed circular shapes that can be collided
///				with.
//=====================================================================================================================

#include "Precompiled.h"
#include "GameObjects\ObstacleManager.h"
#include "GameObjects\Obstacle.h"
#include "Game\GameCore.h"
#include "Game\QuadTree.h"
#include "Game\QuadTreeEntry.h"
#include "Game\QuadTreeNode.h"
#include "Game\QuadTreeAlgo.h"
#include "Graphics\Camera2D.h"
#include "Graphics\BufferedLineRenderer.h"

using namespace Game;

//=====================================================================================================================
// Constants
//=====================================================================================================================
			
/// The maximum radius of each obstacle

const float ObstacleManager::OBSTACLE_MAX_RADIUS = 100.0f;

/// The minimum radius of each obstacle

const float ObstacleManager::OBSTACLE_MIN_RADIUS = 50.0f;

/// Minimum percent of obstacle radius a point in an obstacle can be at from obstacle center

const float ObstacleManager::OBSTACLE_PT_MIN_RADIUS_PC = 0.85f;

/// Maximum percent of obstacle radius a point in an obstacle can be at from obstacle center

const float ObstacleManager::OBSTACLE_PT_MAX_RADIUS_PC = 1.0f;

/// Minimum number of points an obstacle can have

const unsigned ObstacleManager::OBSTACLE_MIN_POINTS = 12;

/// Maximum number of points an obstacle can have

const unsigned ObstacleManager::OBSTACLE_MAX_POINTS = 18;

/// The minimum distance obstacles should be from the center of the map where the player spawns

const float ObstacleManager::OBSTACLE_MIN_PLAYER_DIST = 100.0f;

//=====================================================================================================================
/// @brief		Creates the obstacle manager with the specified number of obstacles in the level
/// @param		num_obstacles - number of obstacles to make
/// @pre		The number of obstacles should be > 0 
/// @pre		The game should exist
//=====================================================================================================================

ObstacleManager::ObstacleManager( unsigned num_obstacles )
{
	// Debug checks
	
	ENSURE( num_obstacles > 0			, "The number of obstacles should be > 0"	);
	ENSURE( GameCore::GetSingleton()	, "The game should exist"					);

	// Make an array to hold all of the obstacles
	
	m_obstacles = new Obstacle * [num_obstacles];
	
	// Save the number of obstacles
	
	m_num_obstacles = num_obstacles;
	
	// Get the random number generator from the game
	
	Random & random = GameCore::GetSingleton()->GetRandom();
	
	// Make our quad tree
	
	m_quad_tree = new QuadTree( Vec2f::Zero() , GameCore::MAP_AREA , 5 );
	
	// Get the game
	
	GameCore * game = GameCore::GetSingleton();
	
	// Get the two stacks from the game:
	
	Stack & s1 = game->GetStack();
	Stack & s2 = game->GetSecondaryStack();
	
	// Make each individual obstacle:
	
	for ( unsigned i = 0 ; i < m_num_obstacles ; i++ )
	{
		// Pick a random position for this obstacle
		
		Vec2f pos = Vec2f
		(
			random.NextFloat( -GameCore::MAP_AREA.x , +GameCore::MAP_AREA.x ),
			random.NextFloat( -GameCore::MAP_AREA.y , +GameCore::MAP_AREA.y )
		);
		
		// Pick a random radius for this obstacle:
		
		float radius = random.NextFloat( OBSTACLE_MIN_RADIUS , OBSTACLE_MAX_RADIUS );
		
		// Pick the number of points we want
		
		unsigned num_points = random.NextUnsigned( OBSTACLE_MIN_POINTS , OBSTACLE_MAX_POINTS );
		
		// Now keep searching for a good place (not too near other obstacles) to put this obstacles: give up after 32 attempts
		
		bool found_good_placement = false;
		
		// Keep looking for a good place to place the asteroid:
		
		for ( int attempt_num = 0 ; attempt_num < 32 && found_good_placement == false ; attempt_num++ )
		{
			// Save the stack pointer: we are not interested in individual results
			
			void * stack_ptr = s1.GetPointer();
		
			// Make sure we are at least twice our radius away from other obstacles
			
			unsigned num_results = ObstacleCollide( pos , radius * 2 , s1 , s2 );
			
			// See if there was a collision:
			
			if ( num_results == 0 )
			{
				// No collision: this is a good place to put the asteroid. Or is it ? we need to check that it is not too close to the center of the map also
				
				if ( pos.DistanceTo( Vec2f::Zero() ) > OBSTACLE_MIN_PLAYER_DIST + radius ) 
				{
					// Found a good placement
				
					found_good_placement = true;
				}
				else
				{
					// Go again:
					
					pos = Vec2f
					(
						random.NextFloat( -GameCore::MAP_AREA.x , +GameCore::MAP_AREA.x ),
						random.NextFloat( -GameCore::MAP_AREA.y , +GameCore::MAP_AREA.y )
					);					
				}
			}
			else
			{
				// Pick a new position:
				
				pos = Vec2f
				(
					random.NextFloat( -GameCore::MAP_AREA.x , +GameCore::MAP_AREA.x ),
					random.NextFloat( -GameCore::MAP_AREA.y , +GameCore::MAP_AREA.y )
				);
			}
			
			// Restore the stack pointer
			
			s1.SetPointer(stack_ptr);
		}
		
		// Make the obstacle:
		
		m_obstacles[i] = new Obstacle( num_points , pos , radius * OBSTACLE_PT_MIN_RADIUS_PC , radius * OBSTACLE_PT_MAX_RADIUS_PC );		
		
		// Insert into the quad tree
	
		m_obstacles[i]->SetQuadTreeEntry( m_quad_tree->AddObject( m_obstacles[i] ) );
	}
}

//=====================================================================================================================
/// @brief		Destroys the obstacle manager
/// @pre		All obstacles should have a quad tree entry
//=====================================================================================================================

ObstacleManager::~ObstacleManager()
{
	// Need to remove all objects from the quad tree and delete:
	
	for ( unsigned i = 0 ; i < m_num_obstacles; ++i )
	{
		// Get this obstacle:
		
		Obstacle * obs = m_obstacles[i];
		
		// Make sure ok:
		
		ENSURE( obs->GetQuadTreeEntry() != 0 , "All obstacles should have a quad tree entry" );
		
		// Remove from the quad tree
		
		m_quad_tree->RemoveObject( obs->GetQuadTreeEntry() );
		
		// Clear it's entry
	
		obs->SetQuadTreeEntry(0);
		
		// Done with this object
		
		obs->RemoveReference();
	}
	
	// Delete the array of pointers
	
	delete[] m_obstacles;
	
	// Done with the quad tree:
	
	m_quad_tree->RemoveReference();
}

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

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

//=====================================================================================================================
/// @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 obstacle must draw at least 3 lines
/// @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 ObstacleManager::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"		);

	// 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 position of the camera

	Vec2f cam_pos = c->GetPosition();
	
	// Save the number of visible objects here

	unsigned visible;	

	// Do a search in the quad tree for obstacles 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 ) - OBSTACLE_MAX_RADIUS , 
			view_area + OBSTACLE_MAX_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:

			Obstacle * o = stack.Pop<Obstacle*>();

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

			#ifdef DEBUG

				// Get the number of lines

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

				// Make sure ok:

				ENSURE( count >= 3 , "Each render function for each obstacle must draw at least 3 lines" );

				// Add to line count

				num_lines += count;

			#else				

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

			#endif
		}

		// Now draw everything that is on the stack:

		game->GetBufferedLineRenderer()->Draw( c->GetViewProjectionMatrix() , num_lines , secondary_stack );
	}

	// 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		Returns a list of pointers to obstacles that the given circle collides with
/// @param		circle_pos - position of the circle
/// @param		circle_radius - radius of the circle
/// @param		results_stack - stack to save the pointers of all the objects collide with. The number of pointers 
///				placed onto the stack equals the return value of this function, and all objects pointed to are 
///				'Obstacle' type objects.
/// @param		work_stack - stack that the manager needs when filtering out collisions in the broad phase
/// @returns	The number of obstacles the given circle collides with
/// @pre		The given circle position must be wrapped within map boundaries
/// @pre		The results stack should not be the same as the work stack
/// @post		The work stack pointer should be at the same position as when the function started
//=====================================================================================================================

unsigned ObstacleManager::ObstacleCollide( const Vec2f & circle_pos , float circle_radius , Stack & results_stack , Stack & work_stack )
{
	// Make sure ok:
	
	ENSURE( PositionalObject::IsWrapped(circle_pos) , "The given circle position must be wrapped within map boundaries" );
	ENSURE( &results_stack !=  &work_stack			, "The results stack should not be the same as the work stack"		);
	
	// Debug only
	
	#ifdef DEBUG
	
		void * work_stack_ptr = work_stack.GetPointer();
	
	#endif
	
	// Calculate the half extents of the search area we will search over:
	
	Vec2f sah = Vec2f
	(
		circle_radius + OBSTACLE_MAX_RADIUS	,
		circle_radius + OBSTACLE_MAX_RADIUS
	);
	
	// Search in the quad tree for nearby obstacles
	
	unsigned num_results = QuadTreeAlgo::FindInRectangle
	(
		m_quad_tree			,
		work_stack			,
		circle_pos - sah	,
		sah * 2.0f
	);
	
	// Store the number of actual collisions here:
	
	unsigned num_collisions = 0;
	
	// Run through the results of the quad tree search:
	
	for ( unsigned i = num_results ; i != 0 ; --i )
	{
		// Get this result:
		
		Obstacle * o = work_stack.Pop<Obstacle*>();
		
		// See if the circle collides with it:
		
		if ( o->CollidesWithCircle(circle_pos,circle_radius) )
		{
			// Collision: put this onto the results stack
			
			results_stack.Push(o);
			
			// One more collision:
			
			num_collisions++;
		}
	}	
	
	// Debug only
	
	#ifdef DEBUG
	
		ENSURE( work_stack_ptr == work_stack.GetPointer() , "The work stack pointer should be at the same position as when the function started" );
	
	#endif	
	
	// Return the number of collisions that were found:
	
	return num_collisions;
}
