/*
	Lan Wars.
	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.
*/

#ifndef GAME_AI_MOVEMENT_H
#define GAME_AI_MOVEMENT_H

//=====================================================================================================================
//=====================================================================================================================

namespace Game
{	
	//=================================================================================================================	
	//=================================================================================================================
	
	class  DoomLevel;
	struct DoomPPNode;
	struct DoomPPSeg;
	struct DoomGLSubsectorV2;

	//=================================================================================================================
	/// @brief		This class performs the most basic level of movement for the AI. It is given an input destination
	///				point and a start point and attempts to avoid obstacles in between, performing turning and 
	///				returning movement intensity in the process. Path-following is performed by using this class in 
	///				conjunction with the path network and a target path.
	//=================================================================================================================

	class AIMover
	{
		public:
		
			//=========================================================================================================
			// Functions
			//=========================================================================================================
		
				//=====================================================================================================	
				/// @brief		Creates the AIMover module
				/// @param		level - level the AI will be moving through
				//=====================================================================================================
				
				AIMover( const DoomLevel & level );
				
				//=====================================================================================================
				/// @brief		Sets the current angle the AI is facing about the Y axis.
				/// @param		angle - new angle to use
				//=====================================================================================================
				
				void SetAngle( float angle )
				{
					// Set:
				
					angle = Angle::Wrap(angle);
					
					// Recalculate our forward direction from this:
					
					m_direction_xz.x = - Float::Sin( m_angle );
					m_direction_xz.y = - Float::Cos( m_angle );

					// Save the angle:

					m_angle = angle;
				}
				
				//=====================================================================================================
				/// @brief		Returns the current angle the AI is facing about the Y axis.
				/// @returns	Angle the AI is facing about the Y axis.
				//=====================================================================================================
				
				inline float GetAngle() const { return m_angle; }

				//=====================================================================================================
				/// @brief		Returns the current direction the AI is facing on the xz axes.
				/// @returns	Direction the AI is facing on the xz axes.
				//=====================================================================================================
				
				inline const Vec2f & GetDirection() const { return m_direction_xz; }
				
				//=====================================================================================================
				/// @brief		Sets the current position of the AI in the level.
				/// @param		pos - new position of the AI in the level
				//=====================================================================================================
				
				inline void SetPosition( const Vec3f & pos )
				{
					m_position		= pos;
					m_position_xz	= Vec2f(pos.x,pos.z);
				}
				
				//=====================================================================================================
				/// @brief		Gets the current position of the AI in the level according to the mover
				/// @returns	The current position of the AI in the level according to the mover
				//=====================================================================================================
				
				inline Vec3f GetPosition() const { return m_position; }

				//=====================================================================================================
				/// @brief		Sets the current goal position of the AI in the level.
				/// @param		pos - goal position of the AI in the level
				//=====================================================================================================

				inline void SetGoalPosition( const Vec3f & pos )
				{
					m_goal_position		= pos;
					m_goal_position_xz	= Vec2f(pos.x,pos.z);
				}

				//=====================================================================================================
				/// @brief		Gets the current goal position of the AI in the level
				/// @returns	The current goal position of the AI in the level
				//=====================================================================================================

				inline const Vec3f & GetGoalPosition() const { return m_goal_position; }

				//=====================================================================================================
				/// @brief		Sets the movement scale for the AI, which acts as a multiplier for the amount of 
				///				forward movement. This value is from 0-1.
				/// @param		movement_scale - amount of movement to perform.
				/// @remarks	This value will be clamped from 0-1.
				//=====================================================================================================
				
				inline void SetMovementScale( float movement_scale ){ m_movement_scale = Float::Clamp(movement_scale,0,1); }
				
				//=====================================================================================================
				/// @brief		Returns the movement scale for the AI, which acts as a multiplier for the amount of 
				///				forward movement. This value is from 0-1.
				/// @returns	The movement scale for the AI.
				//=====================================================================================================
				
				inline float GetMovementScale() const { return m_movement_scale; }
				
				//=====================================================================================================
				/// @brief		Sets the radius in which the AI scans for surrounding obstacles. The AI will try to
				///				avoid obstacles within this radius.
				/// @param		radius - this is the radius to scan for obstacles in
				/// @pre		Given radius must be > 0
				//=====================================================================================================
				
				inline void SetAvoidanceRadius( float radius )
				{ 
					// Make sure ok:
					
					ENSURE( radius > 0 , "Given radius must be > 0" )
				
					// Save:
					
					m_avoidance_radius = radius;	
				}
				
				//=====================================================================================================
				/// @brief		Returns the radius in which the AI scans for surrounding obstacles. The AI will try to
				///				avoid obstacles within this radius.
				/// @returns	The obstacle avoidance radius
				//=====================================================================================================
				
				inline float GetAvoidanceRadius() const { return m_avoidance_radius; }

				//=====================================================================================================
				/// @brief		Sets the strength of turning to avoid obstacles.
				/// @param		strength - Strength of turning to avoid obstacles.
				/// @pre		Given strength must be >= 0
				//=====================================================================================================

				inline void SetAvoidanceStrength( float strength )
				{ 
					// Make sure ok

					ENSURE( strength >= 0 , "Strenght must be >= 0" )

					// Set

					m_avoidance_strength = strength; 
				}

				//=====================================================================================================
				/// @brief		Returns the strength of turning to avoid obstacles.
				/// @returns	Strength of turning to avoid obstacles.
				//=====================================================================================================

				inline float GetAvoidanceStrength() const { return m_avoidance_strength; }

				//=====================================================================================================
				/// @brief		Sets the effect of distance to obstacles on obstacle turning.
				/// @param		attenuation - Effect of distance to obstacles on obstacle turning.
				/// @pre		Attenuation must be >= 0
				//=====================================================================================================

				inline void SetAvoidanceAttenuation( float attenuation )
				{ 
					// Make sure ok

					ENSURE( attenuation >= 0 , "Attenuation must be >= 0" )

					// Set

					m_avoidance_attenuation = attenuation; 
				}

				//=====================================================================================================
				/// @brief		Returns the effect of distance to obstacles on obstacle turning.
				/// @returns	Effect of distance to obstacles on obstacle turning.
				//=====================================================================================================

				inline float GetAvoidanceAttenuation() const { return m_avoidance_attenuation; }

				//=====================================================================================================
				/// @brief		Sets the effect of distance to the goal point on the amount of turning to get to it.
				/// @param		attenuation - Effect of distance to goal point on turning to get there
				/// @pre		Attenuation must be >= 0
				//=====================================================================================================

				inline void SetTurningAttenuation( float attenuation )
				{ 
					// Make sure ok

					ENSURE( attenuation >= 0 , "Attenuation must be >= 0" )

					// Set

					m_turning_attenuation = attenuation;
				}

				//=====================================================================================================
				/// @brief		Gets the effect of distance to the goal point on the amount of turning to get to it.
				/// @returns	Effect of distance to goal point on turning to get there
				//=====================================================================================================

				inline float GetTurningAttenuation() const { return m_turning_attenuation; }

				//=====================================================================================================
				/// @brief		Sets the strength of turning to reach the goal point.
				/// @param		strength - strength of turning to reach the goal point.
				/// @pre		Strenght must be >= 0
				//=====================================================================================================

				inline void SetTurningStrength( float strength )
				{ 
					// Make sure ok

					ENSURE( strength >= 0 , "Strenght must be >= 0" )

					// Set

					m_turning_strength = strength; 
				}

				//=====================================================================================================
				/// @brief		Gets the strength of turning to reach the goal point.
				/// @returns	Strength of turning to reach the goal point.
				//=====================================================================================================

				inline float GetTurningStrength() const { return m_turning_strength; }

				//=====================================================================================================
				/// @brief		Sets the limit of turning to reach goal point per frame.
				/// @param		limit - limit of turning to reach goal point per frame.
				/// @pre		Given limit must be >= 0
				//=====================================================================================================

				inline void SetTurningLimit( float limit )
				{
					// Make sure ok

					ENSURE( limit >= 0 , "Given limit must be >= 0" )

					// Set:

					m_turning_limit = limit;
				}

				//=====================================================================================================
				/// @brief		Gets the limit of turning to reach goal point per frame.
				/// @returns	Limit of turning to reach goal point per frame.
				//=====================================================================================================

				inline float GetTurningLimit() const { return m_turning_limit; }

				//=====================================================================================================
				/// @brief		Sets the limit of turning to avoid obstacles per frame.
				/// @param		limit - limit of turning to avoid obstacles per frame.
				/// @pre		Given limit must be >= 0
				//=====================================================================================================

				inline void SetAvoidanceLimit( float limit )
				{
					// Make sure ok

					ENSURE( limit >= 0 , "Given limit must be >= 0" )

					// Set:

					m_avoidance_limit = limit;
				}

				//=====================================================================================================
				/// @brief		Gets the limit of turning to avoid obstacles per frame.
				/// @returns	Limit of turning to avoid obstacles per frame.
				//=====================================================================================================

				inline float GetAvoidanceLimit() const { return m_avoidance_limit; }

				//=====================================================================================================
				/// @brief		Updates the AI's turning and movement.
				//=====================================================================================================

				void Update();
				
		private:
		
			//=========================================================================================================
			// Functions
			//=========================================================================================================
				
				//=====================================================================================================
				/// @brief		Peforms a lenard-jones potential function evaluation against the given point. This 
				///				particular function however only evaluates the strength of repulsion.
				/// @param		dist - distance to the point of repulsion
				/// @param		rs - strength of repulsion
				/// @param		ra - attenuation of repulsion. Effect that distance has on the repulsion force.
				/// @remarks	This function will always return a negative number, a number that gets bigger when the 
				///				strength of repulsion increases.
				//=====================================================================================================
				
				inline float EvaluateRepulsion( float dist , float rs , float ra ) const
				{
					// Calculate the divisor for repulsion attenuation:
					
					float div = Float::Pow(dist,ra);
					
					// Make sure this never reaches zero
					
					div += 0.000001f;
					
					// Evaluate the potential function;
					
					return - rs / div;
				}

				//=====================================================================================================
				/// @brief		Peforms a lenard-jones potential function evaluation against the given point. This 
				///				particular function however only evaluates the strength of attaction.
				/// @param		p - xz position of the point to evaluate against
				/// @param		aa - strength of attraction
				/// @param		aa - attenuation of attraction. Effect that distance has on the attraction force.
				/// @remarks	This function will always return a positive number, a number that gets bigger when the 
				///				strength of attraction increases.
				//=====================================================================================================
				
				inline float EvaluateAttraction( const Vec2f & p , float as , float aa ) const
				{
					// Get the distance from us to the given point
					
					float dist = ( p - m_position_xz ).Length();
					
					// Calculate the divisor for attraction attenuation:
					
					float div = Float::Pow(dist,aa);
					
					// Make sure this never reaches zero
					
					div += 0.000001f;
					
					// Evaluate the potential function;
					
					return as / div;
				}

				//=====================================================================================================
				/// @brief		Calcualtes the amount of turning due to collision avoidance.
				/// @returns	Amount of turning due to collision avoidance.
				//=====================================================================================================

				float GetAvoidanceTurning();

				//=====================================================================================================
				/// @brief		Recursively builds a list of segments in the level that surround the AI within it's 
				///				obstacle avoidance radius.
				/// @param		stack - stack to put all the surrounding segments of interest into
				/// @param		node - which node to start this process from in the BSP tree
				/// @returns	The number of segs that surround the AI and which are put into the stack
				/// @remarks	The segs are stored as pointers to segs on the stack
				//=====================================================================================================
				
				unsigned FindSurroundingSegsRecursive( Stack & stack , const DoomPPNode & node );							
				
				//=====================================================================================================
				/// @brief		Adds a list of segs that the AI can collide with in the given subsector into the 
				///				given stack. Returns the number of segs added to the stack.
				/// @param		stack - stack to put the segs into
				/// @param		subsector - subsector we are examining
				/// @returns	The number of segs put onto the stack
				/// @remarks	The segs are stored as pointers to segs on the stack
				//=====================================================================================================
				
				unsigned FindSurroundingSegsInSubsector( Stack & stack , const DoomGLSubsectorV2 & subsector );	
				
				//=====================================================================================================
				/// @brief		Tells if the given line segment can be passed by the AI from it's current position
				/// @returns	True if the given line segment can be passed by the AI
				//=====================================================================================================
				
				bool IsSegPassable( const DoomPPSeg & seg );

			//=========================================================================================================
			// Variables
			//=========================================================================================================
			
				/// This is the current position of the AI in the level.
				
				Vec3f m_position;
				
				/// This is the xz position of the AI in the level.
				
				Vec2f m_position_xz;

				/// This is the position the AI would like to reach in the level.

				Vec3f m_goal_position;

				/// This is the position the AI would like to reach in the level. 2D xz coords.

				Vec2f m_goal_position_xz;
				
				/// This is the current direction we are facing on the xz axes.
				
				Vec2f m_direction_xz;
				
				/// This is the current angle we are facing about the y axis.
				
				float m_angle;
				
				/// This is the amount to scale movement by. From 0-1. 0 means the mover will not move the AI forward at all.
				
				float m_movement_scale;
				
				/// This is the doom level
				
				const DoomLevel * m_level;
				
				/// This is the radius to scan for obstacles (walls) that the AI must try and avoid
				
				float m_avoidance_radius;

				/// Controls the strength of turning to avoid walls

				float m_avoidance_strength;

				/// Controls the attenuation of turning (affect that distance to walls has) to avoid walls

				float m_avoidance_attenuation;

				/// The limit of turning to avoid obstacles in either the positive or negative direction per frame.

				float m_avoidance_limit;

				/// Controls the strength of turning to reach the goal point

				float m_turning_strength;

				/// Controls the attenuation of turning to reach the goal point. This is the effect that distance from the goal point has.

				float m_turning_attenuation;

				/// The limit of turning to reach the goal point in either the positive or negative direction per frame.

				float m_turning_limit;
	};
};

#endif
