/**
 * @file llkeyframemotion.h
 * @brief Implementation of LLKeframeMotion class.
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLKEYFRAMEMOTION_H
#define LL_LLKEYFRAMEMOTION_H

//-----------------------------------------------------------------------------
// Header files
//-----------------------------------------------------------------------------

#include <string>

#include "llassetstorage.h"
#include "llbboxlocal.h"
#include "llhandmotion.h"
#include "lljointstate.h"
#include "llmotion.h"
#include "llquaternion.h"
#include "v3dmath.h"
#include "v3math.h"
#include "llbvhconsts.h"

class LLKeyframeDataCache;
class LLDataPacker;

#define MIN_REQUIRED_PIXEL_AREA_KEYFRAME (40.f)
#define MAX_CHAIN_LENGTH (4)

const S32 KEYFRAME_MOTION_VERSION = 1;
const S32 KEYFRAME_MOTION_SUBVERSION = 0;

//-----------------------------------------------------------------------------
// class LLKeyframeMotion
//-----------------------------------------------------------------------------
class LLKeyframeMotion :
    public LLMotion
{
    friend class LLKeyframeDataCache;
public:
    // Constructor
    LLKeyframeMotion(const LLUUID &id);

    // Destructor
    virtual ~LLKeyframeMotion();

private:
    // private helper functions to wrap some asserts
    LLPointer<LLJointState>& getJointState(U32 index);
    LLJoint* getJoint(U32 index );

public:
    //-------------------------------------------------------------------------
    // functions to support MotionController and MotionRegistry
    //-------------------------------------------------------------------------

    // static constructor
    // all subclasses must implement such a function and register it
    static LLMotion *create(const LLUUID& id);

public:
    //-------------------------------------------------------------------------
    // animation callbacks to be implemented by subclasses
    //-------------------------------------------------------------------------

    // motions must specify whether or not they loop
    virtual BOOL getLoop() {
        if (mJointMotionList) return mJointMotionList->mLoop;
        else return FALSE;
    }

    // motions must report their total duration
    virtual F32 getDuration() {
        if (mJointMotionList) return mJointMotionList->mDuration;
        else return 0.f;
    }

    // motions must report their "ease in" duration
    virtual F32 getEaseInDuration() {
        if (mJointMotionList) return mJointMotionList->mEaseInDuration;
        else return 0.f;
    }

    // motions must report their "ease out" duration.
    virtual F32 getEaseOutDuration() {
        if (mJointMotionList) return mJointMotionList->mEaseOutDuration;
        else return 0.f;
    }

    // motions must report their priority
    virtual LLJoint::JointPriority getPriority() {
        if (mJointMotionList) return mJointMotionList->mBasePriority;
        else return LLJoint::LOW_PRIORITY;
    }

    virtual S32 getNumJointMotions()
    {
        if (mJointMotionList)
        {
            return mJointMotionList->getNumJointMotions();
        }
        return 0;
    }

    virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; }

    // called to determine when a motion should be activated/deactivated based on avatar pixel coverage
    virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_KEYFRAME; }

    // run-time (post constructor) initialization,
    // called after parameters have been set
    // must return true to indicate success and be available for activation
    virtual LLMotionInitStatus onInitialize(LLCharacter *character);

    // called when a motion is activated
    // must return TRUE to indicate success, or else
    // it will be deactivated
    virtual BOOL onActivate();

    // called per time step
    // must return TRUE while it is active, and
    // must return FALSE when the motion is completed.
    virtual BOOL onUpdate(F32 time, U8* joint_mask);

    // called when a motion is deactivated
    virtual void onDeactivate();

    virtual void setStopTime(F32 time);

    static void onLoadComplete(const LLUUID& asset_uuid,
                               LLAssetType::EType type,
                               void* user_data, S32 status, LLExtStat ext_status);

public:
    U32     getFileSize();
    BOOL    serialize(LLDataPacker& dp) const;
    BOOL    deserialize(LLDataPacker& dp, const LLUUID& asset_id, bool allow_invalid_joints = true);
    BOOL    isLoaded() { return mJointMotionList != NULL; }
    bool    dumpToFile(const std::string& name);


    // setters for modifying a keyframe animation
    void setLoop(BOOL loop);

    F32 getLoopIn() {
        return (mJointMotionList) ? mJointMotionList->mLoopInPoint : 0.f;
    }

    F32 getLoopOut() {
        return (mJointMotionList) ? mJointMotionList->mLoopOutPoint : 0.f;
    }

    void setLoopIn(F32 in_point);

    void setLoopOut(F32 out_point);

    void setHandPose(LLHandMotion::eHandPose pose) {
        if (mJointMotionList) mJointMotionList->mHandPose = pose;
    }

    LLHandMotion::eHandPose getHandPose() {
        return (mJointMotionList) ? mJointMotionList->mHandPose : LLHandMotion::HAND_POSE_RELAXED;
    }

    void setPriority(S32 priority);

    void setEmote(const LLUUID& emote_id);

    void setEaseIn(F32 ease_in);

    void setEaseOut(F32 ease_in);

    F32 getLastUpdateTime() { return mLastLoopedTime; }

    const LLBBoxLocal& getPelvisBBox();

    static void flushKeyframeCache();

protected:
    //-------------------------------------------------------------------------
    // JointConstraintSharedData
    //-------------------------------------------------------------------------
    class JointConstraintSharedData
    {
    public:
        JointConstraintSharedData() :
            mChainLength(0),
            mEaseInStartTime(0.f),
            mEaseInStopTime(0.f),
            mEaseOutStartTime(0.f),
            mEaseOutStopTime(0.f),
            mUseTargetOffset(FALSE),
            mConstraintType(CONSTRAINT_TYPE_POINT),
            mConstraintTargetType(CONSTRAINT_TARGET_TYPE_BODY),
            mSourceConstraintVolume(0),
            mTargetConstraintVolume(0),
            mJointStateIndices(NULL)
        { };
        ~JointConstraintSharedData() { delete [] mJointStateIndices; }

        S32                     mSourceConstraintVolume;
        LLVector3               mSourceConstraintOffset;
        S32                     mTargetConstraintVolume;
        LLVector3               mTargetConstraintOffset;
        LLVector3               mTargetConstraintDir;
        S32                     mChainLength;
        S32*                    mJointStateIndices;
        F32                     mEaseInStartTime;
        F32                     mEaseInStopTime;
        F32                     mEaseOutStartTime;
        F32                     mEaseOutStopTime;
        BOOL                    mUseTargetOffset;
        EConstraintType         mConstraintType;
        EConstraintTargetType   mConstraintTargetType;
    };

    //-----------------------------------------------------------------------------
    // JointConstraint()
    //-----------------------------------------------------------------------------
    class JointConstraint
    {
    public:
        JointConstraint(JointConstraintSharedData* shared_data);
        ~JointConstraint();

        JointConstraintSharedData*  mSharedData;
        F32                         mWeight;
        F32                         mTotalLength;
        LLVector3                   mPositions[MAX_CHAIN_LENGTH];
        F32                         mJointLengths[MAX_CHAIN_LENGTH];
        F32                         mJointLengthFractions[MAX_CHAIN_LENGTH];
        BOOL                        mActive;
        LLVector3d                  mGroundPos;
        LLVector3                   mGroundNorm;
        LLJoint*                    mSourceVolume;
        LLJoint*                    mTargetVolume;
        F32                         mFixupDistanceRMS;
    };

    void applyKeyframes(F32 time);

    void applyConstraints(F32 time, U8* joint_mask);

    void activateConstraint(JointConstraint* constraintp);

    void initializeConstraint(JointConstraint* constraint);

    void deactivateConstraint(JointConstraint *constraintp);

    void applyConstraint(JointConstraint* constraintp, F32 time, U8* joint_mask);

    BOOL    setupPose();

public:
    enum AssetStatus { ASSET_LOADED, ASSET_FETCHED, ASSET_NEEDS_FETCH, ASSET_FETCH_FAILED, ASSET_UNDEFINED };

    enum InterpolationType { IT_STEP, IT_LINEAR, IT_SPLINE };

    //-------------------------------------------------------------------------
    // ScaleKey
    //-------------------------------------------------------------------------
    class ScaleKey
    {
    public:
        ScaleKey() { mTime = 0.0f; }
        ScaleKey(F32 time, const LLVector3 &scale) { mTime = time; mScale = scale; }

        F32         mTime;
        LLVector3   mScale;
    };

    //-------------------------------------------------------------------------
    // RotationKey
    //-------------------------------------------------------------------------
    class RotationKey
    {
    public:
        RotationKey() { mTime = 0.0f; }
        RotationKey(F32 time, const LLQuaternion &rotation) { mTime = time; mRotation = rotation; }

        F32             mTime;
        LLQuaternion    mRotation;
    };

    //-------------------------------------------------------------------------
    // PositionKey
    //-------------------------------------------------------------------------
    class PositionKey
    {
    public:
        PositionKey() { mTime = 0.0f; }
        PositionKey(F32 time, const LLVector3 &position) { mTime = time; mPosition = position; }

        F32         mTime;
        LLVector3   mPosition;
    };

    //-------------------------------------------------------------------------
    // ScaleCurve
    //-------------------------------------------------------------------------
    class ScaleCurve
    {
    public:
        ScaleCurve();
        ~ScaleCurve();
        LLVector3 getValue(F32 time, F32 duration);
        LLVector3 interp(F32 u, ScaleKey& before, ScaleKey& after);

        InterpolationType   mInterpolationType;
        S32                 mNumKeys;
        typedef std::map<F32, ScaleKey> key_map_t;
        key_map_t           mKeys;
        ScaleKey            mLoopInKey;
        ScaleKey            mLoopOutKey;
    };

    //-------------------------------------------------------------------------
    // RotationCurve
    //-------------------------------------------------------------------------
    class RotationCurve
    {
    public:
        RotationCurve();
        ~RotationCurve();
        LLQuaternion getValue(F32 time, F32 duration);
        LLQuaternion interp(F32 u, RotationKey& before, RotationKey& after);

        InterpolationType   mInterpolationType;
        S32                 mNumKeys;
        typedef std::map<F32, RotationKey> key_map_t;
        key_map_t       mKeys;
        RotationKey     mLoopInKey;
        RotationKey     mLoopOutKey;
    };

    //-------------------------------------------------------------------------
    // PositionCurve
    //-------------------------------------------------------------------------
    class PositionCurve
    {
    public:
        PositionCurve();
        ~PositionCurve();
        LLVector3 getValue(F32 time, F32 duration);
        LLVector3 interp(F32 u, PositionKey& before, PositionKey& after);

        InterpolationType   mInterpolationType;
        S32                 mNumKeys;
        typedef std::map<F32, PositionKey> key_map_t;
        key_map_t       mKeys;
        PositionKey     mLoopInKey;
        PositionKey     mLoopOutKey;
    };

    //-------------------------------------------------------------------------
    // JointMotion
    //-------------------------------------------------------------------------
    class JointMotion
    {
    public:
        PositionCurve   mPositionCurve;
        RotationCurve   mRotationCurve;
        ScaleCurve      mScaleCurve;
        std::string     mJointName;
        U32             mUsage;
        LLJoint::JointPriority  mPriority;

        void update(LLJointState* joint_state, F32 time, F32 duration);
    };

    //-------------------------------------------------------------------------
    // JointMotionList
    //-------------------------------------------------------------------------
    class JointMotionList
    {
    public:
        std::vector<JointMotion*> mJointMotionArray;
        F32                     mDuration;
        BOOL                    mLoop;
        F32                     mLoopInPoint;
        F32                     mLoopOutPoint;
        F32                     mEaseInDuration;
        F32                     mEaseOutDuration;
        LLJoint::JointPriority  mBasePriority;
        LLHandMotion::eHandPose mHandPose;
        LLJoint::JointPriority  mMaxPriority;
        typedef std::list<JointConstraintSharedData*> constraint_list_t;
        constraint_list_t       mConstraints;
        LLBBoxLocal             mPelvisBBox;
        // mEmoteName is a facial motion, but it's necessary to appear here so that it's cached.
        // TODO: LLKeyframeDataCache::getKeyframeData should probably return a class containing
        // JointMotionList and mEmoteName, see LLKeyframeMotion::onInitialize.
        std::string             mEmoteName;
    public:
        JointMotionList();
        ~JointMotionList();
        U32 dumpDiagInfo();
        JointMotion* getJointMotion(U32 index) const { llassert(index < mJointMotionArray.size()); return mJointMotionArray[index]; }
        U32 getNumJointMotions() const { return mJointMotionArray.size(); }
    };

protected:
    JointMotionList*                mJointMotionList;
    std::vector<LLPointer<LLJointState> > mJointStates;
    LLJoint*                        mPelvisp;
    LLCharacter*                    mCharacter;
    typedef std::list<JointConstraint*> constraint_list_t;
    constraint_list_t               mConstraints;
    U32                             mLastSkeletonSerialNum;
    F32                             mLastUpdateTime;
    F32                             mLastLoopedTime;
    AssetStatus                     mAssetStatus;

public:
    void setCharacter(LLCharacter* character) { mCharacter = character; }
};

class LLKeyframeDataCache
{
public:
    // *FIX: implement this as an actual singleton member of LLKeyframeMotion
    LLKeyframeDataCache(){};
    ~LLKeyframeDataCache();

    typedef std::map<LLUUID, class LLKeyframeMotion::JointMotionList*> keyframe_data_map_t;
    static keyframe_data_map_t sKeyframeDataMap;

    static void addKeyframeData(const LLUUID& id, LLKeyframeMotion::JointMotionList*);
    static LLKeyframeMotion::JointMotionList* getKeyframeData(const LLUUID& id);

    static void removeKeyframeData(const LLUUID& id);

    //print out diagnostic info
    static void dumpDiagInfo();
    static void clear();
};

#endif // LL_LLKEYFRAMEMOTION_H