/**
 * @file llkeyframemotionparam.cpp
 * @brief Implementation of LLKeyframeMotion 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$
 */

//-----------------------------------------------------------------------------
// Header Files
//-----------------------------------------------------------------------------
#include "linden_common.h"

#include "llkeyframemotionparam.h"
#include "llcharacter.h"
#include "llmath.h"
#include "m3math.h"
#include "lldir.h"
#include "llanimationstates.h"

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// LLKeyframeMotionParam class
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// LLKeyframeMotionParam()
// Class Constructor
//-----------------------------------------------------------------------------
LLKeyframeMotionParam::LLKeyframeMotionParam( const LLUUID &id) : LLMotion(id)
{
    mDefaultKeyframeMotion = NULL;
    mCharacter = NULL;

    mEaseInDuration = 0.f;
    mEaseOutDuration = 0.f;
    mDuration = 0.f;
    mPriority = LLJoint::LOW_PRIORITY;
}


//-----------------------------------------------------------------------------
// ~LLKeyframeMotionParam()
// Class Destructor
//-----------------------------------------------------------------------------
LLKeyframeMotionParam::~LLKeyframeMotionParam()
{
    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            delete paramMotion.mMotion;
        }
        motionList.clear();
    }
    mParameterizedMotions.clear();
}

//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::onInitialize(LLCharacter *character)
//-----------------------------------------------------------------------------
LLMotion::LLMotionInitStatus LLKeyframeMotionParam::onInitialize(LLCharacter *character)
{
    mCharacter = character;

    if (!loadMotions())
    {
        return STATUS_FAILURE;
    }

    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            LLMotion* motion = paramMotion.mMotion;
            motion->onInitialize(character);

            if (motion->getDuration() > mEaseInDuration)
            {
                mEaseInDuration = motion->getEaseInDuration();
            }

            if (motion->getEaseOutDuration() > mEaseOutDuration)
            {
                mEaseOutDuration = motion->getEaseOutDuration();
            }

            if (motion->getDuration() > mDuration)
            {
                mDuration = motion->getDuration();
            }

            if (motion->getPriority() > mPriority)
            {
                mPriority = motion->getPriority();
            }

            LLPose *pose = motion->getPose();

            mPoseBlender.addMotion(motion);
            for (LLJointState *jsp = pose->getFirstJointState(); jsp; jsp = pose->getNextJointState())
            {
                LLPose *blendedPose = mPoseBlender.getBlendedPose();
                blendedPose->addJointState(jsp);
            }
        }
    }

    return STATUS_SUCCESS;
}

//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::onActivate()
//-----------------------------------------------------------------------------
bool LLKeyframeMotionParam::onActivate()
{
    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            paramMotion.mMotion->activate(mActivationTimestamp);
        }
    }
    return true;
}


//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::onUpdate()
//-----------------------------------------------------------------------------
bool LLKeyframeMotionParam::onUpdate(F32 time, U8* joint_mask)
{
    LL_PROFILE_ZONE_SCOPED;
    F32 weightFactor = 1.f / (F32)mParameterizedMotions.size();

    // zero out all pose weights
    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
//          LL_INFOS() << "Weight for pose " << paramMotion.mMotion->getName() << " is " << paramMotion.mMotion->getPose()->getWeight() << LL_ENDL;
            paramMotion.mMotion->getPose()->setWeight(0.f);
        }
    }


    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        const std::string& paramName = motion_pair.first;
        F32* paramValue = (F32 *)mCharacter->getAnimationData(paramName);
        if (NULL == paramValue) // unexpected, but...
        {
            LL_WARNS() << "paramValue == NULL" << LL_ENDL;
            continue;
        }

        // DANGER! Do not modify mParameterizedMotions while using these pointers!
        const ParameterizedMotion* firstMotion = NULL;
        const ParameterizedMotion* secondMotion = NULL;

        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            paramMotion.mMotion->onUpdate(time, joint_mask);

            F32 distToParam = paramMotion.mParam - *paramValue;

            if ( distToParam <= 0.f)
            {
                // keep track of the motion closest to the parameter value
                firstMotion = &paramMotion;
            }
            else
            {
                // we've passed the parameter value
                // so store the first motion we find as the second one we want to blend...
                if (firstMotion && !secondMotion )
                {
                    secondMotion = &paramMotion;
                }
                //...or, if we've seen no other motion so far, make sure we blend to this only
                else if (!firstMotion)
                {
                    firstMotion = &paramMotion;
                    secondMotion = &paramMotion;
                }
            }
        }

        LLPose *firstPose;
        LLPose *secondPose;

        if (firstMotion)
            firstPose = firstMotion->mMotion->getPose();
        else
            firstPose = NULL;

        if (secondMotion)
            secondPose = secondMotion->mMotion->getPose();
        else
            secondPose = NULL;

        // now modify weight of the subanim (only if we are blending between two motions)
        if (firstMotion && secondMotion)
        {
            if (firstMotion == secondMotion)
            {
                firstPose->setWeight(weightFactor);
            }
            else if (firstMotion->mParam == secondMotion->mParam)
            {
                firstPose->setWeight(0.5f * weightFactor);
                secondPose->setWeight(0.5f * weightFactor);
            }
            else
            {
                F32 first_weight = 1.f -
                    ((llclamp(*paramValue - firstMotion->mParam, 0.f, (secondMotion->mParam - firstMotion->mParam))) /
                        (secondMotion->mParam - firstMotion->mParam));
                first_weight = llclamp(first_weight, 0.f, 1.f);

                F32 second_weight = 1.f - first_weight;

                firstPose->setWeight(first_weight * weightFactor);
                secondPose->setWeight(second_weight * weightFactor);

//              LL_INFOS() << "Parameter " << *paramName << ": " << *paramValue << LL_ENDL;
//              LL_INFOS() << "Weights " << firstPose->getWeight() << " " << secondPose->getWeight() << LL_ENDL;
            }
        }
        else if (firstMotion && !secondMotion)
        {
            firstPose->setWeight(weightFactor);
        }
    }

    // blend poses
    mPoseBlender.blendAndApply();

    LL_INFOS() << "Param Motion weight " << mPoseBlender.getBlendedPose()->getWeight() << LL_ENDL;

    return true;
}

//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::onDeactivate()
//-----------------------------------------------------------------------------
void LLKeyframeMotionParam::onDeactivate()
{
    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            paramMotion.mMotion->onDeactivate();
        }
    }
}

//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::addKeyframeMotion()
//-----------------------------------------------------------------------------
bool LLKeyframeMotionParam::addKeyframeMotion(char *name, const LLUUID &id, char *param, F32 value)
{
    LLMotion *newMotion = mCharacter->createMotion( id );

    if (!newMotion)
    {
        return false;
    }

    newMotion->setName(name);

    // now add motion to this list
    mParameterizedMotions[param].insert(ParameterizedMotion(newMotion, value));

    return true;
}


//-----------------------------------------------------------------------------
// LLKeyframeMotionParam::setDefaultKeyframeMotion()
//-----------------------------------------------------------------------------
void LLKeyframeMotionParam::setDefaultKeyframeMotion(char *name)
{
    for (motion_map_t::value_type& motion_pair : mParameterizedMotions)
    {
        motion_list_t& motionList = motion_pair.second;
        for (const ParameterizedMotion& paramMotion : motionList)
        {
            if (paramMotion.mMotion->getName() == name)
            {
                mDefaultKeyframeMotion = paramMotion.mMotion;
            }
        }
    }
}

//-----------------------------------------------------------------------------
// loadMotions()
//-----------------------------------------------------------------------------
bool LLKeyframeMotionParam::loadMotions()
{
    //-------------------------------------------------------------------------
    // Load named file by concatenating the character prefix with the motion name.
    // Load data into a buffer to be parsed.
    //-------------------------------------------------------------------------
    //std::string path = gDirUtilp->getExpandedFilename(LL_PATH_MOTIONS,mCharacter->getAnimationPrefix())
    //  + "_" + getName() + ".llp";
    //RN: deprecated unused reference to "motion" directory
    std::string path;


    //-------------------------------------------------------------------------
    // open the file
    //-------------------------------------------------------------------------
    S32 fileSize = 0;
    LLAPRFile infile ;
    infile.open(path, LL_APR_R, NULL, &fileSize);
    apr_file_t* fp = infile.getFileHandle() ;
    if (!fp || fileSize == 0)
    {
        LL_INFOS() << "ERROR: can't open: " << path << LL_ENDL;
        return false;
    }

    // allocate a text buffer
    std::vector<char> text(fileSize+1);

    //-------------------------------------------------------------------------
    // load data from file into buffer
    //-------------------------------------------------------------------------
    bool error = false;
    char *p = &text[0];
    while ( 1 )
    {
        if (apr_file_eof(fp) == APR_EOF)
        {
            break;
        }
        if (apr_file_gets(p, 1024, fp) != APR_SUCCESS)
        {
            error = true;
            break;
        }
        while ( *(++p) )
            ;
    }

    //-------------------------------------------------------------------------
    // close the file
    //-------------------------------------------------------------------------
    infile.close();

    //-------------------------------------------------------------------------
    // check for error
    //-------------------------------------------------------------------------
    llassert( p <= (&text[0] + fileSize) );

    if ( error )
    {
        LL_INFOS() << "ERROR: error while reading from " << path << LL_ENDL;
        return false;
    }

    LL_INFOS() << "Loading parametric keyframe data for: " << getName() << LL_ENDL;

    //-------------------------------------------------------------------------
    // parse the text and build keyframe data structures
    //-------------------------------------------------------------------------
    p = &text[0];
    S32 num;
    char strA[80]; /* Flawfinder: ignore */
    char strB[80]; /* Flawfinder: ignore */
    F32 floatA = 0.0f;


    //-------------------------------------------------------------------------
    // get priority
    //-------------------------------------------------------------------------
    bool isFirstMotion = true;
    num = sscanf(p, "%79s %79s %f", strA, strB, &floatA);   /* Flawfinder: ignore */

    while(1)
    {
        if (num == 0 || num == EOF) break;
        if ((num != 3))
        {
            LL_INFOS() << "WARNING: can't read parametric motion" << LL_ENDL;
            return false;
        }

        addKeyframeMotion(strA, gAnimLibrary.stringToAnimState(std::string(strA)), strB, floatA);
        if (isFirstMotion)
        {
            isFirstMotion = false;
            setDefaultKeyframeMotion(strA);
        }

        p = strstr(p, "\n");
        if (!p)
        {
            break;
        }

        p++;
        num = sscanf(p, "%79s %79s %f", strA, strB, &floatA);   /* Flawfinder: ignore */
    }

    return true;
}

// End