/*
 * Copyright (C) 2005-2015 Erwin Coumans http://continuousphysics.com/Bullet/
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies.
 * Erwin Coumans makes no representations about the suitability
 * of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
*/
#ifndef BT_KART_HPP
#define BT_KART_HPP

#include "BulletDynamics/Dynamics/btRigidBody.h"
#include "BulletDynamics/ConstraintSolver/btTypedConstraint.h"
#include "physics/btKartRaycast.hpp"
class btDynamicsWorld;
#include "LinearMath/btAlignedObjectArray.h"
#include "BulletDynamics/Vehicle/btWheelInfo.h"
#include "BulletDynamics/Dynamics/btActionInterface.h"

class btVehicleTuning;
class Kart;
struct btWheelContactPoint;

/** rayCast vehicle, very special constraint that turn a rigidbody into a
 *  vehicle.
 */
class btKart : public btActionInterface
{
public:
    class btVehicleTuning
    {
    public:

        btVehicleTuning()
            :m_suspensionStiffness(btScalar(5.88)),
             m_suspensionCompression(btScalar(0.83)),
             m_suspensionDamping(btScalar(0.88)),
             m_maxSuspensionTravelCm(btScalar(500.)),
             m_frictionSlip(btScalar(10.5)),
             m_maxSuspensionForce(btScalar(6000.))
        {
        }   // btVehicleTuning

        btScalar    m_suspensionStiffness;
        btScalar    m_suspensionCompression;
        btScalar    m_suspensionDamping;
        btScalar    m_maxSuspensionTravelCm;
        btScalar    m_frictionSlip;
        btScalar    m_maxSuspensionForce;

    };   // class btVehicleTuning

private:

    btAlignedObjectArray<btVector3> m_forwardWS;
    btAlignedObjectArray<btVector3> m_axle;
    btAlignedObjectArray<btScalar>  m_forwardImpulse;
    btAlignedObjectArray<btScalar>  m_sideImpulse;

    ///backwards compatibility
    int m_userConstraintType;
    int m_userConstraintId;

    static btRigidBody& getFixedBody();
    btScalar calcRollingFriction(btWheelContactPoint& contactPoint);

    btScalar            m_damping;
    btVehicleRaycaster *m_vehicleRaycaster;

    /** True if a zipper is active for that kart. */
    bool                m_zipper_active;

    /** The zipper velocity (i.e. the velocity the kart should reach in
     *  the first frame that the zipper is active). */
    btScalar            m_zipper_velocity;

    /** The angular velocity to be applied when the kart skids.
     *  0 means no skidding. */
    btScalar            m_skid_angular_velocity;

    /** True if the kart is currently skidding. This is used to detect
     *  the end of skidding (i.e. m_skid_angular_velocity=0 and
     *  m_is_skidding=true), and triggers adjusting of the velocity
     *  direction. */
    bool                m_is_skidding;

    /** Sliding (skidding) will only be permited when this is true. Also check
     *  the friction parameter in the wheels since friction directly affects
     *  skidding.
     */
    bool                m_allow_sliding;

    /** An additional impulse that is applied for a certain amount of time. */
    btVector3           m_additional_impulse;

    /** The time the additional impulse should be applied. */
    float               m_time_additional_impulse;

    /** Additional rotation that is applied over a certain amount of time. */
    btVector3           m_additional_rotation;

    /** Duration over which the additional rotation is applied. */
    float               m_time_additional_rotation;

    /** The rigid body that is the chassis of the kart. */
    btRigidBody        *m_chassisBody;

    /** Number of wheels that touch the ground. */
    int                 m_num_wheels_on_ground;

    /** Index of the right axis. */
    int                 m_indexRightAxis;
    /** Index of the up axis. */
    int                 m_indexUpAxis;
    /** Index of the forward axis. */
    int                 m_indexForwardAxis;

    /** The STK kart object which uses this vehicle. This is mostly used to
     *  get access to the kart properties, which also define physics
     *  properties. */
    Kart               *m_kart;

    /** A visual rotation applied to the kart (for skidding).
     *  The physics use this to provide proper wheel contact points
     *  for skid marks. */
    float m_visual_rotation;

    /** True if the visual wheels touch the ground. */
    bool m_visual_wheels_touch_ground;

    /** Contact point of the visual wheel position. */
    btAlignedObjectArray<btVector3> m_visual_contact_point;

    btAlignedObjectArray<btWheelInfo> m_wheelInfo;

    void     defaultInit();
    btScalar rayCast(btWheelInfo& wheel, const btVector3& ray);

public:

    /** Constructor to create a car from an existing rigidbody.
     *  \param chassis The rigid body to use as chassis.
     *  \param raycaster The raycast object to use.
     *  \paran kart The STK kart object that uses this vehicle
     *         (this is used to get access to the kart properties).
     */
                       btKart(btRigidBody* chassis,
                              btVehicleRaycaster* raycaster,
                              Kart *kart);
     virtual          ~btKart();
    void               reset();
    void               debugDraw(btIDebugDraw* debugDrawer);
    const btTransform& getChassisWorldTransform() const;
    btScalar           rayCast(unsigned int index);
    virtual void       updateVehicle(btScalar step);
    void               resetSuspension();
    btScalar           getSteeringValue(int wheel) const;
    void               setSteeringValue(btScalar steering,int wheel);
    void               applyEngineForce(btScalar force, int wheel);
    const btTransform& getWheelTransformWS( int wheelIndex ) const;
    void               updateWheelTransform(int wheelIndex,
                                            bool interpolatedTransform=true);
    btWheelInfo&       addWheel(const btVector3& connectionPointCS0,
                                const btVector3& wheelDirectionCS0,
                                const btVector3& wheelAxleCS,
                                btScalar suspensionRestLength,
                                btScalar wheelRadius,
                                const btVehicleTuning& tuning,
                                bool isFrontWheel);
    const btWheelInfo& getWheelInfo(int index) const;
    btWheelInfo&       getWheelInfo(int index);
    void               updateWheelTransformsWS(btWheelInfo& wheel,
                                              bool interpolatedTransform=true);
    void               setAllBrakes(btScalar brake);
    void               updateSuspension(btScalar deltaTime);
    virtual void       updateFriction(btScalar timeStep);
public:
    void               setSliding(bool active);
    void               instantSpeedIncreaseTo(float speed);
    void               capSpeed(float max_speed);
    // ------------------------------------------------------------------------
    /** Returns true if both rear visual wheels touch the ground. */
    bool visualWheelsTouchGround() const
    {
        return m_visual_wheels_touch_ground;
    }   // visualWheelsTouchGround
    // ------------------------------------------------------------------------
    /** Returns the contact point of a visual wheel.
     *  \param n Index of the wheel, must be 2 or 3 since only the two rear
     *           wheels define the visual position
     */
    const btVector3&   getVisualContactPoint(int n) const
    {
        assert(n>=2 && n<=3);
        return m_visual_contact_point[n];
    }   // getVisualContactPoint
    // ------------------------------------------------------------------------
    /** btActionInterface interface. */
    virtual void updateAction(btCollisionWorld* collisionWorld,
                              btScalar step)
    {
        (void) collisionWorld;
        updateVehicle(step);
    }   // updateAction
    // ------------------------------------------------------------------------
    /** Returns the number of wheels of this vehicle. */
    inline int getNumWheels() const { return int(m_wheelInfo.size());}
    // ------------------------------------------------------------------------
    /** Returns the chassis (rigid) body. */
    inline btRigidBody* getRigidBody() { return m_chassisBody; }
    // ------------------------------------------------------------------------
    /** Returns the chassis (rigid) body. */
    const btRigidBody* getRigidBody() const { return m_chassisBody; }
    // ------------------------------------------------------------------------
    /** Returns the index of the right axis. */
    inline int getRightAxis() const { return m_indexRightAxis; }
    // ------------------------------------------------------------------------
    /** Returns the index of the up axis. */
    inline int getUpAxis() const { return m_indexUpAxis; }
    // ------------------------------------------------------------------------
    /** Returns the index of the forward axis. */
    inline int getForwardAxis() const { return m_indexForwardAxis; }
    // ------------------------------------------------------------------------
    /** Backwards compatibility. */
    int getUserConstraintType() const { return m_userConstraintType ; }
    // ------------------------------------------------------------------------
    void setUserConstraintType(int userConstraintType)
    {
        m_userConstraintType = userConstraintType;
    }   // setUserConstraintType
    // ------------------------------------------------------------------------
    void setUserConstraintId(int uid) { m_userConstraintId = uid; }
    // ------------------------------------------------------------------------
    int getUserConstraintId() const { return m_userConstraintId; }
    // ------------------------------------------------------------------------
    /** Sets the angular velocity to be used when skidding
     *  (0 means no skidding). */
    void setSkidAngularVelocity(float v) {m_skid_angular_velocity = v; }
    // ------------------------------------------------------------------------
    /** Returns the number of wheels on the ground. */
    unsigned int getNumWheelsOnGround() const {return m_num_wheels_on_ground;}
    // ------------------------------------------------------------------------
    /** Sets an impulse that is applied for a certain amount of time.
     *  \param t Time for the impulse to be active.
     *  \param imp The impulse to apply.  */
    void setTimedCentralImpulse(float t, const btVector3 &imp)
    {
        // Only add impulse if no other impulse is active.
        if(m_time_additional_impulse>0) return;
        m_additional_impulse      = imp;
        m_time_additional_impulse = t;
    }   // setTimedImpulse
    // ------------------------------------------------------------------------
    /** Returns the time an additional impulse is activated. */
    float getCentralImpulseTime() const { return m_time_additional_impulse; }
    // ------------------------------------------------------------------------
    /** Sets a visual rotation to be applied, which the physics use to provide
     *  the location where the graphical wheels touch the ground (for
     *  skidmarks). */
    void setVisualRotation(float angle)
    {
        m_visual_rotation = angle;
    }   // setVisualRotation
    // ------------------------------------------------------------------------
    /** Sets a rotation that is applied over a certain amount of time (to avoid
     *  a too rapid changes in the kart).
     *  \param t Time for the rotation to be applied.
     *  \param torque The rotation to apply.  */
    void setTimedRotation(float t, const btVector3 &rot)
    {
        m_additional_rotation      = rot/t;
        m_time_additional_rotation = t;
    }   // setTimedTorque
};   // class btKart

#endif //BT_KART_HPP
