#include "CraftingGrid.h"

#include <FileSystem.h>
#include <XML.h>

#include "DragController.h"
#include "Game.h"
#include "Globals.h"
#include "UIMLToolTip.h"
#include "UIMLUtils.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().isEqual("craftingGrid");
}

//! erstellt eine neue Drawable zu einem gegebenen xml Element
Framework::Drawable* CraftingGridElement::parseElement(
    Framework::XML::Element& element, Framework::UIMLContainer& generalFactory)
{
    Text targetValue = element.getAttributeValue("target");
    int addressLength = 0;
    int* address = getUIMLTargetAddress(targetValue, addressLength);
    return new CraftingGridView(element.getAttributeValue("id"),
        (int)element.getAttributeValue("rowSize"),
        (int)element.getAttributeValue("colSize"),
        (int)element.getAttributeValue("numOutputSlots"),
        address,
        addressLength);
}

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

//! wendet die layout parameter zu einer Drawable an
void CraftingGridElement::layout(Framework::XML::Element& element,
    Framework::Drawable& 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,
    int* address,
    int addressLength)
    : DrawableBackground(),
      NetworkAPIProcessor(),
      rowSize(rowSize),
      colSize(colSize),
      numOutputSlots(numOutputSlots),
      address(address),
      addressLength(addressLength),
      slots(0),
      outputs(0),
      id(id),
      dragStartId(-1),
      dragStopId(-1),
      currentTooltipSlot(-1),
      requestetTooltipSlot(-1)
{
    craft = uiFactory.createButton(uiFactory.initParam);
    craft->setPosition(rowSize * 60, 0);
    craft->setSize(40, 20);
    craft->setText("Craft");
    craft->setMouseEvent([this](void* p, void* o, MouseEvent me) {
        if (me.id == ME_RLeft)
        {
            char* msg = new char[2 + 4 * this->addressLength];
            msg[0] = 7; // request crafting
            msg[1] = this->addressLength < 4;
            for (int i = 0; i < this->addressLength; i++)
            {
                *(int*)(msg + 2 + i * 4)
                    = this->address[i]; // copy address to message
            }
            World::INSTANCE->zClient()->sendPlayerAction(
                msg, 2 + 4 * this->addressLength);
            delete[] msg;
        }
        return 1;
    });
    recipies = uiFactory.createButton(uiFactory.initParam);
    recipies->setPosition(rowSize * 60, colSize * 60 - 49);
    recipies->setSize(40, 40);
    recipies->addStyle(Button::Style::BImage | Button::Style::BAlpha
                       | Button::Style::Background);
    recipies->removeStyle(Button::Style::Buffered);
    recipies->setBorderWidth(1);
    Framework::LTDBFile file;
    file.setFile(new Framework::Text("data/images/gui_icons.ltdb"));
    file.readData(0);
    recipies->setBackgroundImageZ(file.load(0, new Text("recipies.png")));
    recipies->setMouseEvent([this](void* p, void* o, MouseEvent me) {
        if (me.id == ME_RLeft)
        {
            ((Game*)(Menu*)menuRegister->get("game"))->showItemList();
        }
        return 1;
    });
    recipies->setToolTipText(
        "Recipies", uiFactory.initParam.bildschirm, uiFactory.initParam.font);
    setStyle(DrawableBackground::Style::Visible
             | DrawableBackground::Style::Allowed);
    char* msg = new char[id.getLength() + 12 + 3 + 4];
    msg[0] = 0; // request inventory tooltip
    msg[1] = (char)id.getLength();
    memcpy(msg + 2, id.getText(), id.getLength());
    *(int*)(msg + 2 + id.getLength()) = NetworkAPIProcessor::getId();
    msg[6 + id.getLength()] = (char)12;
    memcpy(msg + 7 + id.getLength(), "CraftingGrid", 12);
    World::INSTANCE->zClient()->inventoryAPIRequest(
        addressLength < 4
            ? Framework::Either<int, Framework::VecN<int, 4>>(address[0])
            : Framework::Either<int, Framework::VecN<int, 4>>(
                  Framework::VecN<int, 4>{
                      address[0], address[1], address[2], address[3]}),
        msg,
        id.getLength() + 12 + 3 + 4);
    delete[] msg;
    setNeedToolTipEvent([this](Drawable* z, Point 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() + 10];
            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())
                = NetworkAPIProcessor::getId();
            *(int*)(msg + 6 + this->id.getLength()) = slot;
            World::INSTANCE->zClient()->inventoryAPIRequest(
                this->addressLength < 4
                    ? Framework::Either<int, Framework::VecN<int, 4>>(
                          this->address[0])
                    : Framework::Either<int, Framework::VecN<int, 4>>(
                          Framework::VecN<int, 4>{this->address[0],
                              this->address[1],
                              this->address[2],
                              this->address[3]}),
                msg,
                this->id.getLength() + 10);
            return 1;
        }
        return 0;
    });
}

CraftingGridView::~CraftingGridView()
{
    DragController<InventoryDragSource, int>* controller
        = menuRegister ? ((Game*)(Menu*)menuRegister->get("game"))
              ->zInventoryDragController() : 0;
    if (menuRegister && controller->getCurrentDragContainer() == this)
        controller->stopDrag();
    if (slots) slots->release();
    if (outputs) outputs->release();
    char* msg = new char[id.getLength() + 6];
    msg[0] = 1;
    msg[1] = (char)id.getLength();
    memcpy(msg + 2, id.getText(), id.getLength());
    *(int*)(msg + 2 + id.getLength()) = NetworkAPIProcessor::getId();
    World::INSTANCE->zClient()->inventoryAPIRequest(
        addressLength < 4
            ? Framework::Either<int, Framework::VecN<int, 4>>(address[0])
            : Framework::Either<int, Framework::VecN<int, 4>>(
                  Framework::VecN<int, 4>{
                      address[0], address[1], address[2], address[3]}),
        msg,
        id.getLength() + 6);
    delete[] msg;
    craft->release();
    recipies->release();
}

int CraftingGridView::getSlotByLocalPos(Point 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->zScreen()->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->getEntryCount(); 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->getEntryCount(); 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->zScreen()->postAction([this, outputs]() {
                if (this->outputs) this->outputs->release();
                this->outputs = outputs;
            });
            break;
        }
    }
}

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

void CraftingGridView::render(Image& rObj)
{
    DrawableBackground::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->getEntryCount() - 1) numRows++;
            }
            index++;
        }
    }
    craft->render(rObj);
    recipies->setStyle(Button::Style::Visible,
        !((Game*)(Menu*)menuRegister->get("game"))->isItemListVisible());
    recipies->render(rObj);
    rObj.fillRegion(rowSize * 60, gr.y / 2 - 5, 25, 10, 0xFF52525E);
    rObj.drawTriangle(Point(rowSize * 60 + 25, gr.y / 2 - 15),
        Point(rowSize * 60 + 40, gr.y / 2),
        Point(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::doMouseEvent(MouseEvent& me, bool userRet)
{
    mausPos.x = me.originalX;
    mausPos.y = me.originalY;
    if (!slots) return;
    if (me.id == ME_Move)
    {
        if (getSlotByLocalPos(Point(me.mx, me.my)) != currentTooltipSlot)
        {
            if (currentTooltipSlot != -1)
            {
                std::cout << "closing tooltip\n";
                setToolTipZ(0);
            }
            else
                toolTipRequested = 0;
            currentTooltipSlot = -1;
        }
    }
    craft->doPublicMouseEvent(me);
    recipies->doPublicMouseEvent(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_RLeft)
            {
                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
                            + (addressLength < 4 ? 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++] = addressLength < 4;
                    if (addressLength < 4)
                    {
                        *(int*)(msg + index) = address[0];
                        index += 4;
                    }
                    else
                    {
                        *(int*)(msg + index) = address[0];
                        *(int*)(msg + index + 4) = address[1];
                        *(int*)(msg + index + 8) = address[2];
                        *(int*)(msg + index + 12) = address[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++;
    }
    DrawableBackground::doMouseEvent(me, userRet);
}

Framework::Either<int, Framework::VecN<int, 4>>
CraftingGridView::getInventoryTarget() const
{
    return addressLength < 4
             ? Framework::Either<int, Framework::VecN<int, 4>>(address[0])
             : Framework::Either<int, Framework::VecN<int, 4>>(
                   Framework::VecN<int, 4>{
                       address[0], address[1], address[2], address[3]});
}