#include "CMayaSpline.h"
#include <Common/Math/MathUtil.h>

void ValidateTangent(CVector2f& Tangent)
{
    if (Tangent.X < 0.f) Tangent.X = 0.f;
    Tangent = Tangent.Normalized();

    if (Tangent.X == 0.f && Tangent.Y != 0.f)
    {
        float Mul = (Tangent.Y >= 0.f ? 1.f : -1.f);
        Tangent.X = 0.0001f;
        Tangent.Y = 5729578 * Tangent.X * Mul; // not sure where that number comes from!
    }
}

void CMayaSplineKnot::GetTangents(const CMayaSplineKnot* pkPrev, const CMayaSplineKnot* pkNext, CVector2f& OutTangentA, CVector2f& OutTangentB) const
{
    if (Flags & 0x8000)
        CalculateTangents(pkPrev, pkNext);

    OutTangentA = CachedTangentA;
    OutTangentB = CachedTangentB;
}

void CMayaSplineKnot::CalculateTangents(const CMayaSplineKnot* pkPrev, const CMayaSplineKnot* pkNext) const
{
    // todo: this function is incomplete
    Flags &= ~0x8000;

    if ((Flags >> 24) == 4 && pkPrev)
    {
        float PrevAmpDiff = Math::Abs(pkPrev->Amplitude - Amplitude);
        float NextAmpDiff = (pkNext ? Math::Abs(pkNext->Amplitude - Amplitude) : PrevAmpDiff);

        if (PrevAmpDiff > 0.05f && NextAmpDiff <= 0.05f)
        {
            Flags &= 0x00FFFFFF;
            Flags |= 0x01000000;
        }
    }

    uint32 TopFlagByte = (Flags >> 24) & 0xFF;

    if (TopFlagByte == 0)
    {
    }
}

uint CMayaSpline::GetKnotCount() const
{
    return mKnots.size();
}

const std::vector<CMayaSplineKnot>& CMayaSpline::GetKnots() const
{
    return mKnots;
}

float CMayaSpline::GetMinTime() const
{
    return (mKnots.empty() ? 0.f : mKnots.front().Time);
}

float CMayaSpline::GetMaxTime() const
{
    return (mKnots.empty() ? 0.f : mKnots.back().Time);
}

float CMayaSpline::GetDuration() const
{
    if (mKnots.empty()) return 0.f;
    else return GetMaxTime() - mKnots.front().Time;
}

float CMayaSpline::EvaluateAt(float Time) const
{
    float Amplitude = EvaluateAtUnclamped(Time);

    if (mClampMode == 0)
    {
        return Amplitude;
    }
    else if (mClampMode == 1)
    {
        return Math::Clamp(mMinAmplitude, mMaxAmplitude, Amplitude);
    }
    else if (mClampMode == 2)
    {
        if (mMaxAmplitude <= mMinAmplitude)
            return mMinAmplitude;

        // todo
        return 0.f;
    }
    else
    {
        return 0.f;
    }
}

float CMayaSpline::EvaluateAtUnclamped(float Time) const
{
    if (mKnots.size() == 0)
        return 0.f;

    // Check for infinity
    if (Time < mKnots.front().Time)
    {
        if (mPreInfinity == 0)
            return mKnots.front().Time;
        else
            return EvaluateInfinities(Time, true);
    }

    if (Time > mKnots.back().Time)
    {
        if (mPostInfinity == 0)
            return mKnots.back().Time;
        else
            return EvaluateInfinities(Time, false);
    }

    // Check for valid cached knot index
    int LastKnotIndex = mKnots.size() - 1;
    int KnotIndex = -1;
    bool ValidKnot = false;

    if (mCachedKnotIndex != -1)
    {
        // WTF second check? I've read the code like 10 times and I don't see any other way to interpret it, this is what the game does
        if ((mCachedKnotIndex < LastKnotIndex) && (Time > mKnots.back().Time))
        {
            // You will notice we already did that check earlier, so this code can't execute...
            float KnotTime = mKnots[mCachedKnotIndex + 1].Time;

            if (Time == KnotTime)
            {
                mCachedKnotIndex = LastKnotIndex;
                return mKnots.back().Amplitude;
            }

            else if (Time < KnotTime)
            {
                KnotIndex = mCachedKnotIndex + 1;
                ValidKnot = true;
            }
        }

        else if (mCachedKnotIndex > 0)
        {
            float KnotTime = mKnots[mCachedKnotIndex].Time;

            if (Time < KnotTime)
            {
                KnotTime = mKnots[mCachedKnotIndex - 1].Time;

                if (Time > KnotTime)
                {
                    KnotIndex = mCachedKnotIndex - 1;
                    ValidKnot = true;
                }

                if (Time == KnotTime)
                {
                    mCachedKnotIndex--;
                    return mKnots[mCachedKnotIndex].Amplitude;
                }
            }
        }
    }

    // Find new knot index if needed
    if (!ValidKnot)
    {
        bool ExactMatch = FindKnot(Time, KnotIndex);

        if (ExactMatch)
        {
            if (KnotIndex == 0)
            {
                mCachedKnotIndex = KnotIndex;
                return mKnots[KnotIndex].Amplitude;
            }

            else if (KnotIndex == mKnots.size())
            {
                mCachedKnotIndex = 0;
                return mKnots.back().Amplitude;
            }
        }
    }

    // Update Hermite coefficients
    int PrevKnot = KnotIndex - 1;

    if (mUnknown1 != PrevKnot)
    {
        mCachedKnotIndex = PrevKnot;
        mUnknown1 = PrevKnot;

        if ( ((mKnots[PrevKnot].Flags >> 16) & 0xFF) == 3)
        {
            mDirtyFlags |= 0x80;
        }

        else
        {
            mDirtyFlags &= ~0x80;

            std::vector<CVector2f> ControlPoints;
            FindControlPoints(PrevKnot, ControlPoints);
            CalculateHermiteCoefficients(ControlPoints, mCachedHermiteCoefficients);
            mCachedMinTime = ControlPoints.front().X;
        }
    }

    // Evaluate Hermite
    if (mDirtyFlags & 0x80)
        return mKnots[mCachedKnotIndex].Amplitude;
    else
        return EvaluateHermite(Time);
}

float CMayaSpline::EvaluateInfinities(float /*Time*/, bool Pre) const
{
    // todo - return Constant for now!
    if (Pre)
        return mKnots.front().Amplitude;
    else
        return mKnots.back().Amplitude;
}

float CMayaSpline::EvaluateHermite(float Time) const
{
    // todo: better organization and more descriptive variable names
    float f4 = Time - mCachedMinTime;
    float f3 = f4 * mCachedHermiteCoefficients[0];
    float f2 = mCachedHermiteCoefficients[1] + f3;
    f2 = f4 * f2;
    float f1 = mCachedHermiteCoefficients[2] + f2;
    f1 = f4 * f1;
    f1 = mCachedHermiteCoefficients[3] + f1;
    return f1;
}

bool CMayaSpline::FindKnot(float Time, int& OutKnotIndex) const
{
    // Stores the index of the closest knot to Time (without going over).
    // Returns whether or not the knot found was an exact match.
    OutKnotIndex = 0;

    if (mKnots.empty())
        return false;

    uint Lower = 0;
    uint Upper = mKnots.size();

    while (Lower < Upper)
    {
        uint Index = (Lower + Upper) >> 1;

        if (mKnots[Index].Time > Time)
            Lower = Index + 1;
        else if (mKnots[Index].Time < Time)
            Upper = Index - 1;

        else
        {
            OutKnotIndex = Index;
            return true;
        }
    }

    OutKnotIndex = Lower;
    return false;
}

void CMayaSpline::FindControlPoints(int KnotIdx, std::vector<CVector2f>& Out) const
{
        const CMayaSplineKnot* pkKnot = &mKnots[KnotIdx];
        CVector2f KnotPos(pkKnot->Time, pkKnot->Amplitude);
        Out.push_back(KnotPos);

        CVector2f TangentA(0,0), TangentB(0,0);
        const CMayaSplineKnot* pkNext = (KnotIdx < mKnots.size() ? &mKnots[KnotIdx + 1] : nullptr);
        const CMayaSplineKnot* pkPrev = (KnotIdx > 0 ? &mKnots[KnotIdx - 1] : nullptr);
        pkKnot->GetTangents(pkPrev, pkNext, TangentA, TangentB);

        Out.push_back(KnotPos + (TangentB * 0.333333f));

        // The game doesn't check whether the next knot exists before executing this code, not sure why...
        KnotIdx++;
        pkKnot = pkNext;
        KnotPos = CVector2f(pkNext->Time, pkNext->Amplitude);

        pkNext = (KnotIdx < mKnots.size() ? &mKnots[KnotIdx + 1] : nullptr);
        pkPrev = (KnotIdx > 0 ? &mKnots[KnotIdx - 1] : nullptr);
        pkKnot->GetTangents(pkPrev, pkNext, TangentA, TangentB);

        Out.push_back(KnotPos - (TangentA * 0.333333f));
        Out.push_back(KnotPos);
}

void CMayaSpline::CalculateHermiteCoefficients(const std::vector<CVector2f>& kControlPoints, float* pOutCoefs) const
{
    // rkControlPoints should contain 4 elements.
    const CVector2f& kKnotA = kControlPoints[0];
    const CVector2f& kTangentA = kControlPoints[1];
    const CVector2f& kTangentB = kControlPoints[2];
    const CVector2f& kKnotB = kControlPoints[3];

    CVector2f Range = kKnotB - kKnotA;

    CVector2f KnotAToTangentA = kTangentA - kKnotA;
    float MulA = (KnotAToTangentA.X == 0 ? 5729578.f : KnotAToTangentA.Y / KnotAToTangentA.X);

    CVector2f KnotBToTangentB = kKnotB - kTangentB;
    float MulB = (KnotBToTangentB.X == 0 ? 5729578.f : KnotBToTangentB.Y / KnotBToTangentB.X);

#if 0
    // todo: better organization and better variable names
    // also this doesn't actually compile, oops! will need to be re-reverse engineered
    float f3 = Range.X * Range.X;
    float f1 = 1.0f;
    float f0 = Range.Y * 2;
    float f5 = MulA * Range.X;
    f3 = 1.f / f3;
    float f6 = MulB * Range.X;
    f0 = Range.Y + f0;
    f1 = f5 + f6;
    f0 -= f5;
    f1 -= f2;
    f0 -= f5;
    f1 -= f2;
    f0 -= f6;
    f1 *= f3;
    f0 *= f3;
    f1 /= Range.X;

    pOutCoefs[0] = f1;
    pOutCoefs[1] = f0;
#else
    pOutCoefs[0] = 0.0f;
    pOutCoefs[1] = 0.0f;
#endif
    pOutCoefs[2] = MulA;
    pOutCoefs[3] = kKnotA.Y;
}
