/*
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at http://live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

#include "CubismMotion.hpp"
#include <float.h>
#include "CubismFramework.hpp"
#include "CubismMotionInternal.hpp"
#include "CubismMotionJson.hpp"
#include "CubismMotionQueueManager.hpp"
#include "CubismMotionQueueEntry.hpp"
#include "Math/CubismMath.hpp"
#include "Type/csmVector.hpp"
#include "Id/CubismIdManager.hpp"

namespace Live2D { namespace Cubism { namespace Framework {

namespace {

const csmChar* EffectNameEyeBlink = "EyeBlink";
const csmChar* EffectNameLipSync  = "LipSync";
const csmChar* TargetNameModel = "Model";
const csmChar* TargetNameParameter = "Parameter";
const csmChar* TargetNamePartOpacity = "PartOpacity";

CubismMotionPoint LerpPoints(const CubismMotionPoint a, const CubismMotionPoint b, const csmFloat32 t)
{
    CubismMotionPoint result;

    result.Time = a.Time + ((b.Time - a.Time) * t);
    result.Value = a.Value + ((b.Value - a.Value) * t);

    return result;
}

csmFloat32 LinearEvaluate(const CubismMotionPoint* points, const csmFloat32 time)
{
    csmFloat32 t = (time - points[0].Time) / (points[1].Time - points[0].Time);

    if (t < 0.0f)
    {
        t = 0.0f;
    }

    return points[0].Value + ((points[1].Value - points[0].Value) * t);
}

csmFloat32 BezierEvaluate(const CubismMotionPoint* points, const csmFloat32 time)
{
    csmFloat32 t = (time - points[0].Time) / (points[3].Time - points[0].Time);

    if (t < 0.0f)
    {
        t = 0.0f;
    }

    const CubismMotionPoint p01 = LerpPoints(points[0], points[1], t);
    const CubismMotionPoint p12 = LerpPoints(points[1], points[2], t);
    const CubismMotionPoint p23 = LerpPoints(points[2], points[3], t);

    const CubismMotionPoint p012 = LerpPoints(p01, p12, t);
    const CubismMotionPoint p123 = LerpPoints(p12, p23, t);

    return LerpPoints(p012, p123, t).Value;
}

csmFloat32 SteppedEvaluate(const CubismMotionPoint* points, const csmFloat32 time)
{
    return points[0].Value;
}

csmFloat32 InverseSteppedEvaluate(const CubismMotionPoint* points, const csmFloat32 time)
{
    return points[1].Value;
}

csmFloat32 EvaluateCurve(const CubismMotionData* motionData, const csmInt32 index, csmFloat32 time)
{
    // Find segment to evaluate.
    const CubismMotionCurve& curve = motionData->Curves[index];

    csmInt32 target = -1;
    const csmInt32 totalSegmentCount = curve.BaseSegmentIndex + curve.SegmentCount;
    csmInt32 pointPosition = 0;
    for (csmInt32 i = curve.BaseSegmentIndex; i < totalSegmentCount; ++i)
    {
        // Get first point of next segment.
        pointPosition = motionData->Segments[i].BasePointIndex
            + (motionData->Segments[i].SegmentType == CubismMotionSegmentType_Bezier
                ? 3
                : 1);


        // Break if time lies within current segment.
        if (motionData->Points[pointPosition].Time > time)
        {
            target = i;
            break;
        }
    }


    if (target == -1)
    {
        return motionData->Points[pointPosition].Value;
    }


    const CubismMotionSegment& segment = motionData->Segments[target];

    return segment.Evaluate(&motionData->Points[segment.BasePointIndex], time);
}

}

CubismMotion::CubismMotion()
    : _sourceFrameRate(30.0f)
    , _loopDurationSeconds(-1.0f)
    , _isLoop(false)                // trueから false へデフォルトを変更
    , _isLoopFadeIn(true)           // ループ時にフェードインが有効かどうかのフラグ
    , _lastWeight(0.0f)
    , _motionData(NULL)
    , _modelCurveIdEyeBlink(NULL)
    , _modelCurveIdLipSync(NULL)
{ }

CubismMotion::~CubismMotion()
{
    CSM_DELETE(_motionData);
}

CubismMotion* CubismMotion::Create(const csmByte* buffer, csmSizeInt size)
{
    CubismMotion* ret = CSM_NEW CubismMotion();

    ret->Parse(buffer, size);
    ret->_sourceFrameRate = ret->_motionData->Fps;
    ret->_loopDurationSeconds = ret->_motionData->Duration;

    // NOTE: Editorではループありのモーション書き出しは非対応
    // ret->_loop = (ret->_motionData->Loop > 0);

    return ret;
}

csmFloat32 CubismMotion::GetDuration()
{
    return _isLoop ? -1.0f : _loopDurationSeconds;
}

void CubismMotion::DoUpdateParameters(CubismModel* model, csmFloat32 userTimeSeconds, csmFloat32 fadeWeight, CubismMotionQueueEntry* motionQueueEntry)
{
    if (_modelCurveIdEyeBlink == NULL)
    {
        _modelCurveIdEyeBlink = CubismFramework::GetIdManager()->GetId(EffectNameEyeBlink);
    }

    if (_modelCurveIdLipSync == NULL)
    {
        _modelCurveIdLipSync = CubismFramework::GetIdManager()->GetId(EffectNameLipSync);
    }

    csmFloat32 timeOffsetSeconds = userTimeSeconds - motionQueueEntry->GetStartTime();

    if (timeOffsetSeconds < 0.0f)
    {
        timeOffsetSeconds = 0.0f; // エラー回避
    }

    csmFloat32 lipSyncValue = FLT_MAX;
    csmFloat32 eyeBlinkValue = FLT_MAX;

    //まばたき、リップシンクのうちモーションの適用を検出するためのビット（maxFlagCount個まで
    const csmInt32 MaxTargetSize = 64;
    csmUint64 lipSyncFlags = 0ULL;
    csmUint64 eyeBlinkFlags = 0ULL;

    //瞬き、リップシンクのターゲット数が上限を超えている場合
    if (_eyeBlinkParameterIds.GetSize() > MaxTargetSize)
    {
        CubismLogDebug("too many eye blink targets : %d", _eyeBlinkParameterIds.GetSize());
    }
    if (_lipSyncParameterIds.GetSize() > MaxTargetSize)
    {
        CubismLogDebug("too many lip sync targets : %d", _lipSyncParameterIds.GetSize());
    }

    const csmFloat32 tmpFadeIn = (_fadeInSeconds <= 0.0f)
                                     ? 1.0f
                                     : CubismMath::GetEasingSine((userTimeSeconds - motionQueueEntry->GetFadeInStartTime()) / _fadeInSeconds);

    const csmFloat32 tmpFadeOut = (_fadeOutSeconds <= 0.0f || motionQueueEntry->GetEndTime() < 0.0f)
                                      ? 1.0f
                                      : CubismMath::GetEasingSine((motionQueueEntry->GetEndTime() - userTimeSeconds) / _fadeOutSeconds);

    csmFloat32 value;
    csmInt32 c, parameterIndex;

    // 'Repeat' time as necessary.
    csmFloat32 time = timeOffsetSeconds;

    if (_isLoop)
    {
        while (time > _motionData->Duration)
        {
            time -= _motionData->Duration;
        }
    }

    csmVector<CubismMotionCurve>& curves = _motionData->Curves;

    // Evaluate model curves.
    for (c = 0; c < _motionData->CurveCount && curves[c].Type == CubismMotionCurveTarget_Model; ++c)
    {
        // Evaluate curve and call handler.
        value = EvaluateCurve(_motionData, c, time);

        if (curves[c].Id == _modelCurveIdEyeBlink)
        {
            eyeBlinkValue = value;
        }
        else if (curves[c].Id == _modelCurveIdLipSync)
        {
            lipSyncValue = value;
        }
    }

    csmInt32 parameterMotionCurveCount = 0;

    for (; c < _motionData->CurveCount && curves[c].Type == CubismMotionCurveTarget_Parameter; ++c)
    {
        parameterMotionCurveCount++;

        // Find parameter index.
        parameterIndex = model->GetParameterIndex(curves[c].Id);

        // Skip curve evaluation if no value in sink.
        if (parameterIndex == -1)
        {
            continue;
        }

        const csmFloat32 sourceValue = model->GetParameterValue(parameterIndex);

        // Evaluate curve and apply value.
        value = EvaluateCurve(_motionData, c, time);

        if (eyeBlinkValue != FLT_MAX)
        {
            for (csmUint32 i = 0; i < _eyeBlinkParameterIds.GetSize() && i < MaxTargetSize; ++i)
            {
                if (_eyeBlinkParameterIds[i] == curves[c].Id)
                {
                    value *= eyeBlinkValue;
                    eyeBlinkFlags |= 1ULL << i;
                    break;
                }
            }
        }

        if (lipSyncValue != FLT_MAX)
        {
            for (csmUint32 i = 0; i < _lipSyncParameterIds.GetSize() && i < MaxTargetSize; ++i)
            {
                if (_lipSyncParameterIds[i] == curves[c].Id)
                {
                    value += lipSyncValue;
                    lipSyncFlags |= 1ULL << i;
                    break;
                }
            }
        }

        csmFloat32 v;
        // パラメータごとのフェード
        if (curves[c].FadeInTime < 0.0f && curves[c].FadeOutTime < 0.0f)
        {
            //モーションのフェードを適用
            v = sourceValue + (value - sourceValue) * fadeWeight;
        }
        else
        {
            // パラメータに対してフェードインかフェードアウトが設定してある場合はそちらを適用
            csmFloat32 fin;
            csmFloat32 fout;

            if (curves[c].FadeInTime < 0.0f)
            {
                fin = tmpFadeIn;
            }
            else
            {
                fin = curves[c].FadeInTime == 0.0f
                            ? 1.0f
                        : CubismMath::GetEasingSine((userTimeSeconds - motionQueueEntry->GetFadeInStartTime()) / curves[c].FadeInTime);
            }

            if (curves[c].FadeOutTime < 0.0f)
            {
                fout = tmpFadeOut;
            }
            else
            {
                fout = (curves[c].FadeOutTime == 0.0f || motionQueueEntry->GetEndTime() < 0.0f)
                            ? 1.0f
                        : CubismMath::GetEasingSine((motionQueueEntry->GetEndTime() - userTimeSeconds) / curves[c].FadeOutTime );
            }

            const csmFloat32 paramWeight = _weight * fin * fout;

            // パラメータごとのフェードを適用
            v = sourceValue + (value - sourceValue) * paramWeight;
        }

        model->SetParameterValue(parameterIndex, v);
    }

    {
        if (eyeBlinkValue != FLT_MAX)
        {
            for (csmUint32 i = 0; i < _eyeBlinkParameterIds.GetSize() && i < MaxTargetSize; ++i)
            {
                const csmFloat32 sourceValue = model->GetParameterValue(_eyeBlinkParameterIds[i]);
                //モーションでの上書きがあった時にはまばたきは適用しない
                if ((eyeBlinkFlags >> i) & 0x01)
                {
                    continue;
                }

                const csmFloat32 v = sourceValue + (eyeBlinkValue - sourceValue) * fadeWeight;

                model->SetParameterValue(_eyeBlinkParameterIds[i], v);
            }
        }

        if (lipSyncValue != FLT_MAX)
        {
            for (csmUint32 i = 0; i < _lipSyncParameterIds.GetSize() && i < MaxTargetSize; ++i)
            {
                const csmFloat32 sourceValue = model->GetParameterValue(_lipSyncParameterIds[i]);
                //モーションでの上書きがあった時にはリップシンクは適用しない
                if ((lipSyncFlags >> i) & 0x01)
                {
                    continue;
                }

                const csmFloat32 v = sourceValue + (lipSyncValue - sourceValue) * fadeWeight;

                model->SetParameterValue(_lipSyncParameterIds[i], v);
            }
        }
    }

    for (; c < _motionData->CurveCount && curves[c].Type == CubismMotionCurveTarget_PartOpacity; ++c)
    {
        // Find parameter index.
        parameterIndex = model->GetParameterIndex(curves[c].Id);

        // Skip curve evaluation if no value in sink.
        if (parameterIndex == -1)
        {
            continue;
        }

        // Evaluate curve and apply value.
        value = EvaluateCurve(_motionData, c, time);

        model->SetParameterValue(parameterIndex, value);
    }

    if (timeOffsetSeconds >= _motionData->Duration)
    {
        if (_isLoop)
        {
            motionQueueEntry->SetStartTime(userTimeSeconds); //最初の状態へ
            if (_isLoopFadeIn)
            {
                //ループ中でループ用フェードインが有効のときは、フェードイン設定し直し
                motionQueueEntry->SetFadeInStartTime(userTimeSeconds);
            }
        }
        else
        {
            motionQueueEntry->IsFinished(true);
        }
    }

    _lastWeight = fadeWeight;
}

void CubismMotion::Parse(const csmByte* motionJson, const csmSizeInt size)
{
    _motionData = CSM_NEW CubismMotionData;

    CubismMotionJson* json = CSM_NEW CubismMotionJson(motionJson, size);

    _motionData->Duration = json->GetMotionDuration();
    _motionData->Loop = json->IsMotionLoop();
    _motionData->CurveCount = json->GetMotionCurveCount();
    _motionData->Fps = json->GetMotionFps();
    _motionData->EventCount = json->GetEventCount();

    if (json->IsExistMotionFadeInTime())
    {
        _fadeInSeconds = (json->GetMotionFadeInTime() < 0.0f)
                             ? 1.0f
                             : json->GetMotionFadeInTime();
    }
    else
    {
        _fadeInSeconds = 1.0f;
    }

    if (json->IsExistMotionFadeOutTime())
    {
        _fadeOutSeconds = (json->GetMotionFadeOutTime() < 0.0f)
                              ? 1.0f
                              : json->GetMotionFadeOutTime();
    }
    else
    {
        _fadeOutSeconds = 1.0f;
    }

    _motionData->Curves.UpdateSize(_motionData->CurveCount, CubismMotionCurve(), true);
    _motionData->Segments.UpdateSize(json->GetMotionTotalSegmentCount(), CubismMotionSegment(), true);
    _motionData->Points.UpdateSize(json->GetMotionTotalPointCount(), CubismMotionPoint(), true);
    _motionData->Events.UpdateSize(_motionData->EventCount, CubismMotionEvent(), true);

    csmInt32 totalPointCount = 0;
    csmInt32 totalSegmentCount = 0;

    // Curves
    for (csmInt32 curveCount = 0; curveCount < _motionData->CurveCount; ++curveCount)
    {
        if (strcmp(json->GetMotionCurveTarget(curveCount), TargetNameModel) == 0)
        {
            _motionData->Curves[curveCount].Type = CubismMotionCurveTarget_Model;
        }
        else if (strcmp(json->GetMotionCurveTarget(curveCount), TargetNameParameter) == 0)
        {
            _motionData->Curves[curveCount].Type = CubismMotionCurveTarget_Parameter;
        }
        else if (strcmp(json->GetMotionCurveTarget(curveCount), TargetNamePartOpacity) == 0)
        {
            _motionData->Curves[curveCount].Type = CubismMotionCurveTarget_PartOpacity;
        }

        _motionData->Curves[curveCount].Id = json->GetMotionCurveId(curveCount);

        _motionData->Curves[curveCount].BaseSegmentIndex = totalSegmentCount;

        _motionData->Curves[curveCount].FadeInTime =
                (json->IsExistMotionCurveFadeInTime(curveCount))
                    ? json->GetMotionCurveFadeInTime(curveCount)
                    : -1.0f ;
        _motionData->Curves[curveCount].FadeOutTime =
                (json->IsExistMotionCurveFadeOutTime(curveCount))
                    ? json->GetMotionCurveFadeOutTime(curveCount)
                    : -1.0f;

        // Segments
        for (csmInt32 segmentPosition = 0; segmentPosition < json->GetMotionCurveSegmentCount(curveCount);)
        {
            if (segmentPosition == 0)
            {
                _motionData->Segments[totalSegmentCount].BasePointIndex = totalPointCount;

                _motionData->Points[totalPointCount].Time = json->GetMotionCurveSegment(curveCount, segmentPosition);
                _motionData->Points[totalPointCount].Value = json->GetMotionCurveSegment(curveCount, segmentPosition + 1);

                totalPointCount += 1;
                segmentPosition += 2;
            }
            else
            {
                _motionData->Segments[totalSegmentCount].BasePointIndex = totalPointCount - 1;
            }

            const csmInt32 segment = json->GetMotionCurveSegment(curveCount, segmentPosition);

            switch (segment)
            {
            case CubismMotionSegmentType_Linear: {
                _motionData->Segments[totalSegmentCount].SegmentType = CubismMotionSegmentType_Linear;
                _motionData->Segments[totalSegmentCount].Evaluate = LinearEvaluate;

                _motionData->Points[totalPointCount].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 1));
                _motionData->Points[totalPointCount].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 2));

                totalPointCount += 1;
                segmentPosition += 3;

                break;
            }
            case CubismMotionSegmentType_Bezier: {
                _motionData->Segments[totalSegmentCount].SegmentType = CubismMotionSegmentType_Bezier;
                _motionData->Segments[totalSegmentCount].Evaluate = BezierEvaluate;

                _motionData->Points[totalPointCount].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 1));
                _motionData->Points[totalPointCount].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 2));

                _motionData->Points[totalPointCount + 1].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 3));
                _motionData->Points[totalPointCount + 1].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 4));

                _motionData->Points[totalPointCount + 2].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 5));
                _motionData->Points[totalPointCount + 2].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 6));

                totalPointCount += 3;
                segmentPosition += 7;

                break;
            }
            case CubismMotionSegmentType_Stepped: {
                _motionData->Segments[totalSegmentCount].SegmentType = CubismMotionSegmentType_Stepped;
                _motionData->Segments[totalSegmentCount].Evaluate = SteppedEvaluate;

                _motionData->Points[totalPointCount].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 1));
                _motionData->Points[totalPointCount].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 2));

                totalPointCount += 1;
                segmentPosition += 3;

                break;
            }
            case CubismMotionSegmentType_InverseStepped: {
                _motionData->Segments[totalSegmentCount].SegmentType = CubismMotionSegmentType_InverseStepped;
                _motionData->Segments[totalSegmentCount].Evaluate = InverseSteppedEvaluate;

                _motionData->Points[totalPointCount].Time = json->GetMotionCurveSegment(curveCount, (segmentPosition + 1));
                _motionData->Points[totalPointCount].Value = json->GetMotionCurveSegment(curveCount, (segmentPosition + 2));

                totalPointCount += 1;
                segmentPosition += 3;

                break;
            }
            default: {
                CSM_ASSERT(0);
                break;
            }
            }

            ++_motionData->Curves[curveCount].SegmentCount;
            ++totalSegmentCount;
        }
    }


    for (csmInt32 userdatacount = 0; userdatacount < json->GetEventCount(); ++userdatacount)
    {
        _motionData->Events[userdatacount].FireTime = json->GetEventTime(userdatacount);
        _motionData->Events[userdatacount].Value = json->GetEventValue(userdatacount);
    }

    CSM_DELETE(json);
}

void CubismMotion::SetParameterFadeInTime(CubismIdHandle parameterId, csmFloat32 value)
{
    csmVector<CubismMotionCurve>& curves = _motionData->Curves;

    for (csmInt16 i = 0; i < _motionData->CurveCount; ++i)
    {
        if (parameterId == curves[i].Id)
        {
            curves[i].FadeInTime = value;
            return;
        }
    }
}

void CubismMotion::SetParameterFadeOutTime(CubismIdHandle parameterId, csmFloat32 value)
{
    csmVector<CubismMotionCurve>& curves = _motionData->Curves;

    for (csmInt16 i = 0; i < _motionData->CurveCount; ++i)
    {
        if (parameterId == curves[i].Id)
        {
            curves[i].FadeOutTime = value;
            return;
        }
    }
}

csmFloat32 CubismMotion::GetParameterFadeInTime(CubismIdHandle parameterId) const
{
    csmVector<CubismMotionCurve>& curves = _motionData->Curves;

    for (csmInt16 i = 0; i < _motionData->CurveCount; ++i)
    {
        if (parameterId == curves[i].Id)
        {
            return curves[i].FadeInTime;
        }
    }

    return -1;
}

csmFloat32 CubismMotion::GetParameterFadeOutTime(CubismIdHandle parameterId) const
{
    csmVector<CubismMotionCurve>& curves = _motionData->Curves;

    for (csmInt16 i = 0; i < _motionData->CurveCount; ++i)
    {
        if (parameterId == curves[i].Id)
        {
            return curves[i].FadeOutTime;
        }
    }

    return -1;
}

void CubismMotion::IsLoop(csmBool loop)
{
    this->_isLoop = loop;
}

csmBool CubismMotion::IsLoop() const
{
    return this->_isLoop;
}

void CubismMotion::IsLoopFadeIn(csmBool loopFadeIn)
{
    this->_isLoopFadeIn = loopFadeIn;
}

csmBool CubismMotion::IsLoopFadeIn() const
{
    return this->_isLoopFadeIn;
}

csmFloat32 CubismMotion::GetLoopDuration()
{
    return _loopDurationSeconds;
}

void CubismMotion::SetEffectIds(const csmVector<CubismIdHandle>& eyeBlinkParameterIds, const csmVector<CubismIdHandle>& lipSyncParameterIds)
{
    _eyeBlinkParameterIds = eyeBlinkParameterIds;
    _lipSyncParameterIds = lipSyncParameterIds;
}

const csmVector<const csmString*>& CubismMotion::GetFiredEvent(csmFloat32 beforeCheckTimeSeconds, csmFloat32 motionTimeSeconds)
{
    _firedEventValues.UpdateSize(0);
    /// イベントの発火チェック
    for (csmInt32 u = 0; u < _motionData->EventCount; ++u)
    {
        if ((_motionData->Events[u].FireTime >beforeCheckTimeSeconds) &&
            (_motionData->Events[u].FireTime <= motionTimeSeconds))
        {
            _firedEventValues.PushBack(&_motionData->Events[u].Value);
        }
    }

    return _firedEventValues;
}

}}}
