#include "CraftingGrid.h"

#include <DateiSystem.h>
#include <XML.h>

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

using namespace Framework;

CraftingGridElement::CraftingGridElement()
    : UIMLElement()
{}

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

//! erstellt eine neue Zeichnung zu einem gegebenen xml Element
Framework::Zeichnung* CraftingGridElement::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>>(VecN<int, 4>(
            {(int)*first, (int)*second, (int)*third, (int)*forth}));
        first->release();
        second->release();
        third->release();
        forth->release();
    }
    return new CraftingGridView(element.getAttributeValue("id"),
        (int)element.getAttributeValue("rowSize"),
        (int)element.getAttributeValue("colSize"),
        (int)element.getAttributeValue("numOutputSlots"),
        target);
}

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

//! wendet die layout parameter zu einer Zeichnung an
void CraftingGridElement::layout(Framework::XML::Element& element,
    Framework::Zeichnung& z,
    int pWidth,
    int pHeight,
    Framework::UIMLContainer& generalLayouter)
{
    z.setSize(500, 500); // TODO: calculate size
    UIMLElement::layout(element, z, pWidth, pHeight, generalLayouter);
}

CraftingGridView::CraftingGridView(Text id,
    int rowSize,
    int colSize,
    int numOutputSlots,
    Either<int, VecN<int, 4>> target)
    : ZeichnungHintergrund(),
      rowSize(rowSize),
      colSize(colSize),
      numOutputSlots(numOutputSlots),
      target(target),
      slots(0),
      outputs(0),
      id(id),
      dragStartId(-1),
      dragStopId(-1),
      currentTooltipSlot(-1),
      requestetTooltipSlot(-1)
{
    craft = uiFactory.createKnopf(uiFactory.initParam);
    craft->setPosition(rowSize * 60, 0);
    craft->setSize(40, 20);
    craft->setText("Craft");
    craft->setMausEreignis([this](void* p, void* o, MausEreignis me) {
        if (me.id == ME_RLinks)
        {
            char* msg = new char[2 + (this->target.isA() ? 4 : 16)];
            msg[0] = 7; // request crafting
            msg[1] = (char)this->target.isA();
            if (msg[1])
                *(int*)(msg + 2) = this->target.getA();
            else
            {
                *(int*)(msg + 2) = this->target.getB()[0];
                *(int*)(msg + 6) = this->target.getB()[1];
                *(int*)(msg + 10) = this->target.getB()[2];
                *(int*)(msg + 14) = this->target.getB()[3];
            }
            World::INSTANCE->zClient()->sendPlayerAction(
                msg, 2 + (this->target.isA() ? 4 : 16));
            delete[] msg;
        }
        return 1;
    });
    recipies = uiFactory.createKnopf(uiFactory.initParam);
    recipies->setPosition(rowSize * 60, colSize * 60 - 49);
    recipies->setSize(40, 40);
    recipies->addStyle(
        Knopf::Style::HBild | Knopf::Style::HAlpha | Knopf::Style::Hintergrund);
    recipies->removeStyle(Knopf::Style::Buffered);
    recipies->setRahmenBreite(1);
    Framework::LTDBDatei file;
    file.setDatei(new Framework::Text("data/images/gui_icons.ltdb"));
    file.leseDaten(0);
    recipies->setHintergrundBildZ(file.laden(0, new Text("recipies.png")));
    recipies->setMausEreignis([this](void* p, void* o, MausEreignis me) {
        if (me.id == ME_RLinks)
        {
            ((Game*)(Menu*)menuRegister->get("game"))->showItemList();
        }
        return 1;
    });
    recipies->setToolTipText("Recipies",
        uiFactory.initParam.bildschirm,
        uiFactory.initParam.schrift);
    setStyle(ZeichnungHintergrund::Style::Sichtbar
             | ZeichnungHintergrund::Style::Erlaubt);
    char* msg = new char[id.getLength() + 12 + 3];
    msg[0] = 0; // request inventory tooltip
    msg[1] = (char)id.getLength();
    memcpy(msg + 2, id.getText(), id.getLength());
    msg[2 + id.getLength()] = (char)12;
    memcpy(msg + 3 + id.getLength(), "CraftingGrid", 12);
    World::INSTANCE->zClient()->inventoryAPIRequest(
        target, msg, id.getLength() + 12 + 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)
        {
            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;
    });
}

CraftingGridView::~CraftingGridView()
{
    DragController<InventoryDragSource, int>* controller
        = ((Game*)(Menu*)menuRegister->get("game"))->zInventoryDragController();
    if (controller->getCurrentDragContainer() == this) controller->stopDrag();
    if (slots) slots->release();
    if (outputs) outputs->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;
    craft->release();
    recipies->release();
}

int CraftingGridView::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 CraftingGridView::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)
                    {
                        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;
            }
            break;
        }
    case 100: // set crafting result
        {
            Array<SlotInfo>* outputs = new Array<SlotInfo>();
            int count = *(int*)(++message);
            for (int i = 0; i < count; i++)
            {
                SlotInfo info;
                info.id = i;
                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();
                }
                outputs->add(info);
            }
            window->zBildschirm()->postAction([this, outputs]() {
                if (this->outputs) this->outputs->release();
                this->outputs = outputs;
            });
            break;
        }
    }
}

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

void CraftingGridView::render(Bild& rObj)
{
    ZeichnungHintergrund::render(rObj);
    if (!rObj.setDrawOptions(pos.x, pos.y, gr.x, gr.y)) return;
    int numRows = 1;
    if (slots)
    {
        int x = 0;
        int y = 0;
        int rowCount = 0;
        int index = 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;
                if (index < slots->getEintragAnzahl() - 1) numRows++;
            }
            index++;
        }
    }
    craft->render(rObj);
    recipies->setStyle(Knopf::Style::Sichtbar,
        !((Game*)(Menu*)menuRegister->get("game"))->isItemListVisible());
    recipies->render(rObj);
    rObj.fillRegion(rowSize * 60, gr.y / 2 - 5, 25, 10, 0xFF52525E);
    rObj.drawDreieck(Punkt(rowSize * 60 + 25, gr.y / 2 - 15),
        Punkt(rowSize * 60 + 40, gr.y / 2),
        Punkt(rowSize * 60 + 25, gr.y / 2 + 15),
        0xFF52525E);
    if (outputs)
    {
        int x = rowSize * 60 + 50;
        int y = 0;
        int colCount = 0;
        for (SlotInfo info : *outputs)
        {
            info.render(
                x, y, rObj, dragStartId == info.id, dragStopId == info.id);
            y += 60;
            if (++colCount >= numRows)
            {
                x += 60;
                y = 0;
                colCount = 0;
            }
        }
    }
    rObj.releaseDrawOptions();
}

void CraftingGridView::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;
        }
    }
    craft->doPublicMausEreignis(me);
    recipies->doPublicMausEreignis(me);
    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>>
CraftingGridView::getInventoryTarget() const
{
    return target;
}