#include "Player.h"

#include "ArrayUtils.h"
#include "Block.h"
#include "Chat.h"
#include "Game.h"
#include "ItemFilter.h"
#include "ItemStack.h"
#include "PlayerHand.h"
#include "QuestDialog.h"
#include "UIController.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(0, 0, 0, 10.f);
    setStamina(10);
    setHunger(10);
    setThirst(10);
    setJumpSpeed(5.f);
    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(), dimensionId, 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 (Entity::useItem(zSlot->zStack() && zSlot->zStack()->zItem()
                            ? zSlot->zStack()->zItem()->getTypeId()
                            : ItemTypeEnum::PLAYER_HAND,
            (ItemStack*)zSlot->zStack(),
            left))
    {
        zSlot->update();
        if (zSlot->zStack())
        {
            if (zSlot->zStack()->zItem()->getDurability() <= 0)
            {
                ItemStack* stack = zSlot->takeItemsOut(
                    zSlot->getNumberOfItems(), NO_DIRECTION);
                Item* broken
                    = stack->zItem()->zItemType()->breakItem(stack->zItem());
                if (broken)
                {
                    ItemStack* newStack
                        = new ItemStack(broken, stack->getSize());
                    zSlot->addItems(newStack, NO_DIRECTION);
                    if (newStack->getSize() > 0)
                    {
                        Game::INSTANCE->spawnItem(
                            getLocation(), getDimensionId(), newStack);
                    }
                    else
                    {
                        newStack->release();
                    }
                }
                stack->release();
            }
            else if (zSlot->zStack()->zItem()->getHp() <= 0)
            {
                ItemStack* stack = zSlot->takeItemsOut(
                    zSlot->getNumberOfItems(), NO_DIRECTION);
                Framework::Array<ItemSlot*> fromSlots;
                for (ItemSlot* slot : *this)
                {
                    if (slot != zSlot) fromSlots.add(slot);
                }
                Framework::Array<ItemSlot*> targetSlots;
                targetSlots.add(zSlot);
                TypeItemFilter filter;
                filter.setType(stack->zItem()->zItemType());
                localTransaction(&fromSlots,
                    &targetSlots,
                    &filter,
                    zSlot->getFreeSpace(),
                    NO_DIRECTION,
                    NO_DIRECTION);
                stack->release();
            }
        }
        updateSlot(zSlot);
    }
}

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->addressUIElement("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:
        {
            Game::INSTANCE->zUIController()->addDialog(
                new UIDialog("player_inventory",
                    getId(),
                    new Framework::XML::Element(getInventoryUIML())));
            break;
        }
    case 5:
        {
            // request gui
            Framework::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, dimensionId);
            }
            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, 0);
            }
            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, dimensionId);
            }
            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, 0);
            }
            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, dimensionId));
            }
            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->addressUIElement("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 9: // open quest dialog
        {
            Game::INSTANCE->zUIController()->addDialog(
                new QuestDialog(getId()));
            break;
        }
    case 10: // fall damage
        {
            float speed = 0.f;
            zRequest->lese((char*)&speed, 4);
            onFall(speed);
            break;
        }
    }
}

void Player::onFall(float collisionSpeed)
{
    Entity::onFall(collisionSpeed);
    // TODO: check validity
}

void Player::onDeath(Entity* zActor, Item* zUsedItem, ItemSkill* zUsedSkill)
{
    this->setHP(zActor, zUsedItem, zUsedSkill, this->getMaxHP());
    Game::INSTANCE->zChat()->broadcastMessage(
        name + " died!", Chat::CHANNEL_INFO);
    // TODO: respown
}

PlayerEntityType::PlayerEntityType()
    : EntityType()
{
    setName("Player");
    setModel(new ModelInfo(
        "entities.m3/player", toArray("entities.ltdb/player.png", 6), 0, 1.f));
}

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)Framework::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);
}