#include <ItemStore/ItemStore.hpp>

ItemStore::Rules ItemStore::Rules::any()
{
    return Rules{[](ItemStack&){return true;}};
}

ItemStore::Rules ItemStore::Rules::empty()
{
    return Rules{[](ItemStack &stack){return stack.isEmpty();}};
}

ItemStore::Rules ItemStore::Rules::operator!() const
{
    auto oldCanAccept = canAcceptStack;
    return Rules{[oldCanAccept](ItemStack &stack){return !oldCanAccept(stack);}};
}

ItemStore::Rules ItemStore::Rules::operator||(const Rules &rhs) const
{
    auto lhsCanAcceptStack = canAcceptStack;
    auto rhsCanAcceptStack = rhs.canAcceptStack;

    return Rules{[lhsCanAcceptStack, rhsCanAcceptStack](ItemStack &stack)
    {
        return lhsCanAcceptStack(stack) || rhsCanAcceptStack(stack);
    }};
}

ItemStore::Rules ItemStore::Rules::operator&&(const Rules &rhs) const
{
    auto lhsCanAcceptStack = canAcceptStack;
    auto rhsCanAcceptStack = rhs.canAcceptStack;

    return Rules{[lhsCanAcceptStack, rhsCanAcceptStack](ItemStack &stack)
    {
        return lhsCanAcceptStack(stack) && rhsCanAcceptStack(stack);
    }};
}

ItemStore::UpdateBatch::UpdateBatch(ItemStore &store) :
    store(store),
    lastKnownKeys(store.stacks.size())
{
    for (size_t i = 0; i < store.stacks.size(); i++)
    {
        lastKnownKeys[i] = store.stacks[i].key();
    }
}

ItemStore::UpdateBatch::~UpdateBatch()
{
    flush();
}

ItemStack& ItemStore::UpdateBatch::getMutable(int slot)
{
    return store.stacks[slot];
}

void ItemStore::UpdateBatch::flush()
{
    for (size_t i = 0; i < lastKnownKeys.size(); i++)
    {
        ItemStack &slot = store.stacks[i];
        if (slot.key() == lastKnownKeys[i])
        {
            continue;
        }

        store.onStackUpdated(i, store.stacks[i]);
        lastKnownKeys[i] = slot.key();
    }
}

ItemStore::iterator::iterator() {}

ItemStore::iterator::iterator(const std::shared_ptr<UpdateBatch> &batch, int index) : updateBatch(batch), index(index) {}

ItemStack& ItemStore::iterator::operator*()
{
    return updateBatch->getMutable(index);
}

ItemStack* ItemStore::iterator::operator->()
{
    return &updateBatch->getMutable(index);
}

ItemStore::iterator& ItemStore::iterator::operator++()
{
    index++;
    return *this;
}

ItemStore::iterator ItemStore::iterator::operator++(int)
{
    auto tmp = *this;
    ++(*this);
    return tmp;
}

bool ItemStore::iterator::operator==(const iterator &other) const
{
    return index == other.index;
}

bool ItemStore::iterator::operator!=(const iterator &other) const
{
    return index != other.index;
}

const int& ItemStore::iterator::slotIndex() const
{
    return index;
}

void ItemStore::iterator::flush()
{
    updateBatch->flush();
}

ItemStore::ItemStore(int capacity, const Rules &rules) : stacks(capacity), rules(rules) {}

ItemStore& ItemStore::withStackUpdatedCallback(StackUpdatedCallback callback)
{
    StackUpdatedCallback prevCallback = onStackUpdated;
    onStackUpdated = [prevCallback, callback](int slot, ItemStack &newStack)
    {
        prevCallback(slot, newStack);
        callback(slot, newStack);
    };

    return *this;
}

int ItemStore::getNumSlots()
{
    return stacks.size();
}

ItemStack ItemStore::getStackInSlot(int slot)
{
    return stacks[slot];
}

void ItemStore::setStackInSlot(int slot, ItemStack stack)
{
    stacks[slot] = stack;
    onStackUpdated(slot, stack);
}

bool ItemStore::canAcceptStack(ItemStack &stack)
{
    return rules.canAcceptStack(stack);
}

ItemStore::iterator ItemStore::at(int slot)
{
    return iterator(std::make_shared<UpdateBatch>(*this), slot);
}

ItemStore::iterator ItemStore::begin()
{
    return iterator(std::make_shared<UpdateBatch>(*this), 0);
}

ItemStore::iterator ItemStore::end()
{
    return iterator(std::make_shared<UpdateBatch>(*this), stacks.size());
}

ItemStore::const_iterator ItemStore::begin() const
{
    return stacks.begin();
}

ItemStore::const_iterator ItemStore::end() const
{
    return stacks.end();
}

ItemStore::const_iterator ItemStore::cbegin() const
{
    return stacks.cbegin();
}

ItemStore::const_iterator ItemStore::cend() const
{
    return stacks.cend();
}

bool ItemStore::operator==(const ItemStore &store) const
{
    if (stacks.size() != store.stacks.size())
    {
        return false;
    }

    for (size_t i = 0; i < stacks.size(); i++)
    {
        if (stacks[i] != store.stacks[i])
        {
            return false;
        }
    }

    return true;
}

bool ItemStore::isAllowedStackInSlot(int slot, ItemStack stack)
{
    return canAcceptStack(stack);
}

void SerialCodec<ItemStore>::encode(std::vector<uint8_t> &outBytes, const ItemStore &obj)
{
    serializeInto(outBytes, obj.stacks);
}

size_t SerialCodec<ItemStore>::decode(const uint8_t *data, size_t size, ItemStore &obj)
{
    int numSlots = obj.stacks.size();
    size_t consumed = deserialize(data, size, obj.stacks);
    obj.stacks.resize(numSlots);
    return consumed;
}