#include <PlayerTriggerRegistry/PlayerTriggerRegistry.hpp>
#include <algorithm>

#include <ItemRecipe/ItemRecipe.hpp>
#include <ItemFilter/ItemFilterByName/ItemFilterByName.hpp>
#include <Entity/PhysicsEntity/CameraEntity/Player/Player.hpp>

bool ItemRecipe::canBeMade(Player &player)
{
    ItemStore &inventory = player.items.store("inventory");

    // Check that the input inventory contains enough of each input item.
    std::vector<ItemStack>::iterator it;
    for (it=identity.inputStacks.begin(); it!=identity.inputStacks.end(); ++it)
    {
        ItemStack &inputStack = *it;

        // If the amount in the input inventory is too low, we can't make.
        if (inventory.countItems(inputStack) < inputStack.getStackSize())
        {
            return false;
        }
    }

    // If there are any empty stacks in the output inventory, we can definitely make.
    int i;
    for (i=0; i<inventory.getNumSlots(); i++)
    {
        if (inventory.getStackInSlot(i).isEmpty())
        {
            return true;
        }
    }

    // Otherwise, make sure we can merge the output stack into existing stacks.
    int numCanMerge = 0;
    for (i=0; i<inventory.getNumSlots(); i++)
    {
        ItemStack stack = inventory.getStackInSlot(i);
        numCanMerge += stack.getMaxInsertableFrom(identity.outputStack);
    }

    // We can make as long as we can merge enough.
    return numCanMerge >= identity.outputStack.getStackSize();
}

void ItemRecipe::make(Player &player)
{
    ItemStore &inventory = player.items.store("inventory");

    if (!milestoneId.empty())
    {
        player.achieve(milestoneId, achievementId);
    }

    identity.outputStack.onMovedBy(player);

    // Remove the correct amount of each input stack from the input inventory.
    std::vector<ItemStack>::iterator it;
    for (it=identity.inputStacks.begin(); it!=identity.inputStacks.end(); ++it)
    {
        ItemStack &inputStack = *it;
        int numLeft = inputStack.getStackSize();

        int i;
        for (i=0; i<inventory.getNumSlots(); i++)
        {
            if (numLeft == 0)
            {
                break;
            }

            ItemStack stack = inventory.getStackInSlot(i);
            if (inputStack.canMergeFrom(stack))
            {
                int takingAway = std::min(stack.getStackSize(), numLeft);
                stack.setStackSize(stack.getStackSize() - takingAway);
                numLeft -= takingAway;

                if (stack.getStackSize() == 0) stack = ItemStack();
                inventory.setStackInSlot(i, stack);
            }
        }
    }

    // First try to merge as much as possible of the output stack into existing stacks.
    ItemStack outputStack = identity.outputStack;
    int i;
    for (i=0; i<inventory.getNumSlots(); i++)
    {
        if (outputStack.isEmpty())
        {
            break;
        }

        ItemStack stack = inventory.getStackInSlot(i);
        if (!stack.isEmpty() && stack.canMergeFrom(outputStack))
        {
            int unitsToMerge = std::min(stack.getMaxStackSize() - stack.getStackSize(), outputStack.getStackSize());
            stack.setStackSize(stack.getStackSize() + unitsToMerge);
            outputStack.setStackSize(outputStack.getStackSize() - unitsToMerge);

            inventory.setStackInSlot(i, stack);
        }
    }

    // If we have an empty stack then we are done.
    if (outputStack.isEmpty())
    {
        return;
    }

    // Otherwise, place this stack in an empty slot.
    for (i=0; i<inventory.getNumSlots(); i++)
    {
        if (inventory.getStackInSlot(i).isEmpty())
        {
            inventory.setStackInSlot(i, outputStack);
            return;
        }
    }
}

bool ItemRecipe::matchesQuery(std::string query)
{
    ItemFilterByName filter(query);

    std::vector<ItemStack>::iterator it;
    for (it=identity.inputStacks.begin(); it!=identity.inputStacks.end(); ++it)
    {
        if (filter.matches(*it))
        {
            return true;
        }
    }

    return filter.matches(identity.outputStack);
}

ItemRecipe* ItemRecipe::withAchievement(std::string milestoneId, int achievementId)
{
    this->milestoneId = milestoneId;
    this->achievementId = achievementId;

    return this;
}