#include "variant.hpp"

#include <cassert>
#include <stdexcept>

#include "esmreader.hpp"
#include "variantimp.hpp"

#include "components/esm/defs.hpp"

namespace ESM
{
    namespace
    {
        constexpr uint32_t STRV = fourCC("STRV");
        constexpr uint32_t INTV = fourCC("INTV");
        constexpr uint32_t FLTV = fourCC("FLTV");
        constexpr uint32_t STTV = fourCC("STTV");

        template <typename T, bool orDefault = false>
        struct GetValue
        {
            constexpr T operator()(int value) const { return static_cast<T>(value); }

            constexpr T operator()(float value) const { return static_cast<T>(value); }

            template <typename V>
            constexpr T operator()(const V&) const
            {
                if constexpr (orDefault)
                    return T{};
                else
                    throw std::runtime_error("cannot convert variant");
            }
        };

        template <typename T>
        struct SetValue
        {
            T mValue;

            explicit SetValue(T value)
                : mValue(value)
            {
            }

            void operator()(int& value) const { value = static_cast<int>(mValue); }

            void operator()(float& value) const { value = static_cast<float>(mValue); }

            template <typename V>
            void operator()(V&) const
            {
                throw std::runtime_error("cannot convert variant");
            }
        };
    }

    const std::string& Variant::getString() const
    {
        return std::get<std::string>(mData);
    }

    int Variant::getInteger() const
    {
        return std::visit(GetValue<int>{}, mData);
    }

    float Variant::getFloat() const
    {
        return std::visit(GetValue<float>{}, mData);
    }

    void Variant::read(ESMReader& esm, Format format)
    {
        // type
        VarType type = VT_Unknown;

        if (format == Format_Global)
        {
            std::string typeId = esm.getHNString("FNAM");

            if (typeId == "s")
                type = VT_Short;
            else if (typeId == "l")
                type = VT_Long;
            else if (typeId == "f")
                type = VT_Float;
            else
                esm.fail("illegal global variable type " + typeId);
        }
        else if (format == Format_Gmst)
        {
            if (!esm.hasMoreSubs())
            {
                type = VT_None;
            }
            else
            {
                esm.getSubName();
                NAME name = esm.retSubName();

                if (name == STRV)
                {
                    type = VT_String;
                }
                else if (name == INTV)
                {
                    type = VT_Int;
                }
                else if (name == FLTV)
                {
                    type = VT_Float;
                }
                else
                    esm.fail("invalid subrecord: " + name.toString());
            }
        }
        else if (format == Format_Info)
        {
            esm.getSubName();
            NAME name = esm.retSubName();

            if (name == INTV)
            {
                type = VT_Int;
            }
            else if (name == FLTV)
            {
                type = VT_Float;
            }
            else
                esm.fail("invalid subrecord: " + name.toString());
        }
        else if (format == Format_Local)
        {
            esm.getSubName();
            NAME name = esm.retSubName();

            if (name == INTV)
            {
                type = VT_Int;
            }
            else if (name == FLTV)
            {
                type = VT_Float;
            }
            else if (name == STTV)
            {
                type = VT_Short;
            }
            else
                esm.fail("invalid subrecord: " + name.toString());
        }

        setType(type);

        std::visit(ReadESMVariantValue{ esm, format, mType }, mData);
    }

    void Variant::write(ESMWriter& esm, Format format) const
    {
        if (mType == VT_Unknown)
        {
            throw std::runtime_error("can not serialise variant of unknown type");
        }
        else if (mType == VT_None)
        {
            if (format == Format_Global)
                throw std::runtime_error("can not serialise variant of type none to global format");

            if (format == Format_Info)
                throw std::runtime_error("can not serialise variant of type none to info format");

            if (format == Format_Local)
                throw std::runtime_error("can not serialise variant of type none to local format");

            // nothing to do here for GMST format
        }
        else
            std::visit(WriteESMVariantValue{ esm, format, mType }, mData);
    }

    void Variant::write(std::ostream& stream) const
    {
        switch (mType)
        {
            case VT_Unknown:

                stream << "variant unknown";
                break;

            case VT_None:

                stream << "variant none";
                break;

            case VT_Short:

                stream << "variant short: " << std::get<int>(mData);
                break;

            case VT_Int:

                stream << "variant int: " << std::get<int>(mData);
                break;

            case VT_Long:

                stream << "variant long: " << std::get<int>(mData);
                break;

            case VT_Float:

                stream << "variant float: " << std::get<float>(mData);
                break;

            case VT_String:

                stream << "variant string: \"" << std::get<std::string>(mData) << "\"";
                break;
        }
    }

    void Variant::setType(VarType type)
    {
        if (type != mType)
        {
            switch (type)
            {
                case VT_Unknown:
                case VT_None:
                    mData = std::monostate{};
                    break;

                case VT_Short:
                case VT_Int:
                case VT_Long:
                    mData = std::visit(GetValue<int, true>{}, mData);
                    break;

                case VT_Float:
                    mData = std::visit(GetValue<float, true>{}, mData);
                    break;

                case VT_String:
                    mData = std::string{};
                    break;
            }

            mType = type;
        }
    }

    void Variant::setString(const std::string& value)
    {
        std::get<std::string>(mData) = value;
    }

    void Variant::setString(std::string&& value)
    {
        std::get<std::string>(mData) = std::move(value);
    }

    void Variant::setInteger(int value)
    {
        std::visit(SetValue(value), mData);
    }

    void Variant::setFloat(float value)
    {
        std::visit(SetValue(value), mData);
    }

    std::ostream& operator<<(std::ostream& stream, const Variant& value)
    {
        value.write(stream);
        return stream;
    }

}