#pragma once

#include <cstring>
#include <cstdint>
#include <vector>


namespace drp::util::serialize {


/**
 * Serialize a basic object to a vector of bytes
 * @tparam Type the type of the object.
 * @param object the object to serialize.
 * @return the object as a vector of bytes.
 */
template<typename Type>
std::vector<std::uint8_t> serializeObject(const Type& object) {
    // create a vector with enough space for the object
    std::vector<std::uint8_t> buffer(sizeof(Type));
    // copy the object data to the buffer
    std::memcpy(buffer.data(), &object, sizeof(Type));

    return buffer;
}


/**
 * Deserialize a vector of bytes into an object.
 * @warning the data used in parameter will be stripped of the data used to deserialize the object.
 * @tparam Type the type of the object
 * @param data the data of the object
 * @return the object
 */
template<typename Type>
Type deserializeObject(std::vector<std::uint8_t>& data) {
    // create an object based on the data
    Type object;
    std::memcpy(&object, data.data(), sizeof(Type));

    // remove the space used by the object in the data
    data.erase(data.begin(), data.begin() + sizeof(Type));

    return object;
}


/**
 * Serialize a vector of anything to a vector of bytes.
 * @tparam Type the type of the data contained in the vector.
 * @tparam SizeType the range of the size of the vector.
 * @param object the vector to serialize.
 * @return the vector data as a vector of bytes.
 */
template<typename Type, typename SizeType = std::uint16_t>
std::vector<std::uint8_t> serializeVector(const std::vector<Type>& object) {
    // create a vector with enough size for the size and the data of the vector
    std::vector<std::uint8_t> buffer(sizeof(SizeType) + object.size() * sizeof(Type));

    // save the size of the vector
    auto size = static_cast<SizeType>(object.size());
    std::memcpy(buffer.data(), &size, sizeof(SizeType));
    // save the content of the vector
    std::memcpy(buffer.data() + sizeof(SizeType), object.data(), object.size() * sizeof(Type));

    return buffer;
}


/**
 * Deserialize a vector of bytes into a vector of object.
 * @warning the data used in parameter will be stripped of the data used to deserialize the object.
 * @tparam Type the type of the object
 * @tparam SizeType the type of the size of the vector
 * @param data the data in the vector
 * @return the vector of object
 */
template<typename Type, typename SizeType = std::uint16_t>
std::vector<Type> deserializeVector(std::vector<std::uint8_t>& data) {
    // get the size of the vector
    SizeType size;
    std::memcpy(&size, data.data(), sizeof(SizeType));
    // create a vector with enough size of the data
    std::vector<Type> object(size);

    // restore the data into the object
    std::memcpy(object.data(), data.data() + sizeof(SizeType), size * sizeof(Type));

    // remove the data used for the deserialization from the source
    data.erase(data.begin(), data.begin() + sizeof(SizeType) + size * sizeof(Type));

    return object;
}


}
