#ifndef SERIALIZE_BASE_HPP_
#define SERIALIZE_BASE_HPP_

#include <vector>
#include <cstdint>
#include <cstddef>

template<typename TSerializable>
struct SerialCodec
{
    /**
     * @brief Encode the object.
     * 
     * @param outBytes The vector into which bytes will be output.
     * @param obj Reference to the object.
     */
    static constexpr void encode(std::vector<uint8_t> &outBytes, const TSerializable &obj)
    {
        static_assert(TSerializable::does_not_exist, "SerialCodec::encode is not defined for this type.");
    }

    /**
     * @brief Decode this object from data.
     * 
     * @param obj The object to decode into.
     * @param data The data.
     * @param size The data size.
     * @return The size of the consumed data (0 meaning decoding failed).
     */
    static constexpr size_t decode(const uint8_t *data, size_t size, TSerializable &obj)
    {
        static_assert(TSerializable::does_not_exist, "SerialCodec::decode is not defined for this type.");
        return 0;
    }
};

static constexpr void serializeInto(std::vector<uint8_t> &outBytes) {}

/**
 * @brief Serialize an object according to supported SerialCodecs.
 * 
 * Appends the serialized data into `outBytes`.
 */
template<typename TSerializable, typename... TArgs>
void serializeInto(std::vector<uint8_t> &outBytes, const TSerializable &obj, TArgs& ...args)
{
    SerialCodec<TSerializable>::encode(outBytes, obj);
    serializeInto(outBytes, args...);
}

/**
 * @brief Serialize the specified objects into a vector of bytes, according to supported SerialCodecs.
 */
template<typename... TArgs>
std::vector<uint8_t> serialize(TArgs& ...args)
{
    std::vector<uint8_t> output;
    serializeInto(output, args...);
    return output;
}

static constexpr size_t deserialize(const uint8_t *data, size_t size)
{
    return 0;
}

/**
 * @brief Decode a serializable object.
 * 
 * Updates `obj` with the decoded object if successful. Returns 0 on error, or
 * the number of consumed bytes on success.
 */
template<typename TSerializable, typename... TArgs>
size_t deserialize(const uint8_t *data, size_t size, TSerializable &obj, TArgs&... args)
{
    size_t sizeA = SerialCodec<TSerializable>::decode(data, size, obj);
    if (sizeA == 0)
    {
        return 0;
    }

    if (sizeA > size)
    {
        return 0;
    }

    size_t sizeB = deserialize(data+sizeA, size-sizeA, args...);
    if (sizeB == 0 && sizeof...(args) != 0)
    {
        return 0;
    }

    return sizeA + sizeB;
}

#endif