#include "Inventory.h"
#include "ItemFilter.h"
#include "Constants.h"
#include "Area.h"

using namespace Framework;

InventoryInteraction::InventoryInteraction( Inventory* current, Inventory* other, Direction dir )
    : current( current ),
    other( other ),
    dir( dir )
{
    lock();
}

InventoryInteraction::InventoryInteraction( const InventoryInteraction& interaction )
    : InventoryInteraction( interaction.current, interaction.other, interaction.dir )
{}

InventoryInteraction::~InventoryInteraction()
{
    unlock();
}

void InventoryInteraction::lock()
{
    if( !current || !other ) return;
    if( current->location.x < other->location.x )
    {
        current->cs.Enter();
        other->cs.Enter();
        return;
    }
    else if( current->location.x == other->location.x )
    {
        if( current->location.y < other->location.y )
        {
            current->cs.Enter();
            other->cs.Enter();
            return;
        }
        else if( current->location.y == other->location.y )
        {
            if( current->location.z < other->location.z )
            {
                current->cs.Enter();
                other->cs.Enter();
                return;
            }
        }
    }
    other->cs.Enter();
    current->cs.Enter();
}

void InventoryInteraction::unlock()
{
    if( !current || !other ) return;
    if( current->location.x < other->location.x )
    {
        current->cs.Leave();
        other->cs.Leave();
        return;
    }
    else if( current->location.x == other->location.x )
    {
        if( current->location.y < other->location.y )
        {
            current->cs.Leave();
            other->cs.Leave();
            return;
        }
        else if( current->location.y == other->location.y )
        {
            if( current->location.z < other->location.z )
            {
                current->cs.Leave();
                other->cs.Leave();
                return;
            }
        }
    }
    other->cs.Leave();
    current->cs.Leave();
}

void InventoryInteraction::transaction( Inventory* zSource, Inventory* zTarget, ItemFilter* zFilter, Direction sourceView, Direction targetView, int count )
{
    auto sourceSlot = zSource->pullSlotsOrder->begin();
    for( auto targetSlot = zTarget->pushSlotsOrder->begin(); targetSlot; )
    {
        int amount = count;
        if( !targetSlot->isFull() )
        {
            if( targetSlot->zStack() )
            {
                Array<ItemSlot*>* zSurceListe = zSource->itemCache->safeGet( targetSlot->zStack()->zItem()->zItemType()->getId(), 0 );
                if( zSurceListe )
                {
                    Array<int> toDelete;
                    int index = 0;
                    for( auto sourceSlot = zSurceListe->begin(); sourceSlot; sourceSlot++, index++ )
                    {
                        if( zFilter && !zFilter->matchItem( sourceSlot->zStack()->zItem() ) )
                            continue;
                        int number = MIN( targetSlot->numberOfAddableItems( sourceSlot->zStack(), sourceView ), count );
                        int tmp = number;
                        if( number > 0 && zSource->allowPullStack( sourceSlot, sourceView ) && zTarget->allowPushStack( targetSlot, targetView, sourceSlot->zStack()->zItem(), tmp ) )
                        {
                            number = MIN( number, tmp );
                            ItemStack* stack = sourceSlot->takeItemsOut( number, dir );
                            if( stack )
                            {
                                if( !sourceSlot->zStack() )
                                    toDelete.add( index, 0 );
                                targetSlot->addItems( stack, dir );
                                zSource->afterPullStack( sourceSlot, sourceView, targetSlot->zStack()->zItem(), number );
                                zTarget->afterPushStack( targetSlot, targetView, targetSlot->zStack()->zItem(), number );
                                if( stack->getSize() )
                                    throw stack;
                                stack->release();
                                count -= number;
                                if( count == 0 )
                                    break;
                            }
                        }
                    }
                    for( auto indexToDelete = toDelete.begin(); indexToDelete; indexToDelete++ )
                        zSurceListe->remove( indexToDelete );
                    if( count == 0 )
                        return;
                }
            }
            else
            {
                while( sourceSlot && (!sourceSlot->zStack() || (zFilter && !zFilter->matchItem( sourceSlot->zStack()->zItem() ))) )
                    sourceSlot++;
                if( !sourceSlot )
                    return;
                int number = MIN( targetSlot->numberOfAddableItems( sourceSlot->zStack(), sourceView ), count );
                int tmp = number;
                if( number > 0 && zSource->allowPullStack( sourceSlot, sourceView ) && zTarget->allowPushStack( targetSlot, targetView, sourceSlot->zStack()->zItem(), tmp ) )
                {
                    number = MIN( number, tmp );
                    ItemStack* stack = sourceSlot->takeItemsOut( number, dir );
                    if( stack )
                    {
                        zSource->updateCache( sourceSlot, targetSlot->zStack()->zItem()->zItemType()->getId() );
                        targetSlot->addItems( stack, dir );
                        zTarget->updateCache( targetSlot, targetSlot->zStack()->zItem()->zItemType()->getId() );
                        zSource->afterPullStack( sourceSlot, sourceView, targetSlot->zStack()->zItem(), number );
                        zTarget->afterPushStack( targetSlot, targetView, targetSlot->zStack()->zItem(), number );
                        if( stack->getSize() )
                            throw stack;
                        stack->release();
                        count -= number;
                        if( count == 0 )
                            return;
                    }
                }
            }
        }
        if( amount == count || targetSlot->isFull() )
            targetSlot++;
    }
}


InventoryInteraction& InventoryInteraction::operator=( const InventoryInteraction& data )
{
    if( &data == this ) return *this;
    unlock();
    current = data.current;
    other = data.other;
    dir = data.dir;
    lock();
    return *this;
}

void InventoryInteraction::endInteraction()
{
    unlock();
    current = 0;
    other = 0;
}

void InventoryInteraction::pullItems( int count, ItemFilter* zFilter )
{
    if( !current || !other ) return;
    transaction( other, current, zFilter, opposite( dir ), dir, count );
}

void InventoryInteraction::pushItems( int count, ItemFilter* zFilter )
{
    if( !current || !other ) return;
    transaction( current, other, zFilter, dir, opposite( dir ), count );
}


Inventory::Inventory( const Framework::Vec3<float> location, bool hasInventory )
    : ReferenceCounter(),
    location( location )
{
    if( hasInventory )
    {
        pullSlotsOrder = new Framework::RCArray<ItemSlot>();
        pushSlotsOrder = new Framework::RCArray<ItemSlot>();
        itemCache = new Framework::HashMap<int, Framework::Array<ItemSlot*>*>( ITEM_CACHE_SIZE, std::_Identity<int>() );
    }
    else
    {
        pullSlotsOrder = 0;
        pushSlotsOrder = 0;
        itemCache = 0;
    }
}

Inventory::~Inventory()
{
    if( pullSlotsOrder )
        pullSlotsOrder->release();
    if( pushSlotsOrder )
        pushSlotsOrder->release();
    if( itemCache )
        itemCache->release();
}

void Inventory::updateCache( ItemSlot* zSlot, int beforeKey )
{
    if( !itemCache )
        return;
    int key = zSlot->zStack()->zItem()->zItemType()->getId();
    if( key == beforeKey )
        return;
    if( beforeKey >= 0 )
    {
        auto tmp = itemCache->safeGet( key, 0 );
        if( tmp )
            tmp->removeValue( zSlot );
    }
    if( zSlot->zStack() )
    {
        auto tmp = itemCache->safeGet( key, 0 );
        if( !tmp )
        {
            tmp = new Array<ItemSlot*>();
            itemCache->put( key, tmp );
        }
        tmp->add( zSlot, 0 );
    }
}

void Inventory::addSlot( ItemSlot* slot )
{
    cs.Enter();
    int pullPrio = slot->getPullPriority();
    int pushPrio = slot->getPushPriority();
    int index = 0;
    for( auto stack : *pullSlotsOrder )
    {
        if( stack->getPullPriority() > pullPrio )
            break;
        index++;
    }
    pullSlotsOrder->add( dynamic_cast<ItemSlot*>(slot->getThis()), index );
    index = 0;
    for( auto stack : *pushSlotsOrder )
    {
        if( stack->getPushPriority() > pushPrio )
            break;
        index++;
    }
    pullSlotsOrder->add( slot, index );
    updateCache( slot, -1 );
    cs.Leave();
}

bool Inventory::allowPullStack( ItemSlot* zSlot, Direction dir )
{
    return pullSlotsOrder;
}

bool Inventory::allowPushStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int& count )
{
    return pushSlotsOrder;
}

void Inventory::afterPullStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int count )
{}

void Inventory::afterPushStack( ItemSlot* zSlot, Direction dir, const Item* zItem, int count )
{}

void Inventory::loadInventory( Framework::StreamReader* zReader )
{
    if( itemCache )
    {
        for( auto stack : *pushSlotsOrder )
        {
            int size = 0;
            zReader->lese( (char*)&size, 4 );
            if( size != 0 )
            {
                int id = 0;
                zReader->lese( (char*)&id, 4 );
                Item* item = StaticRegistry<ItemType>::INSTANCE.zElement( id )->loadItem( zReader );
                stack->addItems( new ItemStack( item, size ), NO_DIRECTION );
            }
        }
    }
}

void Inventory::saveInventory( Framework::StreamWriter* zWriter )
{
    if( itemCache )
    {
        for( auto slot : *pushSlotsOrder )
        {
            const ItemStack* stack = slot->zStack();
            int value = 0;
            if( !stack || !stack->zItem() )
            {
                zWriter->schreibe( (char*)&value, 4 );
            }
            else
            {
                value = stack->getSize();
                zWriter->schreibe( (char*)&value, 4 );
                value = stack->zItem()->zItemType()->getId();
                zWriter->schreibe( (char*)&value, 4 );
                stack->zItem()->zItemType()->saveItem( stack->zItem(), zWriter );
            }
        }
    }
}

void Inventory::localTransaction( Array< ItemSlot* >* zSourceSlots, Array< ItemSlot* >* zTargetSlots, ItemFilter* zFilter, int count )
{
    if( itemCache )
    {
        cs.Enter();
        auto sourceSlot = zSourceSlots->begin();
        for( auto targetSlot = zTargetSlots->begin(); targetSlot; )
        {
            int amount = count;
            if( !targetSlot->isFull() )
            {
                if( targetSlot->zStack() )
                {
                    Array<ItemSlot*>* zSurceListe = itemCache->safeGet( targetSlot->zStack()->zItem()->zItemType()->getId(), 0 );
                    if( zSurceListe )
                    {
                        Array<int> toDelete;
                        int index = 0;
                        for( auto sourceSlot = zSurceListe->begin(); sourceSlot; sourceSlot++, index++ )
                        {
                            if( zSourceSlots->getWertIndex( sourceSlot ) < 0 )
                                continue;
                            if( zFilter && !zFilter->matchItem( sourceSlot->zStack()->zItem() ) )
                                continue;
                            int number = MIN( targetSlot->numberOfAddableItems( sourceSlot->zStack(), NO_DIRECTION ), count );
                            if( number > 0 )
                            {
                                ItemStack* stack = sourceSlot->takeItemsOut( number, NO_DIRECTION );
                                if( stack )
                                {
                                    if( !sourceSlot->zStack() )
                                        toDelete.add( index, 0 );
                                    targetSlot->addItems( stack, NO_DIRECTION );
                                    if( stack->getSize() )
                                    {
                                        cs.Leave();
                                        throw stack;
                                    }
                                    stack->release();
                                    count -= number;
                                    if( count == 0 )
                                        break;
                                }
                            }
                        }
                        for( auto indexToDelete = toDelete.begin(); indexToDelete; indexToDelete++ )
                            zSurceListe->remove( indexToDelete );
                        if( count == 0 )
                        {
                            cs.Leave();
                            return;
                        }
                    }
                }
                else
                {
                    while( sourceSlot && (!sourceSlot->zStack() || (zFilter && !zFilter->matchItem( sourceSlot->zStack()->zItem() ))) )
                        sourceSlot++;
                    if( !sourceSlot )
                    {
                        cs.Leave();
                        return;
                    }
                    int number = MIN( targetSlot->numberOfAddableItems( sourceSlot->zStack(), NO_DIRECTION ), count );
                    if( number > 0 )
                    {
                        ItemStack* stack = sourceSlot->takeItemsOut( number, NO_DIRECTION );
                        if( stack )
                        {
                            updateCache( sourceSlot, targetSlot->zStack()->zItem()->zItemType()->getId() );
                            targetSlot->addItems( stack, NO_DIRECTION );
                            updateCache( targetSlot, targetSlot->zStack()->zItem()->zItemType()->getId() );
                            if( stack->getSize() )
                            {
                                cs.Leave();
                                throw stack;
                            }
                            stack->release();
                            count -= number;
                            if( count == 0 )
                            {
                                cs.Leave();
                                return;
                            }
                        }
                    }
                }
            }
            if( amount == count || targetSlot->isFull() )
                targetSlot++;
        }
        cs.Leave();
    }
}

InventoryInteraction Inventory::interactWith( Inventory* zInventory, Direction dir )
{
    return InventoryInteraction( this, zInventory, dir );
}