/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "ByteTools.hpp"
#include "Structs/Vector.hpp"

namespace
{
    /// Utility wrapper to contain an integral constant
    /// representable in 3 bytes (allowed range is -8388607 to 8388607)
    class Int24_t
    {
        public:
            enum : std::int32_t { Limit = 8388607 };

            explicit Int24_t(std::int32_t integer)
            {
                if (integer < -Limit) m_integer = -Limit;
                else if (integer > Limit) m_integer = Limit;
                else m_integer = static_cast<std::int32_t>(integer);
            }

            std::uint32_t getOffset() const
            {
                return static_cast<std::uint32_t>(m_integer + Limit);
            }

            static std::int32_t fromOffset(std::uint32_t offset)
            {
                return static_cast<std::int32_t>(offset) - Limit;
            }

        private:
            std::int32_t m_integer;
    };

    /// The amount by which each float is multiplied/divided by
    /// before/after serialization into 3-byte signed integers
    constexpr float FLOAT_PRECISION = 64.f;
}

namespace ByteTools
{
    void writeInt(std::int32_t value, std::list<std::uint8_t> &dst)
    {
        writeUnsigned<std::uint32_t, 3>(Int24_t(value).getOffset(), dst);
    }

    bool readInt(std::list<std::uint8_t> &bytes, std::int32_t &dst)
    {
        std::uint32_t offset;
        if (readUnsigned<std::uint32_t, 3>(bytes, offset))
        {
            dst = Int24_t::fromOffset(offset);
            return true;
        }

        return false;
    }

    void writeFloat(float value, std::list<std::uint8_t> &dst)
    {
        constexpr float overflowLimit =
            static_cast<float>(Int24_t::Limit) / FLOAT_PRECISION;

        if (value < -overflowLimit) value = -overflowLimit;
        else if (value > overflowLimit) value = overflowLimit;

        writeInt(static_cast<std::int32_t>(value * FLOAT_PRECISION), dst);
    }

    bool readFloat(std::list<std::uint8_t> &bytes, float &dst)
    {
        std::int32_t integralRepresentation;
        if (readInt(bytes, integralRepresentation))
        {
            dst = static_cast<float>(integralRepresentation) / FLOAT_PRECISION;
            return true;
        }

        return false;
    }

    void writeVector(const Vector &value, std::list<std::uint8_t> &dst)
    {
        writeFloat(value.x, dst);
        writeFloat(value.y, dst);
    }

    bool readVector(std::list<std::uint8_t> &bytes, Vector &dst)
    {
        return (readFloat(bytes, dst.x) && readFloat(bytes, dst.y));
    }

    bool writeUtf8(const Utf8::utf8_string &value, std::list<std::uint8_t> &dst)
    {
        const std::string &strValue = value.get();
        if (strValue.size() > 255)
            return false;

        dst.push_back(static_cast<std::uint8_t>(strValue.size()));
        dst.insert(dst.end(), strValue.begin(), strValue.end());

        return true;
    }

    bool readUtf8(std::list<std::uint8_t> &bytes, Utf8::utf8_string &dst)
    {
        if (bytes.empty() || bytes.front() >= bytes.size())
            return false;

        std::uint8_t byteLength = bytes.front();
        bytes.pop_front();

        std::list<std::uint8_t>::iterator it_end =
            std::next(bytes.begin(), byteLength);

        //Extract the valid part of the string
        dst = Utf8::utf8_string(std::string(bytes.begin(), it_end));
        bytes.erase(bytes.begin(), it_end);

        return true;
    }

    bool writeString(const std::string &value, std::list<std::uint8_t> &dst)
    {
        if (value.length() > 255)
            return false;

        //Extract the valid part of the string
        return writeUtf8(value, dst);
    }

    bool readString(std::list<std::uint8_t> &bytes, std::string &dst)
    {
        Utf8::utf8_string read;
        if (!readUtf8(bytes, read))
            return false;

        dst = read.get();
        return true;
    }
}
