#pragma once

#include "Mix/Dynamics/IKinematicCharacter.h"
#include "Mix/Private/Dynamics/Object.h"

#include "LinearMath/btVector3.h"
#include "BulletDynamics/Character/btCharacterControllerInterface.h"
#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"

class btCollisionShape;
class btConvexShape;
class btRigidBody;
class btCollisionWorld;
class btCollisionDispatcher;
class btPairCachingGhostObject;

//class btKinematicCharacterController;

namespace Mix{ namespace Dynamics{

	class ObjectContext;
	class CapsuleShape;

	class KinematicCharacter : public Mix::Dynamics::IKinematicCharacter, public Mix::Dynamics::Object
	{
	private:
		class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
		{
		protected:
			btCollisionObject* m_me;

		public:
			ClosestNotMeRayResultCallback( btCollisionObject* me );

			virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace );
		};

		class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
		{
		protected:
			btCollisionObject* m_me;
			const btVector3 m_up;
			btScalar m_minSlopeDot;

		public:
			ClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot );

			virtual btScalar addSingleResult( btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace );
		};

		ATTRIBUTE_ALIGNED16( class ) Controller : public btCharacterControllerInterface
		{
		protected:
			btScalar m_halfHeight;
	
			btPairCachingGhostObject* m_ghostObject;
			btConvexShape* m_convexShape;//is also in m_ghostObject, but it needs to be convex, so we store it here to avoid upcast
	
			btScalar m_verticalVelocity;
			btScalar m_verticalOffset;
			btScalar m_fallSpeed;
			btScalar m_jumpSpeed;
			btScalar m_maxJumpHeight;
			btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value)
			btScalar m_maxSlopeCosine;  // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization)
			btScalar m_gravity;

			btScalar m_turnAngle;
	
			btScalar m_stepHeight;

			btScalar m_addedMargin;//@todo: remove this and fix the code

			///this is the desired walk direction, set by the user
			btVector3 m_walkDirection;
			btVector3 m_normalizedDirection;

			//some internal variables
			btVector3 m_currentPosition;
			btScalar  m_currentStepOffset;
			btVector3 m_targetPosition;

			///keep track of the contact manifolds
			btManifoldArray	m_manifoldArray;

			bool m_touchingContact;
			btVector3 m_touchingNormal;

			bool m_wasOnGround;
			bool m_wasJumping;
			bool m_useGhostObjectSweepTest;
			bool m_useWalkDirection;
			btScalar m_velocityTimeInterval;
			int m_upAxis;

			btScalar m_activeThreshold;
			btScalar m_activeThreshold2;
			btScalar m_deactivationElapsedTime;
			btScalar m_deactiveTimeCounter;
			bool m_active;

			bool m_interpolateUp;
			bool full_drop;
			bool bounce_fix;

		protected:
			btVector3 computeReflectionDirection( const btVector3& direction, const btVector3& normal );
			btVector3 parallelComponent( const btVector3& direction, const btVector3& normal );
			btVector3 perpindicularComponent( const btVector3& direction, const btVector3& normal );

			bool recoverFromPenetration( btCollisionWorld* collisionWorld );
			void stepUp( btCollisionWorld* collisionWorld );
			void updateTargetPositionBasedOnCollision( const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0) );
			void stepForwardAndStrafe (btCollisionWorld* collisionWorld, const btVector3& walkMove);
			void stepDown( btCollisionWorld* collisionWorld, btScalar dt );

			static btVector3* getUpAxisDirections( void );
			static btVector3 getNormalizedVector( const btVector3& v );

		public:
			////////////////////////////////////////////////////////////////////////////////////////////////////
			// Controller
			////////////////////////////////////////////////////////////////////////////////////////////////////

			BT_DECLARE_ALIGNED_ALLOCATOR();

			Controller( btPairCachingGhostObject* ghostObject,btConvexShape* convexShape,btScalar stepHeight, int upAxis = 1 );
			~Controller( void );

			void setUpAxis( int axis );
			void setFallSpeed( btScalar fallSpeed );
			void setJumpSpeed( btScalar jumpSpeed );
			void setMaxJumpHeight( btScalar maxJumpHeight );

			btScalar getGravity( void ) const;
			void setGravity( btScalar gravity );

			/// The max slope determines the maximum angle that the controller can walk up.
			/// The slope angle is measured in radians.
			btScalar getMaxSlope( void ) const;
			void setMaxSlope( btScalar slopeRadians );

			btScalar getActiveThreshold( void ) const;
			void setActiveThreshold( btScalar threshold );
			btScalar getDeactivationElapsedTime( void ) const;
			void setDeactivationElapsedTime( btScalar elapsedTime );

			bool isActive( void ) const;

			btPairCachingGhostObject* getGhostObject( void );
			void setUseGhostSweepTest( bool useGhostObjectSweepTest );

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// btCharacterControllerInterface
			////////////////////////////////////////////////////////////////////////////////////////////////////

			/// 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 dt.
			/// This call will reset any velocity set by setVelocityForTimeInterval().
			virtual void setWalkDirection( const btVector3& walkDirection );

			/// Caller provides a velocity with which the character should move 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 btVector3& velocity, btScalar timeInterval );

			virtual void reset( btCollisionWorld* collisionWorld );
			virtual void warp( const btVector3& origin );

			virtual void preStep( btCollisionWorld* collisionWorld );
			virtual void playerStep( btCollisionWorld* collisionWorld, btScalar dt );

			virtual bool canJump( void ) const;
			virtual void jump( void );

			virtual bool onGround( void ) const;
			virtual void setUpInterpolate( bool value );

			////////////////////////////////////////////////////////////////////////////////////////////////////
			// btActionInterface
			////////////////////////////////////////////////////////////////////////////////////////////////////

			virtual void updateAction( btCollisionWorld* collisionWorld,btScalar deltaTime );
			virtual void debugDraw(btIDebugDraw* debugDrawer);
		};

	public:
		static KinematicCharacter* CreateInstance( void );

	private:
		btCapsuleShape* m_pShape;
		btPairCachingGhostObject* m_pObject;
		KinematicCharacter::Controller* m_pController;

		Float32 m_Height;
		Float32 m_Radius;

		Float32 m_MaxFallSpeed;
		Float32 m_InitalJumpSpeed;

		Float32 m_StepHeight;

		Mix::Dynamics::ObjectContext* m_pContext;

		Mix::Vector3 m_LinearVelocity;

		Int32 m_UserIndex;
		void* m_pUserPtr;

	private:
		KinematicCharacter( void );
		virtual ~KinematicCharacter( void );

	public:
		Boolean Initialize( Float32 height, Float32 radius, Float32 stepHeight, const wchar_t* pDebugName );

		Boolean Refresh( void );

	public:
		virtual Mix::Dynamics::ObjectContext* GetContextPtr( void ) const;

		virtual btCollisionObject* Bullet_GetCollisionObjectPtr( void ) const;
		virtual btActionInterface* Bullet_GetActionInterfacePtr( void ) const;
		virtual btCharacterControllerInterface* Bullet_GetCharacterControllerInterfacePtr( void ) const;

	public:
		virtual Float32 GetHeight( void ) const;
		virtual Float32 GetRadius( void ) const;

		virtual void SetGravity( Float32 gravity );
		virtual Float32 GetGravity( void ) const;

		virtual void SetMaxFallSpeed( Float32 speed );
		virtual Float32 GetMaxFallSpeed( void ) const;

		virtual void SetInitalJumpSpeed( Float32 speed );
		virtual Float32 GetInitalJumpSpeed( void ) const;

		virtual Float32 GetStepHeight( void ) const;
		virtual void SetStepHeight( Float32 height );

		virtual void SetSlopeLimit( Float32 rad );
		virtual Float32 GetSlopeLimit( void ) const;

		virtual Float32 GetActiveThreshold( void ) const;
		virtual void SetActiveThreshold( Float32 threshold );
		virtual Float32 GetDeactivationElapsedTime( void ) const;
		virtual void SetDeactivationElapsedTime( Float32 elapsedTime );

		virtual const Mix::Vector3& GetLinearVelocity( void ) const;
		virtual void SetLinearVelocity( const Mix::Vector3& velocity );

		virtual Boolean OnGround( void ) const;
		virtual Boolean IsActive( void ) const;

		virtual void Jump( void );

	public:
		virtual Mix::Dynamics::IObject::TYPE GetType( void ) const;

		virtual Boolean GetShape( Mix::Dynamics::IShape** ppShape );

		virtual Float32 GetShapeMargin( void ) const;
		virtual void SetShapeMargin( Float32 margin );

		virtual UInt16 GetFilterGroup( void ) const;
		virtual void SetFilterGroup( UInt16 filterGroup );

		virtual UInt16 GetFilterMask( void ) const;
		virtual void SetFilterMask( UInt16 filterMask );

		virtual const Mix::Dynamics::MATERIAL& GetMaterial( void ) const;
		virtual void SetMaterial( const Mix::Dynamics::MATERIAL& material );

		virtual void SetWorldTransform( const Mix::Quaternion& rot, const Mix::Vector3& pos );

		virtual Mix::Quaternion GetWorldRotation( void ) const;
		virtual void SetWorldRotation( const Mix::Quaternion& rotation );

		virtual Mix::Vector3 GetWorldPosition( void ) const;
		virtual void SetWorldPosition( const Mix::Vector3& pos );

		virtual Mix::Matrix4x4 GetWorldMatrix( void ) const;

		virtual Boolean IsInWorld( void ) const;

		virtual Mix::Geometry::AABB GetBounds( void ) const;

		virtual Boolean AddListener( Mix::Dynamics::IObjectListener* pListener );
		virtual void RemoveListener( Mix::Dynamics::IObjectListener* pListener );

		virtual Int32 GetUserIndex( void ) const;
		virtual void SetUserIndex( Int32 index );

		virtual void* GetUserPtr( void ) const;
		virtual void SetUserPtr( void* pData );

		virtual UInt32 Debug_GetDrawFlags( void ) const;
		virtual void Debug_SetDrawFlags( UInt32 flags );

		virtual Float32 Debug_GetDrawAxisScaling( void ) const;
		virtual void Debug_SetDrawAxisScaling( Float32 scaling );

		virtual void Debug_Draw( Mix::Graphics::Utility::IPerspectiveRenderer* pPerspectiveRenderer, Float32 opacity );

	public:
		static const wchar_t* FAILED_CREATE;
		static const Float32 MIN_LINEAR_VELOCITY;
	};

}}
