//
// Lol Engine
//
// Copyright: (c) 2010-2012 Sam Hocevar <sam@hocevar.net>
//            (c) 2009-2012 Benjamin Huet <huet.benjamin@gmail.com>
//   This program is free software; you can redistribute it and/or
//   modify it under the terms of the Do What The Fuck You Want To
//   Public License, Version 2, as published by Sam Hocevar. See
//   http://sam.zoy.org/projects/COPYING.WTFPL for more details.
//

//
// The BulletCharacterController class
// ------------------
// This class is a equivalent of btKinematicCharacterController, but more useful for Lol.
//

#if !defined __BULLETCHARACTERCONTROLLER_BULLETCHARACTERCONTROLLER_H__
#define __BULLETCHARACTERCONTROLLER_BULLETCHARACTERCONTROLLER_H__

#ifdef HAVE_PHYS_USE_BULLET
#include "core.h"
#include "EasyPhysics.h"
//#include "BulletDynamics\Character\btCharacterControllerInterface.h"
#endif

#define USE_LOL_CTRLR_CHARAC

namespace lol
{

	namespace phys
	{

#ifdef USE_LOL_CTRLR_CHARAC
#ifdef HAVE_PHYS_USE_BULLET

	//SweepCallback used for Swweep Tests.
	class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
	{
	public:
		ClosestNotMeConvexResultCallback(btCollisionObject* NewMe, const vec3& NewUp, float MinSlopeDot) :
						btCollisionWorld::ClosestConvexResultCallback(LOL2BTU_VEC3(vec3(.0f)), LOL2BTU_VEC3(vec3(.0f))),
						m_me(NewMe),
						m_up(NewUp),
						m_min_slope_dot(MinSlopeDot) { }

		virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& ConvexResult, bool NormalInWorld)
		{
			//We hit ourselves, FAIL
			if (ConvexResult.m_hitCollisionObject == m_me)
				return btScalar(1.f);

			vec3 WorldHitNomal(.0f);
			if (NormalInWorld)
				WorldHitNomal = BT2LOL_VEC3(ConvexResult.m_hitNormalLocal);
			else //need to transform Normal into worldspace
			{
				btVector3 TmpWorldHitNormal = ConvexResult.m_hitCollisionObject->getWorldTransform().getBasis() * ConvexResult.m_hitNormalLocal;
				WorldHitNomal = BT2LOL_VEC3(TmpWorldHitNormal);
			}

			float DotUp = dot(m_up, WorldHitNomal);
			//We hit below the accepted slope_dot, FAIL
			if (DotUp < m_min_slope_dot)
				return btScalar(1.f);

			//Continue to next.
			return ClosestConvexResultCallback::addSingleResult(ConvexResult, NormalInWorld);
		}
	protected:
		btCollisionObject*	m_me;
		const vec3			m_up;
		float				m_min_slope_dot;
	};

		///BulletKinematicCharacterController is an object that supports a sliding motion in a world.
		///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations.
		///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user.
		class BulletKinematicCharacterController : public btActionInterface
		{
		public:
			BulletKinematicCharacterController(btPairCachingGhostObject* NewGhostObject, btConvexShape* NewConvexShape, float NewStepHeight, int NewUpAxis=1)
			{
				m_convex_shape = NewConvexShape;	
				m_i_up_axis = NewUpAxis;
				m_ghost_object = NewGhostObject;
				m_step_height = NewStepHeight;

				m_added_margin = 0.02f;
				m_walk_direction = vec3(.0f, .0f, .0f);
				m_do_gobject_sweep_test = true;
				m_turn_angle = .0f;
				m_use_walk_direction = false; // Should remove walk direction, this doesn't work correctly.
				m_velocity_time_interval = .0f;
				m_vertical_velocity = .0f;
				m_vertical_offset = .0f;
				m_f_gravity = 9.8f * 3.f; // 3G acceleration.
				m_fall_speed = 55.f; // Terminal velocity of a sky diver in m/s.
				m_jump_speed = 10.f; // ?
				m_was_on_ground = false;
				m_was_jumping = false;
				SetMaxSlope(45.f);
			}
			~BulletKinematicCharacterController() { }

		protected:

			static vec3* GetUpAxisDirections()
			{
				static vec3 sUpAxisDirection[3] = { vec3(1.0f, 0.0f, 0.0f), vec3(0.0f, 1.0f, 0.0f), vec3(0.0f, 0.0f, 1.0f) };
	
				return sUpAxisDirection;
			}

			//--------------------------
			//CONVENIENCE FUNCTIONS
			//--

			//Returns the reflection Direction of a ray going 'Direction' hitting a surface with Normal 'Normal' from: http://www-cs-students.stanford.edu/~adityagp/final/node3.html
			vec3 GetReflectedDir(const vec3& Direction, const vec3& Normal)
			{
				return Direction - (2.f * dot(Direction, Normal) * Normal);
			}
			//Returns the portion of 'direction' that is parallel to 'normal'
			vec3 ProjectDirOnNorm(const vec3& Direction, const vec3& Normal)
			{
				return Normal * dot(Direction, Normal);
			}
			//Returns the portion of 'Direction' that is perpindicular to 'Normal'
			vec3 ProjectDirOnNormPerpindicular(const vec3& Direction, const vec3& Normal)
			{
				return Direction - ProjectDirOnNorm(Direction, Normal);
			}
			//Returns Ghost Object. -duh-
			btPairCachingGhostObject* GetGhostObject()
			{
				return m_ghost_object;
			}

			//"Real" war functions
			bool RecoverFromPenetration(btCollisionWorld* CollisionWorld);
			void UpdateTargetOnHit(const vec3& hit_normal, float TangentMag = .0f, float NormalMag = 1.f);
			void DoMove(btCollisionWorld* CollisionWorld, const vec3& MoveStep, float DeltaTime);

		public:
			///btActionInterface interface : KEEP IN camelCase
			virtual void updateAction(btCollisionWorld* CollisionWorld, float deltaTime)
			{
				PreStep(CollisionWorld);
				PlayerStep(CollisionWorld, deltaTime);
			}

			//not in the interface, but called above
			void PreStep(btCollisionWorld* CollisionWorld);
			void PlayerStep(btCollisionWorld* CollisionWorld, float DeltaTime);

			///btActionInterface interface : KEEP IN camelCase
			void debugDraw(btIDebugDraw* debugDrawer) { }
			
			void SetUpAxis(int NewAxis)
			{
				if (NewAxis < 0)
					NewAxis = 0;
				if (NewAxis > 2)
					NewAxis = 2;
				m_i_up_axis = NewAxis;
			}

			//!!!!!! SHOULD DITCH THAT !!!!!!
			//This should probably be called setPositionIncrementPerSimulatorStep.
			//This is neither a Direction nor a velocity, but the amount to
			//increment the position each simulation iteration, regardless
			//of DeltaTime.
			//This call will Reset any velocity set by SetVelocityForTimeInterval().
			virtual void SetWalkDirection(const vec3& walkDirection)
			{
				m_use_walk_direction = true;
				m_walk_direction = walkDirection;
				m_normalized_direction = normalize(m_walk_direction);
			}

			//Caller provides a velocity with which the character should MoveStep for
			//the given time period.  After the time period, velocity is Reset
			//to zero.
			//This call will Reset any walk Direction set by SetWalkDirection().
			//Negative time intervals will result in no motion.
			virtual void SetVelocityForTimeInterval(const vec3& velocity, float timeInterval)
			{
				m_use_walk_direction = false;
				m_walk_direction = velocity;
				m_normalized_direction = normalize(m_walk_direction);
				m_velocity_time_interval = timeInterval;
			}

			//Usefulness ?
			void Reset() { }
			void Warp(const vec3& NewOrigin)
			{
				btTransform NewTransform;
				NewTransform.setIdentity();
				NewTransform.setOrigin(LOL2BTU_VEC3(NewOrigin));
				m_ghost_object->setWorldTransform(NewTransform);
			}

			//External Setup
			//--

			void SetFallSpeed(float NewFallSpeed)			{ m_fall_speed = NewFallSpeed; }
			void SetJumpSpeed(float NewJumpSpeed)			{ m_jump_speed = NewJumpSpeed; }
			void SetMaxJumpHeight(float NewMaxJumpHeight)	{ m_max_jump_height = NewMaxJumpHeight; }

			//Jump logic will go in EasyCC
			bool CanJump() const							{ return OnGround(); }
			void Jump();

			//NewGravity functions
			void SetGravity(float NewGravity)				{ m_f_gravity = NewGravity; }
			float GetGravity() const						{ return m_f_gravity; }

			//The max slope determines the maximum angle that the controller can walk up.
			//The slope angle is measured in radians.
			void SetMaxSlope(float NewSlopeRadians)			{ m_max_slope_radians = NewSlopeRadians; m_max_slope_cosine = lol::cos(NewSlopeRadians); }
			float GetMaxSlope() const						{ return m_max_slope_radians; }

			void SetUseGhostSweepTest(bool UseGObjectSweepTest) { m_do_gobject_sweep_test = UseGObjectSweepTest; }

			bool OnGround() const							{ return m_vertical_velocity == .0f && m_vertical_offset == .0f; }

		private:

			btPairCachingGhostObject*	m_ghost_object;
			btConvexShape*				m_convex_shape; //is also in m_ghost_object, but it needs to be convex, so we store it here to avoid upcast

			//keep track of the contact manifolds
			btManifoldArray				m_manifold_array;

			float						m_half_height;
			float						m_velocity_time_interval;
			float						m_vertical_velocity;
			float						m_vertical_offset;
			float						m_fall_speed;
			float						m_jump_speed;
			float						m_max_jump_height;
			float						m_max_slope_radians; // Slope angle that is set (used for returning the exact value)
			float						m_max_slope_cosine;  // Cosine equivalent of m_max_slope_radians (calculated once when set, for optimization)
			float						m_f_gravity;
			float						m_turn_angle;
			float						m_step_height;
			float						m_added_margin;//@todo: remove this and fix the code

			///this is the desired walk Direction, set by the user
			vec3						m_walk_direction;
			vec3						m_normalized_direction;

			//some internal variables
			vec3						m_current_position;
			float 						m_current_step_offset;
			vec3						m_target_position;

			vec3						m_touching_normal;
			bool						m_touching_contact;

			bool						m_was_on_ground;
			bool 						m_was_jumping;
			bool						m_do_gobject_sweep_test;
			bool						m_use_walk_direction;
			int							m_i_up_axis;

			//---------------------------------------------------------------------
			//NEW INTERNAL VARS
			//---------------------------------------------------------------------

			//Gravity in vec3
			vec3						m_gravity;

			//Current Velocity
			vec3						m_velocity;
		};

#endif // HAVE_PHYS_USE_BULLET
#endif // USE_LOL_CTRLR_CHARAC

	} /* namespace phys */

} /* namespace lol */

#endif /* __BULLETCHARACTERCONTROLLER_BULLETCHARACTERCONTROLLER_H__ */