/*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/>.
*/

#ifndef DEF_BYTE_TOOLS
#define DEF_BYTE_TOOLS

#include "Tools/Utf8.hpp"
#include "Tools/Log.hpp"
#include <list>
#include <vector>
#include <string>
#include <type_traits>
#include <cstdint>
#include <cstddef>

struct Vector;

namespace ByteTools
{
    template <typename T, std::size_t N = sizeof(T)>
    void writeUnsigned(T value, std::list<std::uint8_t> &dst)
    {
        static_assert(std::is_unsigned<T>::value, "T must be unsigned");
        static_assert((N > 0 && N <= sizeof(T)), "Invalid number of bytes N");

        for (std::size_t i = 0; i < N; i++)
        {
            dst.push_back(static_cast<std::uint8_t>(value % 256));
            value /= 256;
        }
    }

    template <typename T, std::size_t N = sizeof(T)>
    bool readUnsigned(std::list<std::uint8_t> &bytes, T &dst)
    {
        static_assert(std::is_unsigned<T>::value, "T must be unsigned");
        static_assert((N > 0 && N <= sizeof(T)), "Invalid number of bytes N");

        if (bytes.size() < N)
            return false;

        T value = 0, exponentFactor = 1;
        for (std::size_t i = 0; i < N; i++)
        {
            value += exponentFactor * bytes.front();
            exponentFactor *= 256;
            bytes.pop_front();
        }

        dst = value;
        return true;
    }

    inline void writeUnsigned(std::uint8_t value, std::list<std::uint8_t> &dst)
    {
        dst.push_back(value);
    }

    inline bool readUnsigned(std::list<std::uint8_t> &bytes, std::uint8_t &dst)
    {
        if (bytes.empty())
            return false;

        dst = bytes.front();
        bytes.pop_front();

        return true;
    }

    void writeInt(std::int32_t value, std::list<std::uint8_t> &dst);
    bool readInt(std::list<std::uint8_t> &bytes, std::int32_t &dst);

    void writeFloat(float value, std::list<std::uint8_t> &dst);
    bool readFloat(std::list<std::uint8_t> &bytes, float &dst);

    void writeVector(const Vector &value, std::list<std::uint8_t> &dst);
    bool readVector(std::list<std::uint8_t> &bytes, Vector &dst);

    bool writeUtf8(const Utf8::utf8_string &value, std::list<std::uint8_t> &dst);
    bool readUtf8(std::list<std::uint8_t> &bytes, Utf8::utf8_string &dst);

    bool writeString(const std::string &value, std::list<std::uint8_t> &dst);
    bool readString(std::list<std::uint8_t> &bytes, std::string &dst);

    template <typename T>
    bool writeList(const std::vector<T> &values, std::list<std::uint8_t> &dst)
    {
        if (values.size() > 255)
        {
            Log::err("Could not write list: too many values");
            return false;
        }

        dst.push_back(static_cast<std::uint8_t>(values.size()));
        for (const T &value : values)
        {
            if (!value.writeToBuffer(dst))
                return false;
        }

        return true;
    }

    template <typename T>
    bool readList(std::list<std::uint8_t> &bytes, std::vector<T> &dst)
    {
        if (bytes.empty())
        {
            Log::err("Could not read list: empty message");
            return false;
        }

        dst.clear();

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

        for (std::uint8_t i = 0; i < valueSize; i++)
        {
            T value;
            if (!value.readFromBuffer(bytes))
                return false;

            dst.push_back(value);
        }

        return true;
    }
}

#endif
