#include "InventoryView.h"

#include <Bild.h>
#include <XML.h>

#include "DragController.h"
#include "Game.h"
#include "Globals.h"
#include "UIMLToolTip.h"

using namespace Framework;

InventoryElement::InventoryElement()
    : UIMLElement()
{}

//! prüft, ob dieses UIML Element für ein bestimmtes xml Element zuständig ist
bool InventoryElement::isApplicableFor(Framework::XML::Element& element)
{
    return element.getName().istGleich("inventory");
}

//! erstellt eine neue Zeichnung zu einem gegebenen xml Element
Framework::Zeichnung* InventoryElement::parseElement(
    Framework::XML::Element& element, Framework::UIMLContainer& generalFactory)
{
    Text targetValue = element.getAttributeValue("target");
    Vec3<int> blockPos(0, 0, 0);
    Framework::Either<int, VecN<int, 4>> target((int)targetValue);
    if (targetValue.hat(','))
    {
        Text* first
            = targetValue.getTeilText(0, targetValue.positionVon(",", 0) + 1);
        Text* second
            = targetValue.getTeilText(targetValue.positionVon(",", 0) + 1,
                targetValue.positionVon(",", 1));
        Text* third
            = targetValue.getTeilText(targetValue.positionVon(",", 1) + 1,
                targetValue.positionVon(",", 2));
        Text* forth
            = targetValue.getTeilText(targetValue.positionVon(",", 2) + 1);
        target = Framework::Either<int, VecN<int, 4>>(Framework::VecN<int, 4>(
            {(int)*first, (int)*second, (int)*third, (int)*forth}));
        first->release();
        second->release();
        third->release();
        forth->release();
    }
    return new InventoryView(element.getAttributeValue("id"),
        target,
        (int)element.getAttributeValue("rowSize"),
        element.getAttributeValue("slotNameFilter"));
}

bool InventoryElement::updateElement(Framework::XML::Element& element,
    Framework::Zeichnung& z,
    Framework::UIMLContainer& generalFactory)
{
    return false;
}

//! wendet die layout parameter zu einer Zeichnung an
void InventoryElement::layout(Framework::XML::Element& element,
    Framework::Zeichnung& z,
    int pWidth,
    int pHeight,
    Framework::UIMLContainer& generalLayouter)
{
    UIMLElement::layout(element, z, pWidth, pHeight, generalLayouter);
}

void SlotInfo::render(
    int x, int y, Framework::Bild& rObj, bool selected, bool lightBackground)
{
    const Text* filter
        = dynamic_cast<Game*>((Menu*)menuRegister->get("game"))->zFilterText();
    TextRenderer tr;
    tr.setSchriftZ(
        dynamic_cast<Schrift*>(uiFactory.initParam.schrift->getThis()));
    tr.setSchriftSize(12);
    bool filterMatch
        = filter->getLength() > 0 && this->name.hat(filter->getText());
    rObj.fillRegion(
        x, y, 52, 52, selected || filterMatch ? 0xFFFFFFFF : 0xFF52525E);
    rObj.fillRegion(x + 1,
        y + 1,
        50,
        50,
        lightBackground            ? 0xFF42424E
        : filter->getLength() == 0 ? 0xFF222222
                                   : (filterMatch ? 0xFF222222 : 0xFF000000));
    if (itemCount > 0)
    {
        rObj.alphaBild(x + 1, y + 1, 50, 50, *zItem);
        if (hp < maxHp)
        {
            rObj.fillRegion(x + 1, y + 47, 50, 2, 0xFF000000);
            rObj.fillRegion(
                x + 1, y + 47, (int)((hp / maxHp) * 50), 2, 0xFFFFFF00);
        }
        if (durability < maxDurability)
        {
            rObj.fillRegion(x + 1, y + 49, 50, 2, 0xFF000000);
            rObj.fillRegion(x + 1,
                y + 49,
                (int)((durability / maxDurability) * 50),
                2,
                0xFF00FF00);
        }
        const char* units[] = {"", "K", "M", "G", "T", "P"};
        int i = 0;
        int tmpCount = itemCount;
        for (; i < 6 && tmpCount > 1024; i++)
            tmpCount = tmpCount / 1024;
        Text count = tmpCount;
        count += units[i];
        tr.renderText(x + 45 - tr.getTextBreite(count),
            y + 45 - tr.getTextHeight(count),
            count,
            rObj,
            0xFFFFFFFF);
    }
}

InventoryView::InventoryView(
    Text id, Either<int, VecN<int, 4>> target, int rowSize, Text slotNameFilter)
    : ZeichnungHintergrund(),
      rowSize(rowSize),
      target(target),
      slotNameFilter(slotNameFilter),
      id(id),
      slots(0),
      dragStartId(-1),
      dragStopId(-1),
      currentTooltipSlot(-1),
      requestetTooltipSlot(-1)
{
    setStyle(ZeichnungHintergrund::Style::Sichtbar
             | ZeichnungHintergrund::Style::Erlaubt);
    char* msg = new char[id.getLength() + slotNameFilter.getLength() + 3];
    msg[0] = 0;
    msg[1] = (char)id.getLength();
    memcpy(msg + 2, id.getText(), id.getLength());
    msg[2 + id.getLength()] = (char)slotNameFilter.getLength();
    memcpy(msg + 3 + id.getLength(),
        slotNameFilter.getText(),
        slotNameFilter.getLength());
    World::INSTANCE->zClient()->inventoryAPIRequest(
        target, msg, id.getLength() + slotNameFilter.getLength() + 3);
    delete[] msg;
    setNeedToolTipEvent([this](Zeichnung* z, Punkt p) {
        int slot = getSlotByLocalPos(p);
        if (currentTooltipSlot != slot && currentTooltipSlot != -1)
        {
            std::cout << "closing tooltip\n";
            this->setToolTipZ(0);
            currentTooltipSlot = -1;
        }
        if (requestetTooltipSlot != slot && slot != -1)
        {
            if (World::INSTANCE)
            {
                std::cout << "requesting tooltip for slot " << slot << "\n";
                requestetTooltipSlot = slot;
                char* msg = new char[this->id.getLength() + 6];
                msg[0] = 2; // request inventory tooltip
                msg[1] = (char)this->id.getLength();
                memcpy(msg + 2, this->id.getText(), this->id.getLength());
                *(int*)(msg + 2 + this->id.getLength()) = slot;
                World::INSTANCE->zClient()->inventoryAPIRequest(
                    this->target, msg, this->id.getLength() + 6);
                return 1;
            }
        }
        return 0;
    });
}

InventoryView::~InventoryView()
{
    DragController<InventoryDragSource, int>* controller
        = ((Game*)(Menu*)menuRegister->get("game"))->zInventoryDragController();
    if (controller->getCurrentDragContainer() == this) controller->stopDrag();
    if (slots) slots->release();
    char* msg = new char[id.getLength() + 2];
    msg[0] = 1;
    msg[1] = (char)id.getLength();
    memcpy(msg + 2, id.getText(), id.getLength());
    World::INSTANCE->zClient()->inventoryAPIRequest(
        target, msg, id.getLength() + 2);
    delete[] msg;
}

int InventoryView::getSlotByLocalPos(Punkt pos)
{
    int x = 0;
    int y = 0;
    int rowCount = 0;
    int slot = 0;
    dragStopId = -1;
    if (slots)
    {
        for (SlotInfo& info : *slots)
        {
            if (pos.x >= x && pos.x < x + 50 && pos.y >= y && pos.y < y + 50)
                return info.id;
            x += 60;
            if (++rowCount >= rowSize)
            {
                y += 60;
                x = 0;
                rowCount = 0;
            }
            slot++;
        }
    }
    return -1;
}

void InventoryView::api(char* message)
{
    switch (message[0])
    {
    case 0:
        // send inventory content
        {
            Array<SlotInfo>* slots = new Array<SlotInfo>();
            int count = *(int*)(++message);
            for (int i = 0; i < count; i++)
            {
                SlotInfo info;
                info.id = *(int*)(message += 4);
                info.itemCount = *(int*)(message += 4);
                if (info.itemCount > 0)
                {
                    info.hp = *(float*)(message += 4);
                    info.maxHp = *(float*)(message += 4);
                    info.durability = *(float*)(message += 4);
                    info.maxDurability = *(float*)(message += 4);
                    info.zItem = zItemType(*(int*)(message += 4))->zIcon();
                    char len = *(message += 4);
                    char* name = new char[len + 1];
                    memcpy(name, message += 1, len);
                    name[len] = 0;
                    info.name = name;
                    delete[] name;
                    message += len - 4;
                }
                slots->add(info);
            }
            window->zBildschirm()->postAction([this, slots]() {
                if (this->slots) this->slots->release();
                this->slots = slots;
            });
            break;
        }
    case 1: // set count of items
        {
            if (!slots) return;
            int id = *(int*)(message + 1);
            int count = *(int*)(message + 5);
            for (int i = 0; i < slots->getEintragAnzahl(); i++)
            {
                if (slots->get(i).id == id)
                {
                    SlotInfo info = slots->get(i);
                    info.itemCount = count;
                    if (info.itemCount == 0)
                    {
                        info.name = "";
                        DragController<InventoryDragSource, int>* controller
                            = ((Game*)(Menu*)menuRegister->get("game"))
                                  ->zInventoryDragController();
                        if (controller
                            && controller->getCurrentDragContainer() == this
                            && controller->getCurrentDaragElement() == info.id)
                        {
                            controller->stopDrag();
                        }
                    }
                    slots->set(info, i);
                    break;
                }
            }
            break;
        }
    case 2: // add new stack
        {
            if (!slots) return;
            int id = *(int*)(message + 1);
            for (int i = 0; i < slots->getEintragAnzahl(); i++)
            {
                if (slots->get(i).id == id)
                {
                    SlotInfo info = slots->get(i);
                    info.itemCount = *(int*)(message + 5);
                    info.hp = *(float*)(message + 9);
                    info.maxHp = *(float*)(message + 13);
                    info.durability = *(float*)(message + 17);
                    info.maxDurability = *(float*)(message + 21);
                    info.zItem = zItemType(*(int*)(message + 25))->zIcon();
                    char len = *(message + 29);
                    char* name = new char[len + 1];
                    memcpy(name, message + 30, len);
                    name[len] = 0;
                    info.name = name;
                    delete[] name;
                    slots->set(info, i);
                    break;
                }
            }
            break;
        }
    case 3: // receive tooltip uiml
        {
            int slotId = *(int*)(message + 1);
            if (slotId == requestetTooltipSlot)
            {
                std::cout << "tooltip loaded for slot " << slotId << "\n";
                short len = *(short*)(message + 5);
                if (len > 0)
                {
                    char* uiml = new char[len + 1];
                    memcpy(uiml, message + 7, len);
                    uiml[len] = 0;
                    UIMLToolTip* tip = new UIMLToolTip();
                    tip->setUIML(uiml);
                    tip->setWarten(0);
                    tip->setPosition(mausPos.x, mausPos.y + 15);
                    setToolTipZ(tip);
                    delete[] uiml;
                    currentTooltipSlot = slotId;
                    requestetTooltipSlot = -1;
                }
                else
                    toolTipRequested = 0;
            }
        }
    }
}

bool InventoryView::tick(double tickVal)
{
    return ZeichnungHintergrund::tick(tickVal);
}

void InventoryView::render(Bild& rObj)
{
    ZeichnungHintergrund::render(rObj);
    if (!rObj.setDrawOptions(pos.x, pos.y, gr.x, gr.y)) return;
    if (slots)
    {
        int x = 0;
        int y = 0;
        int rowCount = 0;
        for (SlotInfo info : *slots)
        {
            info.render(
                x, y, rObj, dragStartId == info.id, dragStopId == info.id);
            x += 60;
            if (++rowCount >= rowSize)
            {
                y += 60;
                x = 0;
                rowCount = 0;
            }
        }
    }
    rObj.releaseDrawOptions();
}

void InventoryView::doMausEreignis(MausEreignis& me, bool userRet)
{
    mausPos.x = me.originalX;
    mausPos.y = me.originalY;
    if (!slots) return;
    if (me.id == ME_Bewegung)
    {
        if (getSlotByLocalPos(Punkt(me.mx, me.my)) != currentTooltipSlot)
        {
            if (currentTooltipSlot != -1)
            {
                std::cout << "closing tooltip\n";
                setToolTipZ(0);
            }
            else
                toolTipRequested = 0;
            currentTooltipSlot = -1;
        }
    }
    DragController<InventoryDragSource, int>* controller
        = ((Game*)(Menu*)menuRegister->get("game"))->zInventoryDragController();
    int x = 0;
    int y = 0;
    int rowCount = 0;
    int slot = 0;
    dragStopId = -1;
    for (SlotInfo info : *slots)
    {
        if (me.mx >= x && me.mx < x + 50 && me.my >= y && me.my < y + 50)
        {
            if (me.id == ME_RLinks)
            {
                if (!controller->getCurrentDragContainer()
                    && info.itemCount > 0)
                {
                    controller->beginDrag(this, info.id, info.zItem, [this]() {
                        dragStartId = -1;
                    });
                    dragStartId = info.id;
                }
                else if (controller->getCurrentDragContainer())
                {
                    // request to transfer items from source to target slot
                    Framework::Either<int, Framework::VecN<int, 4>> source
                        = controller->getCurrentDragContainer()
                              ->getInventoryTarget();
                    int len = 2 + (source.isA() ? 4 : 16) + 5
                            + (target.isA() ? 4 : 16) + 4;
                    char* msg = new char[len];
                    int index = 0;
                    msg[index++] = 6;
                    msg[index++] = (char)source.isA();
                    if (source.isA())
                    {
                        *(int*)(msg + index) = source.getA();
                        index += 4;
                    }
                    else
                    {
                        *(int*)(msg + index) = source.getB()[0];
                        *(int*)(msg + index + 4) = source.getB()[1];
                        *(int*)(msg + index + 8) = source.getB()[2];
                        *(int*)(msg + index + 12) = source.getB()[3];
                        index += 16;
                    }
                    *(int*)(msg + index) = controller->getCurrentDaragElement();
                    index += 4;
                    msg[index++] = target.isA();
                    if (target.isA())
                    {
                        *(int*)(msg + index) = target.getA();
                        index += 4;
                    }
                    else
                    {
                        *(int*)(msg + index) = target.getB()[0];
                        *(int*)(msg + index + 4) = target.getB()[1];
                        *(int*)(msg + index + 8) = target.getB()[2];
                        *(int*)(msg + index + 12) = target.getB()[3];
                        index += 16;
                    }
                    *(int*)(msg + index) = info.id;
                    World::INSTANCE->zClient()->sendPlayerAction(msg, len);
                    delete[] msg;
                }
            }
            else
            {
                if (controller->getCurrentDragContainer()
                    && (controller->getCurrentDragContainer() != this
                        || controller->getCurrentDaragElement() != info.id))
                {
                    dragStopId = info.id;
                }
            }
            break;
        }
        x += 60;
        if (++rowCount >= rowSize)
        {
            y += 60;
            x = 0;
            rowCount = 0;
        }
        slot++;
    }
    ZeichnungHintergrund::doMausEreignis(me, userRet);
}

Framework::Either<int, Framework::VecN<int, 4>>
InventoryView::getInventoryTarget() const
{
    return target;
}