#include "Player.h"

#include "Game.h"
#include "ItemFilter.h"
#include "PlayerHand.h"

Player::Player(Framework::Vec3<float> location, int dimensionId, int entityId)
    : Entity(EntityTypeEnum::PLAYER, location, dimensionId, entityId),
      BasicShapedCrafter(3, 3, this, "inventory")
{
    for (int i = 0; i < 10; i++)
    {
        ItemSlot* slot = new ItemSlot("ItemBar", 50, 0, i, 0, ANY_DIRECTION, 0);
        itemBar.add(slot);
        addSlot(slot);
    }
    for (int i = 0; i < 30; i++)
    {
        ItemSlot* slot
            = new ItemSlot("Inventory", 50, 0, i + 10, 0, ANY_DIRECTION, 0);
        addSlot(slot);
    }
    leftHandPosition = 0;
    maxHP = 10;
    maxStamina = 10;
    maxHunger = 10;
    maxThirst = 10;
    setHP(10);
    setStamina(10);
    setHunger(10);
    setThirst(10);
    keyState = 0;
    jumping = 0;
    faceOffset = {0.f, 0.f, 1.5f};
    targetDistanceLimit = 4;
    maxMovementSpeed = 4;
}

void Player::onTargetChange()
{
    NetworkMessage* msg = new NetworkMessage();
    ActionTarget::toMessage(zTarget(), getCurrentDimensionId(), msg);
    Game::INSTANCE->sendMessage(msg, this);
}

Framework::Text Player::getInventoryUIML()
{
    Framework::Text result
        = "<dialog id=\"player_inventory\" title=\"Inventory\" width=\"610\" "
          "height=\"450\">";
    result.append()
        << "<craftingGrid id=\"crafting\" margin-top=\"9\" "
           "align-top=\"start\" align-left=\"start\" margin-left=\"9\" "
           "width=\"282\" height=\"172\" rowSize=\"3\" colSize=\"3\" "
           "numOutputSlots=\"1\" target=\""
        << getId() << "\"/>"
        << "<inventory id=\"inventory\" margin-bottom=\"18\" "
           "align-bottom=\"item_bar\" align-left=\"start\" "
           "margin-left=\"9\" width=\"592\" height=\"172\" rowSize=\"10\" "
           "numSlots=\"30\" slotNameFilter=\"Inventory\" target=\""
        << getId() << "\"/>"
        << "<inventory id=\"item_bar\" margin-bottom=\"9\" "
           "align-bottom=\"end\" align-left=\"start\" margin-left=\"9\" "
           "width=\"592\" height=\"52\" rowSize=\"10\" numSlots=\"10\" "
           "slotNameFilter=\"ItemBar\" target=\""
        << getId() << "\"/>"
        << "</dialog>";
    return result;
}

Framework::Text Player::getPlayerGUI()
{
    Framework::Text result = "<gui id=\"player_gui\">";
    result.append()
        << "<itemBar id=\"gui_item_bar\" margin-bottom=\"9\" "
           "align-bottom=\"end\" align-left=\"center\" width=\"592\" "
           "height=\"52\" rowSize=\"10\" slotNameFilter=\"ItemBar\" target=\""
        << getId() << "\"/>"
        << "<statusBars id=\"gui_status_bars\" margin-bottom=\"9\" "
           "align-bottom=\"gui_item_bar\" align-left=\"center\" target=\""
        << getId() << "\"/>"
        << "</gui>";
    return result;
}

void Player::useItemSlot(ItemSlot* zSlot, bool left)
{
    if (zSlot->zStack())
    {
        ItemStack* stack = takeItemsOut(zSlot, 1, NO_DIRECTION);
        if (stack)
        {
            Item* item = stack->extractFromStack();
            Entity::useItem(item->getTypeId(), item, left);
            if (item->getHp() > 0)
            {
                if (item->getDurability() > 0)
                { // put used item back
                    stack->addToStack(item);
                    if (!zSlot->numberOfAddableItems(stack, NO_DIRECTION))
                    { // move other items to other space
                        ItemStack* oldItems = takeItemsOut(
                            zSlot, zSlot->zStack()->getSize(), NO_DIRECTION);
                        addItems(zSlot, stack, NO_DIRECTION);
                        addItems(oldItems, NO_DIRECTION, 0);
                        if (oldItems->getSize() > 0)
                        {
                            // TODO: drop remaining items
                        }
                    }
                    else
                        addItems(zSlot, stack, NO_DIRECTION);
                }
                else
                { // item is broken
                    // move other items of the same type to the slot
                    Array<ItemSlot*> fromSlots;
                    for (ItemSlot* slot : *this)
                    {
                        if (slot != zSlot) fromSlots.add(slot);
                    }
                    Array<ItemSlot*> targetSlots;
                    targetSlots.add(zSlot);
                    TypeItemFilter filter(item->zItemType());
                    localTransaction(&fromSlots,
                        &targetSlots,
                        &filter,
                        zSlot->getFreeSpace(),
                        NO_DIRECTION,
                        NO_DIRECTION);
                    // place broken item in inventory
                    const ItemType* brokenType
                        = item->zItemType()->zBrokenItemType();
                    if (brokenType)
                    {
                        Item* broken = item->zItemType()->breakItem(item);
                        if (broken)
                        {
                            stack->addToStack(broken);
                            addItems(stack, NO_DIRECTION, 0);
                            if (stack->getSize() > 0)
                            {
                                // TODO: drop remaining items
                            }
                        }
                    }
                    item->release();
                }
            }
            else
                item->release();
            stack->release();
        }
    }
    else
        Entity::useItem(ItemTypeEnum::PLAYER_HAND, 0, left); // hand usage
}

void Player::setName(Framework::Text name)
{
    this->name = name;
}

const char* Player::getName() const
{
    return name;
}

void Player::tick(const Dimension* zDimension)
{
    if ((keyState | Key::LEFT_HAND_ACTION) == keyState)
        useItemSlot(itemBar.get(leftHandPosition), true);
    if ((keyState | Key::RIGHT_HAND_ACTION) == keyState)
        useItemSlot(
            itemBar.get(leftHandPosition), false);
    return Entity::tick(zDimension);
}

void Player::playerApi(
    Framework::StreamReader* zRequest, NetworkMessage* zResponse)
{
    char byte;
    zRequest->lese(&byte, 1);
    switch (byte)
    {
    case 0:
        // stop action
        zRequest->lese(&byte, 1);
        switch (byte)
        {
        case 8:
            keyState = keyState & ~Key::LEFT_HAND_ACTION;
            break;
        case 9:
            keyState = keyState & ~Key::RIGHT_HAND_ACTION;
            break;
        }
        break;
    case 1:
        // begin action
        zRequest->lese(&byte, 1);
        switch (byte)
        {
        case 8:
            keyState = keyState | Key::LEFT_HAND_ACTION;
            break;
        case 9:
            keyState = keyState | Key::RIGHT_HAND_ACTION;
            break;
        }
        break;
    case 2:
        // set movement
        {
            MovementFrame frame;
            zRequest->lese((char*)&frame.direction.x, 4);
            zRequest->lese((char*)&frame.direction.y, 4);
            zRequest->lese((char*)&frame.direction.z, 4);
            zRequest->lese((char*)&frame.targetPosition.x, 4);
            zRequest->lese((char*)&frame.targetPosition.y, 4);
            zRequest->lese((char*)&frame.targetPosition.z, 4);
            zRequest->lese((char*)&frame.movementFlags, 4);
            zRequest->lese((char*)&frame.duration, 8);
            addMovementFrame(frame);
            calculateTarget(frame.targetPosition,
                frame.direction,
                !itemBar.get(leftHandPosition)->isEmpty()
                    ? itemBar.get(leftHandPosition)
                    ->zStack()
                    ->zItem() : 0);
            break;
        }
    case 3:
        { // switch item bar position
            zRequest->lese((char*)&leftHandPosition, 4);
            leftHandPosition = leftHandPosition % itemBar.getEintragAnzahl();
            NetworkMessage* msg = new NetworkMessage();
            msg->addressGui("gui_item_bar");
            char* message = new char[5];
            message[0] = 3; // set selected slot
            *(int*)(message + 1) = leftHandPosition;
            msg->setMessage(message, 5);
            Game::INSTANCE->sendMessage(msg, this);
            break;
        }
    case 4:
        {
            // open inventory
            zResponse->openDialog("player_inventory");
            Text uiml = getInventoryUIML();
            int msgSize = 4 + uiml.getLength();
            char* msg = new char[msgSize];
            *(int*)msg = uiml.getLength();
            memcpy(msg + 4, uiml.getText(), uiml.getLength());
            zResponse->setMessage(msg, msgSize);
            break;
        }
    case 5:
        {
            // request gui
            Text uiml = getPlayerGUI();
            int msgSize = 6 + uiml.getLength();
            char* msg = new char[msgSize];
            msg[0] = 2; // gui message
            msg[1] = 2; // set gui
            *(int*)(msg + 2) = uiml.getLength();
            memcpy(msg + 6, uiml.getText(), uiml.getLength());
            zResponse->setMessage(msg, msgSize);
            break;
        }
    case 6:
        { // inventory transaction
            bool isEntity;
            zRequest->lese((char*)&isEntity, 1);
            Inventory* source;
            if (isEntity)
            {
                int id;
                zRequest->lese((char*)&id, 4);
                source = Game::INSTANCE->zEntity(id, getCurrentDimensionId());
            }
            else
            {
                int dim;
                Framework::Vec3<int> pos;
                zRequest->lese((char*)&dim, 4);
                zRequest->lese((char*)&pos.x, 4);
                zRequest->lese((char*)&pos.y, 4);
                zRequest->lese((char*)&pos.z, 4);
                source = Game::INSTANCE->zBlockAt(pos, dim);
            }
            int sourceSlotId;
            zRequest->lese((char*)&sourceSlotId, 4);
            zRequest->lese((char*)&isEntity, 1);
            Inventory* target;
            if (isEntity)
            {
                int id;
                zRequest->lese((char*)&id, 4);
                target = Game::INSTANCE->zEntity(id, getCurrentDimensionId());
            }
            else
            {
                int dim;
                Framework::Vec3<int> pos;
                zRequest->lese((char*)&dim, 4);
                zRequest->lese((char*)&pos.x, 4);
                zRequest->lese((char*)&pos.y, 4);
                zRequest->lese((char*)&pos.z, 4);
                target = Game::INSTANCE->zBlockAt(pos, dim);
            }
            if (source && target)
            {
                int targetSlotId;
                zRequest->lese((char*)&targetSlotId, 4);
                SpecificSlotFilter filter(sourceSlotId, targetSlotId);
                source->interactWith(target, Direction::NO_DIRECTION)
                    .pushItems(source->zSlot(sourceSlotId)->getNumberOfItems(),
                        &filter);
            }
            break;
        }
    case 7: // craft action
        {
            bool isEntity;
            zRequest->lese((char*)&isEntity, 1);
            BasicShapedCrafter* target;
            if (isEntity)
            {
                int id;
                zRequest->lese((char*)&id, 4);
                target = dynamic_cast<BasicShapedCrafter*>(
                    Game::INSTANCE->zEntity(id, getCurrentDimensionId()));
            }
            else
            {
                int dim;
                Framework::Vec3<int> pos;
                zRequest->lese((char*)&dim, 4);
                zRequest->lese((char*)&pos.x, 4);
                zRequest->lese((char*)&pos.y, 4);
                zRequest->lese((char*)&pos.z, 4);
                target = dynamic_cast<BasicShapedCrafter*>(
                    Game::INSTANCE->zRealBlockInstance(pos, dim));
            }
            if (target) target->applyCurrentRecipie();
            break;
        }
    case 8: // request left hand position
        {
            NetworkMessage* msg = new NetworkMessage();
            msg->addressGui("gui_item_bar");
            char* message = new char[5];
            message[0] = 3; // set selected slot
            *(int*)(message + 1) = leftHandPosition;
            msg->setMessage(message, 5);
            Game::INSTANCE->sendMessage(msg, this);
            break;
        }
    }
}

void Player::onFall(float collisionSpeed)
{
    Entity::onFall(collisionSpeed);
    gravityMultiplier = 1.f;
    jumping = 0;
}

PlayerEntityType::PlayerEntityType()
    : EntityType(EntityTypeEnum::PLAYER,
        ModelInfo("player", "player.ltdb/player.png", 6))
{}

void PlayerEntityType::loadSuperEntity(
    Entity* zEntity, Framework::StreamReader* zReader) const
{
    Player* zPlayer = dynamic_cast<Player*>(zEntity);
    if (!zPlayer)
        throw "PlayerEntityType::loadSuperEntity was called with an entity "
              "witch is not an instance of Player";
    zReader->lese((char*)&zPlayer->leftHandPosition, 4);
    char len;
    zReader->lese(&len, 1);
    char* name = new char[(int)len + 1];
    zReader->lese(name, (int)len);
    name[(int)len] = 0;
    zPlayer->name = name;
    delete[] name;
    EntityType::loadSuperEntity(zPlayer, zReader);
}

void PlayerEntityType::saveSuperEntity(
    Entity* zEntity, Framework::StreamWriter* zWriter) const
{
    Player* zPlayer = dynamic_cast<Player*>(zEntity);
    if (!zPlayer)
        throw "PlayerEntityType::saveSuperEntity was called with an entity "
              "witch is not an instance of Player";
    zWriter->schreibe((char*)&zPlayer->leftHandPosition, 4);
    char len = (char)textLength(zPlayer->getName());
    zWriter->schreibe(&len, 1);
    zWriter->schreibe(zPlayer->getName(), (int)len);
    EntityType::saveSuperEntity(zEntity, zWriter);
}

Entity* PlayerEntityType::createEntity(
    Framework::Vec3<float> position, int dimensionId, int entityId) const
{
    return new Player(position, dimensionId, entityId);
}