///File to handle keys used by nif file records

#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP
#define OPENMW_COMPONENTS_NIF_NIFKEY_HPP

#include "nifstream.hpp"

#include <sstream>
#include <map>

#include "niffile.hpp"

namespace Nif
{

enum InterpolationType
{
    InterpolationType_Unknown = 0,
    InterpolationType_Linear = 1,
    InterpolationType_Quadratic = 2,
    InterpolationType_TBC = 3,
    InterpolationType_XYZ = 4,
    InterpolationType_Constant = 5
};

template<typename T>
struct KeyT {
    T mValue;
    T mInTan; // Only for Quadratic interpolation, and never for QuaternionKeyList
    T mOutTan; // Only for Quadratic interpolation, and never for QuaternionKeyList

    // FIXME: Implement TBC interpolation
    /*
    float mTension;    // Only for TBC interpolation
    float mBias;       // Only for TBC interpolation
    float mContinuity; // Only for TBC interpolation
    */
};
using FloatKey = KeyT<float>;
using Vector3Key = KeyT<osg::Vec3f>;
using Vector4Key = KeyT<osg::Vec4f>;
using QuaternionKey = KeyT<osg::Quat>;

template<typename T, T (NIFStream::*getValue)()>
struct KeyMapT {
    using MapType = std::map<float, KeyT<T>>;

    using ValueType = T;
    using KeyType = KeyT<T>;

    unsigned int mInterpolationType = InterpolationType_Linear;
    MapType mKeys;

    //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
    void read(NIFStream *nif, bool force = false, bool morph = false)
    {
        assert(nif);

        mInterpolationType = InterpolationType_Unknown;

        if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106))
            nif->getString(); // Frame name

        size_t count = nif->getUInt();
        if (count == 0 && !force && !morph)
            return;

        if (morph && nif->getVersion() > NIFStream::generateVersion(10,1,0,0))
        {
            if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,104) &&
                nif->getVersion() <= NIFStream::generateVersion(20,1,0,2) && nif->getBethVersion() < 10)
                nif->getFloat(); // Legacy weight
            return;
        }

        mKeys.clear();

        mInterpolationType = nif->getUInt();

        KeyT<T> key;
        NIFStream &nifReference = *nif;

        if (mInterpolationType == InterpolationType_Linear
         || mInterpolationType == InterpolationType_Constant)
        {
            for(size_t i = 0;i < count;i++)
            {
                float time = nif->getFloat();
                readValue(nifReference, key);
                mKeys[time] = key;
            }
        }
        else if (mInterpolationType == InterpolationType_Quadratic)
        {
            for(size_t i = 0;i < count;i++)
            {
                float time = nif->getFloat();
                readQuadratic(nifReference, key);
                mKeys[time] = key;
            }
        }
        else if (mInterpolationType == InterpolationType_TBC)
        {
            for(size_t i = 0;i < count;i++)
            {
                float time = nif->getFloat();
                readTBC(nifReference, key);
                mKeys[time] = key;
            }
        }
        //XYZ keys aren't actually read here.
        //data.hpp sees that the last type read was InterpolationType_XYZ and:
        //    Eats a floating point number, then
        //    Re-runs the read function 3 more times.
        //        When it does that it's reading in a bunch of InterpolationType_Linear keys, not InterpolationType_XYZ.
        else if(mInterpolationType == InterpolationType_XYZ)
        {
            //Don't try to read XYZ keys into the wrong part
            if ( count != 1 )
            {
                std::stringstream error;
                error << "XYZ_ROTATION_KEY count should always be '1' .  Retrieved Value: "
                      << count;
                nif->file->fail(error.str());
            }
        }
        else if (mInterpolationType == InterpolationType_Unknown)
        {
            if (count != 0)
                nif->file->fail("Interpolation type 0 doesn't work with keys");
        }
        else
        {
            std::stringstream error;
            error << "Unhandled interpolation type: " << mInterpolationType;
            nif->file->fail(error.str());
        }
    }

private:
    static void readValue(NIFStream &nif, KeyT<T> &key)
    {
        key.mValue = (nif.*getValue)();
    }

    template <typename U>
    static void readQuadratic(NIFStream &nif, KeyT<U> &key)
    {
        readValue(nif, key);
        key.mInTan = (nif.*getValue)();
        key.mOutTan = (nif.*getValue)();
    }

    static void readQuadratic(NIFStream &nif, KeyT<osg::Quat> &key)
    {
        readValue(nif, key);
    }

    static void readTBC(NIFStream &nif, KeyT<T> &key)
    {
        readValue(nif, key);
        /*key.mTension = */nif.getFloat();
        /*key.mBias = */nif.getFloat();
        /*key.mContinuity = */nif.getFloat();
    }
};
using FloatKeyMap = KeyMapT<float,&NIFStream::getFloat>;
using Vector3KeyMap = KeyMapT<osg::Vec3f,&NIFStream::getVector3>;
using Vector4KeyMap = KeyMapT<osg::Vec4f,&NIFStream::getVector4>;
using QuaternionKeyMap = KeyMapT<osg::Quat,&NIFStream::getQuaternion>;
using ByteKeyMap = KeyMapT<char,&NIFStream::getChar>;

using FloatKeyMapPtr = std::shared_ptr<FloatKeyMap>;
using Vector3KeyMapPtr = std::shared_ptr<Vector3KeyMap>;
using Vector4KeyMapPtr = std::shared_ptr<Vector4KeyMap>;
using QuaternionKeyMapPtr = std::shared_ptr<QuaternionKeyMap>;
using ByteKeyMapPtr = std::shared_ptr<ByteKeyMap>;

} // Namespace
#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP