#ifndef DENSE_ARRAY_HPP_
#define DENSE_ARRAY_HPP_

#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <vector>
#include <Serialize/Serialize.hpp>

/**
 * @brief An array of unsigned integers of a specific arbitrary bit width.
 */
class DenseArray
{
private:
    uint8_t bitsPerEntry;
    uint32_t numEntries;
    uint8_t *bytes = nullptr;
    size_t numBytes;

    uint32_t extractByte(size_t bitpos) const;

public:
    DenseArray(uint8_t bitsPerEntry = 0, uint32_t numEntries = 0);
    DenseArray(const DenseArray &other);
    DenseArray(DenseArray &&other);
    ~DenseArray();

    DenseArray& operator=(const DenseArray& other);
    DenseArray& operator=(DenseArray&& other);

    void clear(uint8_t bitsPerEntry = 0, uint32_t numEntries = 0);
    uint32_t get(size_t index) const;
    void set(size_t index, uint32_t value);
    uint8_t getBitsPerEntry() const;
    uint32_t getNumEntries() const;
    void transform(size_t newBitsPerEntry, std::function<uint32_t(uint32_t)> transformFn);
    void push(uint32_t value);
    std::optional<size_t> find(uint32_t value) const;

    bool operator==(const DenseArray &other) const;

    friend class SerialCodec<DenseArray>;
};

template<>
struct SerialCodec<DenseArray>
{
    static void encode(std::vector<uint8_t> &outBytes, const DenseArray &array)
    {
        serializeInto(outBytes, array.bitsPerEntry, array.numEntries);
        outBytes.insert(outBytes.end(), array.bytes, array.bytes + array.numBytes);
    }

    static size_t decode(const uint8_t *data, size_t size, DenseArray &array)
    {
        uint8_t bitsPerEntry;
        uint32_t numEntries;
        size_t headerSize = deserialize(data, size, bitsPerEntry, numEntries);

        if (headerSize == 0)
        {
            return 0;
        }

        data += headerSize;
        size -= headerSize;

        array.clear(bitsPerEntry, numEntries);

        if (size < array.numBytes)
        {
            return 0;
        }

        std::memcpy(array.bytes, data, array.numBytes);

        return headerSize + array.numBytes;
    }
};

#endif