#include "inventoryitemmodel.hpp"

#include <sstream>

#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/npcstats.hpp"

#include "../mwworld/containerstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"

#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"

namespace MWGui
{

InventoryItemModel::InventoryItemModel(const MWWorld::Ptr &actor)
    : mActor(actor)
{
}

ItemStack InventoryItemModel::getItem (ModelIndex index)
{
    if (index < 0)
        throw std::runtime_error("Invalid index supplied");
    if (mItems.size() <= static_cast<size_t>(index))
        throw std::runtime_error("Item index out of range");
    return mItems[index];
}

size_t InventoryItemModel::getItemCount()
{
    return mItems.size();
}

ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item)
{
    size_t i = 0;
    for (ItemStack& itemStack : mItems)
    {
        if (itemStack == item)
            return i;
        ++i;
    }
    return -1;
}

MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip)
{
    if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor))
        throw std::runtime_error("Item to copy needs to be from a different container!");
    return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, allowAutoEquip);
}

void InventoryItemModel::removeItem (const ItemStack& item, size_t count)
{
    int removed = 0;
    // Re-equipping makes sense only if a target has inventory
    if (mActor.getClass().hasInventoryStore(mActor))
    {
        MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor);
        removed = store.remove(item.mBase, count, mActor, true);
    }
    else
    {
        MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor);
        removed = store.remove(item.mBase, count, mActor);
    }

    std::stringstream error;

    if (removed == 0)
    {
        error << "Item '" << item.mBase.getCellRef().getRefId() << "' was not found in container store to remove";
        throw std::runtime_error(error.str());
    }
    else if (removed < static_cast<int>(count))
    {
        error << "Not enough items '" << item.mBase.getCellRef().getRefId() << "' in the stack to remove (" << static_cast<int>(count) << " requested, " << removed << " found)";
        throw std::runtime_error(error.str());
    }
}

MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, ItemModel *otherModel)
{
    // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items via the 'Take All' button.
    if (item.mFlags & ItemStack::Flag_Bound)
        return MWWorld::Ptr();

    MWWorld::Ptr ret = otherModel->copyItem(item, count);
    removeItem(item, count);
    return ret;
}

void InventoryItemModel::update()
{
    MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor);

    mItems.clear();

    for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
    {
        MWWorld::Ptr item = *it;

        if (!item.getClass().showsInInventory(item))
            continue;

        ItemStack newItem (item, this, item.getRefData().getCount());

        if (mActor.getClass().hasInventoryStore(mActor))
        {
            MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor);
            if (invStore.isEquipped(newItem.mBase))
                newItem.mType = ItemStack::Type_Equipped;
        }

        mItems.push_back(newItem);
    }
}

bool InventoryItemModel::onTakeItem(const MWWorld::Ptr &item, int count)
{
    // Looting a dead corpse is considered OK
    if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead())
        return true;

    MWWorld::Ptr player = MWMechanics::getPlayer();
    MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item, mActor, count);

    return true;
}

}