#ifndef ITEM_STORE_HPP_
#define ITEM_STORE_HPP_

#include <Item/IInventory.hpp>
#include <ItemStack/ItemStack.hpp>
#include <Serialize/Serialize.hpp>
#include <functional>
#include <vector>

/**
 * @brief A serializable array of item stacks, with rules on which stacks may be inserted.
 */
class ItemStore final : public IInventory
{
public:
    using StackUpdatedCallback = std::function<void(int, ItemStack&)>;

    struct Rules
    {
        std::function<bool(ItemStack&)> canAcceptStack;

        /**
         * @brief Returns `Rules` that accept any stack.
         */
        static Rules any();

        /**
         * @brief Returns `Rules` that accept empty stacks.
         */
        static Rules empty();

        /**
         * @brief Returns `Rules` that accept stacks whose item extends the specified type.
         */
        template<typename T>
        static Rules itemExtends()
        {
            return Rules{[](ItemStack &stack)
            {
                return stack.getItemAs<T>() != nullptr;
            }};
        }

        /**
         * @brief Returns the opposite `Rules`.
         */
        Rules operator!() const;

        /**
         * @brief Returns new `Rules` that accept stacks accepted by either this or `rhs`.
         */
        Rules operator||(const Rules &rhs) const;

        /**
         * @brief Returns new `Rules` that accept stacks accepted by both this and `rhs`.
         */
        Rules operator&&(const Rules &rhs) const;
    };

private:
    std::vector<ItemStack> stacks;
    Rules rules;
    StackUpdatedCallback onStackUpdated = [](int, ItemStack&){};

    class UpdateBatch final
    {
    private:
        ItemStore &store;
        std::vector<ItemStack::Key> lastKnownKeys;
    
    public:
        UpdateBatch(ItemStore &store);

        ~UpdateBatch();

        ItemStack& getMutable(int slot);
        void flush();
    };

public:
    class iterator
    {
    private:
        std::shared_ptr<UpdateBatch> updateBatch;
        int index;
    
    public:
        iterator();
        iterator(const std::shared_ptr<UpdateBatch> &batch, int index);

        ItemStack& operator*();
        ItemStack* operator->();
        iterator& operator++();
        iterator operator++(int);
        bool operator==(const iterator &other) const;
        bool operator!=(const iterator &other) const;
        const int& slotIndex() const;
        void flush();
    };

    using const_iterator = std::vector<ItemStack>::const_iterator;

    ItemStore() = delete;

    ItemStore(int capacity, const Rules &rules = Rules::any());

    /**
     * @brief Set the callback to invoke when the stack in a slot is updated.
     * 
     * Returns `*this`.
     */
    ItemStore& withStackUpdatedCallback(StackUpdatedCallback callback);

    /**
     * @brief Return the number of slots in this store.
     * 
     * @return The number of slots.
     */
    virtual int getNumSlots() override;

    /**
     * @brief Get the stack in the specified slot.
     */
    virtual ItemStack getStackInSlot(int slot) override;

    /**
     * @brief Set the stack in the specified slot.
     */
    virtual void setStackInSlot(int slot, ItemStack stack) override;

    /**
     * @brief Returns true if the specified stack is allowed into this store.
     */
    bool canAcceptStack(ItemStack &stack);

    iterator at(int slot);
    iterator begin();
    iterator end();
    const_iterator begin() const;
    const_iterator end() const;
    const_iterator cbegin() const;
    const_iterator cend() const;

    bool operator==(const ItemStore &store) const;

    virtual bool isAllowedStackInSlot(int slot, ItemStack stack) override;

    friend struct SerialCodec<ItemStore>;
    friend class ItemStore::UpdateBatch;
};

template<>
struct SerialCodec<ItemStore>
{
    static void encode(std::vector<uint8_t> &outBytes, const ItemStore &obj);
    static size_t decode(const uint8_t *data, size_t size, ItemStore &obj);
};

#endif