#ifndef BAG_HPP_
#define BAG_HPP_

#include <Item/IBag.hpp>
#include <Item/IEquipment.hpp>
#include <Item/StatefulItem/StatefulItem.hpp>
#include <ItemStore/ItemStore.hpp>
#include <Utilities/Utilities.hpp>
#include <tuple>
#include <unordered_map>

template<int NSlots, typename TExtraState>
struct BagState
{
    ItemStore store;
    TExtraState extra;

    BagState() : store(NSlots, !ItemStore::Rules::itemExtends<IBag>()), extra() {}

    bool operator==(const BagState<NSlots, TExtraState> &other) const
    {
        return store == other.store && extra == other.extra;
    }
};

template<int NSlots>
struct BagState<NSlots, void>
{
    ItemStore store;

    BagState() : store(NSlots, !ItemStore::Rules::itemExtends<IBag>()) {}

    bool operator==(const BagState<NSlots, void> &other) const
    {
        return store == other.store;
    }
};

template<int NSlots, typename TExtraState>
DERIVE_STRUCT_SERIALIZER_PARTIAL(
    SINGLE_ARG(BagState<NSlots, TExtraState>),
    self.store, self.extra
);

template<int NSlots>
DERIVE_STRUCT_SERIALIZER_PARTIAL(
    SINGLE_ARG(BagState<NSlots, void>),
    self.store
);

/**
 * @brief Base class for items which container other items and can be equipped to expose them.
 */
template<int NSlots, typename TExtraState = void>
class Bag :
    public StatefulItem<BagState<NSlots, TExtraState>>,
    public IBag
{
private:
    using TransformCallback = std::function<void(ItemStack &stack, const std::vector<uint8_t> &data)>;

    std::unordered_map<std::string, TransformCallback> transformCommands;

protected:
    template<typename... TArgs>
    void registerBagTransform(const std::string &cmd, const std::function<void(ItemStack&, TArgs... args)> &callback)
    {
        transformCommands.emplace(cmd, [callback](ItemStack &stack, const std::vector<uint8_t> &data)
        {
            std::tuple<TArgs...> argTuple;
            if (std::apply([&data](TArgs&... args){ return deserialize(data.data(), data.size(), args...); }, argTuple) != data.size())
            {
                return;
            }

            std::apply(
                [&callback, &stack](TArgs... args)
                {
                    callback(stack, args...);
                },
                argTuple
            );
        });
    }

    template<typename T>
    void registerBagTransform(const std::string &cmd, T t)
    {
        registerBagTransform(cmd, std::function(t));
    }

public:
    static constexpr int numBagSlots = NSlots;

    Bag(std::string name) : StatefulItem<BagState<NSlots, TExtraState>>(name)
    {
        this->setMaxStackSize(1);
    }

    virtual ItemStore& bagStore(ItemStack &stack) override
    {
        return this->state(stack).store;
    }

    virtual int getNumBagSlots() override
    {
        return numBagSlots;
    }

    virtual void applyBagTransform(ItemStack &stack, const std::string &cmd, const std::vector<uint8_t> &data) override
    {
        auto it = transformCommands.find(cmd);
        if (it == transformCommands.end())
        {
            return;
        }

        it->second(stack, data);
    }
};

#endif