#include "QuestGraph.h"

#include <HashMap.h>
#include <Image.h>
#include <Scroll.h>
#include <XML.h>

#include "Globals.h"
#include "Load.h"
#include "World.h"

QuestGraphElement::QuestGraphElement() {}

bool QuestGraphElement::isApplicableFor(Framework::XML::Element& element)
{
    return element.getName().isEqual("questGraph");
}

Framework::Drawable* QuestGraphElement::parseElement(
    Framework::XML::Element& element, Framework::UIMLContainer& generalFactory)
{
    QuestGraph* graph = new QuestGraph();
    graph->collectionName = element.getAttributeValue("collectionName");
    graph->id = element.getAttributeValue("id");

    auto children = element.selectChildsByName("questGraphItem");
    Framework::HashMap<Framework::Text, QuestGraphItem*> map(
        100, [](Framework::Text str) { return str.hashCode(); });
    // parse graph elements
    for (Framework::XML::Element* itemElement : children)
    {
        Framework::Drawable* parsed
            = generalFactory.parseElement(*itemElement, generalFactory);
        QuestGraphItem* item = dynamic_cast<QuestGraphItem*>(parsed);
        if (!item)
        {
            if (parsed)
            {
                parsed->release();
            }
        }
        else
        {
            map.put(item->getId(), item);
        }
        generalFactory.layout(*itemElement,
            *parsed,
            graph->getWidth(),
            graph->getHeight(),
            generalFactory);
    }
    // set connection references
    int connectionId = -1;
    for (MapEntry<Framework::Text, QuestGraphItem*> entry : map)
    {
        QuestGraphItem* item = entry.getValue();
        Framework::Text requirements = item->getRequirements();
        while (requirements.getLength() > 0)
        {
            connectionId++;
            Framework::Text* part = 0;
            if (requirements.has("||"))
            {
                part = requirements.getTeilText(
                    0, requirements.positionOf("||"));
                requirements.remove(0, requirements.positionOf("||") + 2);
            }
            else
            {
                part = new Framework::Text(requirements);
                requirements = "";
            }
            while (part->getLength() > 0)
            {
                Framework::Text* requirement;
                if (part->has("&&"))
                {
                    requirement = part->getTeilText(0, part->positionOf("&&"));
                    part->remove(0, part->positionOf("&&") + 2);
                }
                else
                {
                    requirement = new Text(part->getText());
                    part->setText("");
                }
                requirement->removeWhitespaceAfter(0);
                requirement->removeWhitespaceBefore(requirement->getLength());
                QuestGraphItem* requiredItem = map.get(*requirement);
                if (requiredItem)
                {
                    requiredItem->addNextLayerRef({item, connectionId});
                    item->addPreviousLayerRef({requiredItem, connectionId});
                }
                requirement->release();
            }
            part->release();
        }
    }
    // calculate layer index based on connections
    bool changed = 1;
    while (changed)
    {
        changed = 0;
        for (MapEntry<Framework::Text, QuestGraphItem*> entry : map)
        {
            changed |= entry.getValue()->calculateLaxerIndex();
        }
    }
    // add items to graph
    for (MapEntry<Framework::Text, QuestGraphItem*> entry : map)
    {
        if (entry.getValue()->getLayerIndex() < 0)
        {
            entry.getValue()->setVirtual(
                1); // hide nodes witch layers could not be calculated because
                    // of invalid circular connections
        }
        graph->addItem(entry.getValue());
    }
    graph->addVirtualConnectionNodes();
    graph->sortItems();
    graph->fixItemPositions();
    return graph;
}

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

void QuestGraphElement::layout(Framework::XML::Element& element,
    Framework::Drawable& z,
    int pWidth,
    int pHeight,
    Framework::UIMLContainer& generalLayouter)
{
    UIMLElement::layout(element, z, pWidth, pHeight, generalLayouter);
    QuestGraph* graph = dynamic_cast<QuestGraph*>(&z);
    graph->centerVertically();
}

QuestGraphItemElement::QuestGraphItemElement() {}

bool QuestGraphItemElement::isApplicableFor(Framework::XML::Element& element)
{
    return element.getName().isEqual("questGraphItem");
}

Framework::Drawable* QuestGraphItemElement::parseElement(
    Framework::XML::Element& element, Framework::UIMLContainer& generalFactory)
{
    QuestGraphItem* item = new QuestGraphItem();
    Framework::Text name = element.getAttributeValue("name");
    Framework::Text description = element.getAttributeValue("description");
    item->setToolTipText(name + "\n" + description,
        generalFactory.getFactory().initParam.bildschirm,
        generalFactory.getFactory().initParam.font);
    item->setBackgroundImageZ(loadImage(element.getAttributeValue("image")));
    item->finished = (int)element.getAttributeValue("finished") != 0;
    if (item->finished)
    {
        item->setBorderColor(0xFF55FF00);
    }
    item->rewarded = (int)element.getAttributeValue("rewarded") != 0;
    item->mainQuest = (int)element.getAttributeValue("mainQuest") != 0;
    if (item->mainQuest)
    {
        item->setBorderWidth(3);
    }
    item->id = element.getAttributeValue("id");
    item->requirements = element.getAttributeValue("requirements");
    item->virtualNode = 0;
    return item;
}

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

void QuestGraphItemElement::layout(Framework::XML::Element& element,
    Framework::Drawable& z,
    int pWidth,
    int pHeight,
    Framework::UIMLContainer& generalLayouter)
{
    UIMLElement::layout(element, z, pWidth, pHeight, generalLayouter);
}

Connection::Connection(QuestGraphItem* zSource)
    : ReferenceCounter(),
      zSource(zSource),
      vx(0),
      minY(0),
      maxY(0)
{}

void Connection::renderVertical(Framework::Image& rObj)
{
    if (targets.getEntryCount())
    {
        rObj.drawLineV(
            vx, minY, maxY - minY, isActive() ? 0xFFFFFFFF : 0xFF52525E);
    }
}

void Connection::renderHorizontal(Framework::Image& rObj)
{
    if (targets.getEntryCount())
    {
        Framework::Point start = zSource->getPosition();
        start.x += zSource->getWidth();
        start.y += zSource->getHeight() / 2;
        rObj.drawLineH(start.x, start.y - 2, vx - start.x, 0xA0000000);
        rObj.drawLineH(start.x, start.y - 1, vx - start.x, 0xA0000000);
        rObj.drawLineH(start.x,
            start.y,
            vx - start.x,
            isActive() ? 0xFFFFFFFF : 0xFF52525E);
        rObj.drawLineH(start.x, start.y + 1, vx - start.x, 0xA0000000);
        rObj.drawLineH(start.x, start.y + 2, vx - start.x, 0xA0000000);
        for (const ConnectionTarget& target : targets)
        {
            Framework::Point end
                = target.zTarget->getTargetPosition(target.targetIndex);
            rObj.drawLineH(vx + 1, end.y - 2, end.x - vx - 1, 0xA0000000);
            rObj.drawLineH(vx + 1, end.y - 1, end.x - vx - 1, 0xA0000000);
            rObj.drawLineH(vx + 1,
                end.y,
                end.x - vx - 1,
                isActive() ? 0xFFFFFFFF : 0xFF52525E);
            rObj.drawLineH(vx + 1, end.y + 1, end.x - vx - 1, 0xA0000000);
            rObj.drawLineH(vx + 1, end.y + 2, end.x - vx - 1, 0xA0000000);
        }
    }
}

void Connection::setVx(int vx)
{
    this->vx = vx;
}

void Connection::addConnectionTarget(ConnectionTarget target)
{
    targets.add(target);
}

void Connection::setTargetIndex(AndNode* zTarget, int index)
{
    for (auto target = targets.begin(); target; target++)
    {
        if (target.val().zTarget == zTarget)
        {
            target.set({index, target.val().zTarget});
            return;
        }
    }
}

void Connection::layout()
{
    minY = 0;
    maxY = 0;
    bool start = 1;
    for (const ConnectionTarget& target : targets)
    {
        Framework::Point end
            = target.zTarget->getTargetPosition(target.targetIndex);
        if (start || end.y < minY)
        {
            minY = end.y;
        }
        if (start || end.y > maxY)
        {
            maxY = end.y;
        }
        start = 0;
    }
    Framework::Point origin = zSource->getPosition();
    origin.x += zSource->getWidth();
    origin.y += zSource->getHeight() / 2;
    if (minY > origin.y) minY = origin.y;
    if (maxY < origin.y) maxY = origin.y;
    maxY++;
}

bool Connection::isActive() const
{
    return zSource->isFinished();
}

int Connection::getOrderNum() const
{
    return zSource->getNodeIndex();
}

int Connection::getTargetCount() const
{
    return targets.getEntryCount();
}

int Connection::getVerticalLength() const
{
    return maxY - minY;
}

int Connection::getVx() const
{
    return vx;
}

bool Connection::hasTarget(AndNode* zAndNode, int index) const
{
    for (auto target : targets)
    {
        if (target.zTarget == zAndNode && target.targetIndex == index)
        {
            return true;
        }
    }
    return false;
}

AndNode::AndNode()
    : DrawableBackground()
{
    setStyle(
        DrawableBackground::Style::Visible | DrawableBackground::Style::Border);
    setBorderWidth(1);
    setBorderColor(0xFF52525E);
    tr.setFontSize(12);
    tr.setFontZ(dynamic_cast<Font*>(uiFactory.initParam.font->getThis()));
}

void AndNode::addConnection(Connection* zConnection)
{
    int pos = 0;
    for (Connection* other : connections)
    {
        if (other->getOrderNum() > zConnection->getOrderNum()) break;
        pos++;
    }
    zConnection->addConnectionTarget({pos, this});
    connections.add(zConnection, pos);
    pos = 0;
    for (Connection* con : connections)
    {
        con->setTargetIndex(this, pos);
        pos++;
    }
}

void AndNode::render(Framework::Image& rObj)
{
    setBorderColor(isActive() ? 0xFFFFFFFF : 0xFF52525E);
    if (connections.getEntryCount() > 1)
    {
        DrawableBackground::render(rObj);
        if (rObj.setDrawOptions(pos.x + getBorderWidth(),
                pos.y + getBorderWidth(),
                getInnerWidth(),
                getInnerHeight()))
        {
            tr.renderText(getInnerWidth() / 2 - tr.getTextWidth("&") / 2,
                getInnerHeight() / 2 - tr.getTextHeight("&") / 2,
                "&",
                rObj,
                isActive() ? 0xFFFFFFFF : 0xFF52525E);
            rObj.releaseDrawOptions();
        }
    }
    else if (connections.getEntryCount() > 0)
    {
        if (rObj.setDrawOptions(pos, gr))
        {
            rObj.drawLineH(0,
                0,
                gr.x,
                connections.get(0)->isActive() ? 0xFFFFFFFF : 0xFF52525E);
            rObj.releaseDrawOptions();
        }
    }
}

Framework::Point AndNode::getTargetPosition(int index)
{
    if (connections.getEntryCount() == 1)
    {
        return pos;
    }
    return pos + Framework::Point(0, 10 + index * 11);
}

void AndNode::layout()
{
    if (connections.getEntryCount() == 1)
    {
        setSize(20, 1);
    }
    else
    {
        setSize(20, 10 + connections.getEntryCount() * 11);
    }
}

bool AndNode::isActive() const
{
    for (Connection* connection : connections)
    {
        if (!connection->isActive()) return false;
    }
    return connections.getEntryCount() > 0;
}

int AndNode::getConnectionCount() const
{
    return connections.getEntryCount();
}

OrConnection::OrConnection(QuestGraphItem* zTarget,
    const Framework::Array<ConnectionInfo>& connections)
    : ReferenceCounter(),
      zTarget(zTarget),
      minY(0),
      maxY(0)
{
    int currId = -1;
    AndNode* currNode = 0;
    for (const ConnectionInfo& info : connections)
    {
        if (info.id != currId)
        {
            currNode = new AndNode();
            andNodes.add(currNode);
            currId = info.id;
        }
        currNode->addConnection(info.target->zOutgoingConnection());
    }
}

void OrConnection::render(Framework::Image& rObj)
{
    if (andNodes.getEntryCount() == 0) return;
    bool active = isActive();
    rObj.drawLineV(zTarget->getX() - 11,
        minY,
        maxY - minY,
        active ? 0xFFFFFFFF : 0xFF52525E);
    rObj.drawLineH(zTarget->getX() - 10,
        zTarget->getY() + zTarget->getHeight() / 2,
        10,
        active ? 0xFFFFFFFF : 0xFF52525E);
    for (AndNode* node : andNodes)
    {
        rObj.drawLineH(node->getX() + node->getWidth(),
            node->getY() + node->getHeight() / 2,
            10,
            active ? 0xFFFFFFFF : 0xFF52525E);
        node->render(rObj);
    }
}

void OrConnection::layout()
{
    int nodeHeight = -10;
    for (AndNode* node : andNodes)
    {
        node->layout();
        nodeHeight += node->getHeight() + 10;
    }
    int y = zTarget->getY() + zTarget->getHeight() / 2 - nodeHeight / 2;
    bool start = 1;
    AndNode* last = 0;
    for (AndNode* node : andNodes)
    {
        node->setPosition(zTarget->getX() - 21 - node->getWidth(), y);
        if (start)
        {
            minY = y + node->getHeight() / 2;
        }
        y += node->getHeight() + 10;
        start = 0;
        last = node;
    }
    if (last)
    {
        y -= 10 + last->getHeight();
        maxY = y + last->getHeight() / 2 + 1;
    }
    else
    {
        maxY = minY + 1;
    }
}

int OrConnection::getNeededHeight() const
{
    int minY = 0;
    int maxY = 0;
    bool first = 1;
    for (AndNode* node : andNodes)
    {
        if (first || node->getY() < minY)
        {
            minY = node->getY();
        }
        if (first || node->getY() + node->getHeight() > maxY)
        {
            maxY = node->getY() + node->getHeight();
        }
        first = 0;
    }
    return maxY - minY;
}

bool OrConnection::isActive() const
{
    for (AndNode* node : andNodes)
    {
        if (node->isActive()) return 1;
    }
    return 0;
}

bool OrConnection::isHorizontalLineConflict(
    int y, Connection* zIgnoredIncommingConnection) const
{
    for (AndNode* node : andNodes)
    {
        for (int i = 0; i < node->getConnectionCount(); i++)
        {
            if (!zIgnoredIncommingConnection
                || !zIgnoredIncommingConnection->hasTarget(node, i))
            {
                Point connection = node->getTargetPosition(i);
                if (abs(y - connection.y) < 5) return 1;
            }
        }
    }
    return 0;
}

QuestGraphItem::QuestGraphItem()
    : DrawableBackground(),
      layerIndex(-1),
      nodeIndex(-1),
      outgoingConnection(0),
      incommingConnection(0),
      finished(0),
      rewarded(0),
      mainQuest(0),
      virtualNode(0),
      animationProgress(0)
{
    addStyle(
        DrawableBackground::Style::Visible | DrawableBackground::Style::Border
        | DrawableBackground::Style::Allowed | DrawableBackground::Style::BImage
        | DrawableBackground::Style::BImageScale
        | DrawableBackground::Style::BAlpha
        | DrawableBackground::Style::Background);
    setBorderColor(0xFF52525E);
    setBorderWidth(1);
    setMouseEvent(Framework::_ret1ME);
}

QuestGraphItem::~QuestGraphItem()
{
    if (outgoingConnection) outgoingConnection->release();
    if (incommingConnection) incommingConnection->release();
}

bool QuestGraphItem::tick(double tickVal)
{
    animationProgress += tickVal * 20;
    if (animationProgress > getWidth() * 2 + getHeight() * 2 - 4)
    {
        animationProgress -= getWidth() * 2 + getHeight() * 2 - 4;
    }
    rend = 1;
    return DrawableBackground::tick(tickVal);
}

int getBorderX(int p, int width, int height)
{
    if (p < width)
    {
        return p;
    }
    else if (p < width + height - 1)
    {
        return width - 1;
    }
    else if (p < width * 2 + height - 2)
    {
        return width - (p - width - height + 2);
    }
    else
    {
        return 0;
    }
}

int getBorderY(int p, int width, int height)
{
    if (p < width)
    {
        return 0;
    }
    else if (p < width + height - 1)
    {
        return p - width + 1;
    }
    else if (p < width * 2 + height - 2)
    {
        return height - 1;
    }
    else
    {
        return height - (p - width * 2 - height + 3);
    }
}

void QuestGraphItem::render(Framework::Image& rObj)
{
    if (incommingConnection) incommingConnection->render(rObj);
    if (isVirtual())
    {
        rObj.drawLineH(
            pos.x, pos.y, gr.x, isFinished() ? 0xFFFFFFFF : 0xFF52525E);
        return;
    }
    DrawableBackground::render(rObj);
    if (finished && !rewarded)
    {
        if (rObj.setDrawOptions(pos.x, pos.y, gr.x, gr.y))
        {
            for (int i = 0; i < 7; i++)
            {
                int p
                    = ((int)animationProgress + i) % (gr.x * 2 + gr.y * 2 - 4);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) - 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) - 1,
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) + 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) + 1,
                    0xFFFFFF00);
                p = ((int)animationProgress + i + gr.x - 1)
                  % (gr.x * 2 + gr.y * 2 - 4);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) - 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) - 1,
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) + 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) + 1,
                    0xFFFFFF00);
                p = ((int)animationProgress + i + gr.x + gr.y - 2)
                  % (gr.x * 2 + gr.y * 2 - 4);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) - 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) - 1,
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) + 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) + 1,
                    0xFFFFFF00);
                p = ((int)animationProgress + i + gr.x * 2 + gr.y - 3)
                  % (gr.x * 2 + gr.y * 2 - 4);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) - 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) - 1,
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y) + 1,
                    getBorderY(p, gr.x, gr.y),
                    0xFFFFFF00);
                rObj.setPixelDP(getBorderX(p, gr.x, gr.y),
                    getBorderY(p, gr.x, gr.y) + 1,
                    0xFFFFFF00);
            }
            rObj.releaseDrawOptions();
        }
    }
}

void QuestGraphItem::renderVerticalConnections(Framework::Image& rObj)
{
    if (outgoingConnection) outgoingConnection->renderVertical(rObj);
}

void QuestGraphItem::renderHorizontalConnections(Framework::Image& rObj)
{
    if (outgoingConnection) outgoingConnection->renderHorizontal(rObj);
}

void QuestGraphItem::replacePreviousConnections(
    QuestGraphItem* zBefore, QuestGraphItem* zAfter)
{
    for (auto con = previousLayersConnections.begin(); con; con++)
    {
        if (con.val().target == zBefore) con.set({zAfter, con.val().id});
    }
}

const Framework::Text& QuestGraphItem::getId() const
{
    return id;
}

const Framework::Text& QuestGraphItem::getRequirements() const
{
    return requirements;
}

bool QuestGraphItem::calculateLaxerIndex()
{
    int oldLayerIndex = layerIndex;
    int maxLayerIndex = -1;
    for (const ConnectionInfo& item : previousLayersConnections)
    {
        if (item.target->getLayerIndex() < 0) return 0;
        if (item.target->getLayerIndex() > maxLayerIndex)
        {
            maxLayerIndex = item.target->getLayerIndex();
        }
    }
    layerIndex = maxLayerIndex + 1;
    return oldLayerIndex != layerIndex;
}

int QuestGraphItem::getLayerIndex() const
{
    return layerIndex;
}

void QuestGraphItem::setVirtual(bool virtualNode)
{
    this->virtualNode = virtualNode;
    gr.y = virtualNode ? 1 : gr.y;
    gr.x = 1;
    setStyle(DrawableBackground::Style::Visible, virtualNode);
}

void QuestGraphItem::setNodeIndex(int index)
{
    this->nodeIndex = index;
}

void QuestGraphItem::addNextLayerRef(ConnectionInfo zItem)
{
    nextLayersConnections.add(zItem);
}

void QuestGraphItem::addPreviousLayerRef(ConnectionInfo zItem)
{
    previousLayersConnections.add(zItem);
}

void QuestGraphItem::initializeConnections()
{
    outgoingConnection = new Connection(this);
    incommingConnection = new OrConnection(this, previousLayersConnections);
}

const Framework::Array<ConnectionInfo>&
QuestGraphItem::getNextLayersConnections() const
{
    return nextLayersConnections;
}

int QuestGraphItem::getNodeIndex() const
{
    return nodeIndex;
}

bool QuestGraphItem::isMainQuest() const
{
    return mainQuest;
}

bool QuestGraphItem::isVirtual() const
{
    return virtualNode;
}

bool QuestGraphItem::isFinished() const
{
    return virtualNode ? incommingConnection->isActive() : finished;
}

Connection* QuestGraphItem::zOutgoingConnection() const
{
    return outgoingConnection;
}

OrConnection* QuestGraphItem::zIncommingConnection() const
{
    return incommingConnection;
}

QuestGraphItemLayer::QuestGraphItemLayer()
    : ReferenceCounter()
{}

bool QuestGraphItemLayer::tick(double tickVal)
{
    bool result = 0;
    for (QuestGraphItem* item : items)
    {
        result |= item->tick(tickVal);
    }
    return result;
}

void QuestGraphItemLayer::render(Framework::Image& rObj)
{
    for (QuestGraphItem* item : items)
    {
        item->render(rObj);
        item->renderVerticalConnections(rObj);
    }
    for (QuestGraphItem* item : items)
    {
        item->renderHorizontalConnections(rObj);
    }
}

void QuestGraphItemLayer::doMouseEvent(Framework::MouseEvent& me)
{
    for (QuestGraphItem* item : items)
    {
        item->doPublicMouseEvent(me);
    }
}

void QuestGraphItemLayer::addItem(QuestGraphItem* item)
{
    items.add(item);
    item->setNodeIndex(items.getEntryCount() - 1);
}

bool QuestGraphItemLayer::sortItems()
{
    bool changed = 0;
    for (int index = 0; index < items.getEntryCount() - 1; index++)
    {
        QuestGraphItem* current = items.z(index);
        int conflicts = 0;
        int afterSwapConflicts = 0;
        QuestGraphItem* other = items.z(index + 1);
        for (const ConnectionInfo& connection :
            current->getNextLayersConnections())
        {
            for (const ConnectionInfo& otherConnection :
                other->getNextLayersConnections())
            {
                if ((otherConnection.target->getNodeIndex()
                        < connection.target->getNodeIndex()))
                {
                    conflicts++;
                }
                if ((otherConnection.target->getNodeIndex()
                        > connection.target->getNodeIndex()))
                {
                    afterSwapConflicts++;
                }
            }
        }
        if (conflicts > afterSwapConflicts)
        {
            // move node down
            QuestGraphItem* after = items.z(index + 1);
            after->setNodeIndex(index);
            current->setNodeIndex(index + 1);
            items.swap(index, index + 1);
            changed = 1;
        }
    }
    return changed;
}

int QuestGraphItemLayer::fixItemPositions(int x)
{
    // calculate size needed for & nodes and | nodes
    int maxWidth = 0;
    for (QuestGraphItem* item : items)
    {
        item->initializeConnections();
        item->zIncommingConnection()->layout();
        if (item->getWidth() > maxWidth)
        {
            maxWidth = item->getWidth();
        }
    }
    x += 20 + 21; // ofset for incomming connections
    int y = 0;
    // calculate y positions of nodes
    for (QuestGraphItem* item : items)
    {
        int height = 0;
        int lastId = -1;
        int nodeHeight = item->zIncommingConnection()->getNeededHeight();
        if (nodeHeight < item->getHeight()) nodeHeight = item->getHeight();
        item->setPosition(x + maxWidth / 2 - item->getWidth() / 2,
            y + nodeHeight / 2 - item->getHeight() / 2);
        y += nodeHeight + 20;
    }
    x += maxWidth; // this layers node size
    for (QuestGraphItem* item : items)
    {
        item->zIncommingConnection()->layout();
        item->zOutgoingConnection()->setVx(x);
        item->zOutgoingConnection()->layout();
    }
    x += items.getEntryCount() * 11 + 10; // offset for outgoing connections
    return x + 20;                        // min space between layers
}

void QuestGraphItemLayer::sortConnections()
{
    for (QuestGraphItem* item : items)
    {
        item->zIncommingConnection()->layout();
        item->zOutgoingConnection()->layout();
    }
    int connectionCount = 0;
    bool* sorted = new bool[items.getEntryCount()];
    memset(sorted, 0, sizeof(bool) * items.getEntryCount());
    int sortedCount = 0;
    while (true)
    {
        int minHeight = -1;
        QuestGraphItem* next = 0;
        for (QuestGraphItem* item : items)
        {
            if (sorted[item->getNodeIndex()]) continue;
            if (item->zOutgoingConnection()->getTargetCount()
                == connectionCount)
            {
                if (minHeight < 0
                    || item->zOutgoingConnection()->getVerticalLength()
                           < minHeight)
                {
                    minHeight
                        = item->zOutgoingConnection()->getVerticalLength();
                    next = item;
                }
            }
        }
        if (!next)
        {
            if (sortedCount < items.getEntryCount())
            {
                connectionCount++;
            }
            else
            {
                break;
            }
        }
        else
        {
            next->zOutgoingConnection()->setVx(
                next->zOutgoingConnection()->getVx() + 10 + sortedCount * 11);
            sorted[next->getNodeIndex()] = 1;
            sortedCount++;
        }
    }
    delete[] sorted;
}

const Framework::RCArray<QuestGraphItem>& QuestGraphItemLayer::getItems() const
{
    return items;
}

int QuestGraphItemLayer::getLeyerHeight(int& minY) const
{
    minY = 0;
    int end = 0;
    bool first = 1;
    for (QuestGraphItem* item : items)
    {
        if (first || item->getY() < minY) minY = item->getY();
        if (first || item->getY() + item->getHeight() > end)
            end = item->getY() + item->getHeight();
        first = 0;
    }
    return end - minY;
}

int QuestGraphItemLayer::getLeyerHeight() const
{
    int y;
    return getLeyerHeight(y);
}

void QuestGraphItemLayer::centerVertically(int pos)
{
    int minY = 0;
    int height = getLeyerHeight(minY);
    int yOffset = pos - height / 2 - minY;
    for (QuestGraphItem* item : items)
    {
        item->setPosition(item->getX(), item->getY() + yOffset);
        item->zIncommingConnection()->layout();
        item->zOutgoingConnection()->layout();
    }
}

void QuestGraphItemLayer::resolveHorizontalConflicts(
    const QuestGraphItemLayer* zNextLayer)
{
    for (QuestGraphItem* item : items)
    {
        int outgoingY = item->getY() + item->getHeight() / 2;
        if (zNextLayer->isHorizontalLineConflict(
                outgoingY, item->zOutgoingConnection()))
        {
            int offset = 0;
            for (int i = 1; i <= 10; i++)
            {
                if (!zNextLayer->isHorizontalLineConflict(
                        outgoingY - i, item->zOutgoingConnection()))
                {
                    offset = -i;
                    break;
                }
                if (!zNextLayer->isHorizontalLineConflict(
                        outgoingY + i, item->zOutgoingConnection()))
                {
                    offset = i;
                    break;
                }
            }
            item->setPosition(item->getX(), item->getY() + offset);
            item->zIncommingConnection()->layout();
            item->zOutgoingConnection()->layout();
        }
    }
}

bool QuestGraphItemLayer::isHorizontalLineConflict(
    int y, Connection* zIgnoredIncommingConnection) const
{
    for (QuestGraphItem* item : items)
    {
        if (item->zIncommingConnection()->isHorizontalLineConflict(
                y, zIgnoredIncommingConnection))
            return 1;
    }
    return 0;
}

QuestGraph::QuestGraph()
    : DrawableBackground()
{
    addStyle(DrawableBackground::Style::Visible
             | DrawableBackground::Style::Allowed
             | DrawableBackground::Style::Border);
    setBorderWidth(1);
    setBorderColor(0xFF52525E);
    setMouseEvent(Framework::_ret1ME);
}

bool QuestGraph::tick(double tickVal)
{
    for (QuestGraphItemLayer* layer : layers)
    {
        rend |= layer->tick(tickVal);
    }
    return DrawableBackground::tick(tickVal);
}

void QuestGraph::render(Framework::Image& rObj)
{
    if (hasStyle(DrawableBackground::Style::Visible)
        && layers.getEntryCount() > 0)
    {
        DrawableBackground::render(rObj);
        if (rObj.setDrawOptions(pos, gr))
        {
            if (rObj.setDrawOptions(getBorderWidth(),
                    getBorderWidth(),
                    getInnerWidth(),
                    getInnerHeight()))
            {
                int hScrollOffset = 0;
                if (hasStyle(DrawableBackground::Style::HScroll)
                    && horizontalScrollBar)
                {
                    hScrollOffset = horizontalScrollBar->getScroll();
                }
                int vScrollOffset = 0;
                if (hasStyle(DrawableBackground::Style::VScroll)
                    && vertikalScrollBar)
                {
                    vScrollOffset = vertikalScrollBar->getScroll();
                }
                if (hScrollOffset || vScrollOffset)
                {
                    rObj.addScrollOffset(hScrollOffset, vScrollOffset);
                }
                for (QuestGraphItemLayer* layer : layers)
                {
                    layer->render(rObj);
                }
                rObj.releaseDrawOptions();
            }
            rObj.releaseDrawOptions();
        }
    }
}

void QuestGraph::doMouseEvent(Framework::MouseEvent& me, bool userRet)
{
    userRet &= hasStyle(DrawableBackground::Style::Visible);
    bool vera = me.processed;
    if (!userRet)
    {
        me.processed = 1;
    }
    for (QuestGraphItemLayer* layer : layers)
    {
        layer->doMouseEvent(me);
    }
    if (!userRet)
    {
        me.processed = vera;
    }
}

void QuestGraph::addVirtualConnectionNodes()
{
    int layerIndex = 0;
    // add virtual items for connections that do not go directly to the next
    // layer after this operation each node only have connections to the
    // immediate following layer
    for (QuestGraphItemLayer* layer : layers)
    {
        for (int i = 0; i < layer->getItems().getEntryCount(); i++)
        {
            QuestGraphItem* item = layer->getItems().z(i);
            auto iterator = item->getNextLayersConnections().begin();
            QuestGraphItem* virtualItem = 0;
            while (iterator)
            {
                if (iterator.val().target->getLayerIndex() > layerIndex + 1)
                {
                    if (!virtualItem)
                    {
                        virtualItem = new QuestGraphItem();
                        virtualItem->addPreviousLayerRef(
                            {item, iterator.val().id});
                        virtualItem->addNextLayerRef(
                            {iterator.val().target, iterator.val().id});
                        iterator.val().target->replacePreviousConnections(
                            item, virtualItem);
                        virtualItem->setVirtual(true);
                        virtualItem->calculateLaxerIndex();
                        iterator.set({virtualItem, iterator.val().id});
                        iterator++;
                    }
                    else
                    {
                        virtualItem->addNextLayerRef(
                            {iterator.val().target, iterator.val().id});
                        iterator.val().target->replacePreviousConnections(
                            item, virtualItem);
                        iterator.remove();
                    }
                }
                else if (iterator.val().target->getLayerIndex() < 0)
                { // remove connections to invalid nodes
                    iterator.remove();
                }
                else
                {
                    iterator++;
                }
            }
            if (virtualItem)
            {
                addItem(virtualItem);
            }
        }
        layerIndex++;
    }
}

void QuestGraph::sortItems()
{
    bool changed = 1;
    while (changed)
    {
        changed = 0;
        for (QuestGraphItemLayer* layer : layers)
        {
            changed |= layer->sortItems();
        }
    }
}

void QuestGraph::fixItemPositions()
{
    int height = 0;
    int x = 0;
    for (QuestGraphItemLayer* layer : layers)
    {
        x = layer->fixItemPositions(x);
        if (layer->getLeyerHeight() > height)
        {
            height = layer->getLeyerHeight();
        }
    }
    for (QuestGraphItemLayer* layer : layers)
    {
        layer->sortConnections();
    }
    if (height > getInnerHeight())
    {
        addStyle(DrawableBackground::Style::VScroll);
        setVerticalClickScroll(10);
        vertikalScrollBar->update(height, getInnerHeight());
    }
    if (layers.getEntryCount() * 150 - 80 > getInnerWidth())
    {
        addStyle(DrawableBackground::Style::HScroll);
        setHorizontalClickScroll(10);
        horizontalScrollBar->update(
            layers.getEntryCount() * 150 - 80, getInnerWidth());
    }
    QuestGraphItemLayer* last = 0;
    for (int i = layers.getEntryCount() - 1; i >= 0; i--)
    {
        QuestGraphItemLayer* layer = layers.z(i);
        layer->centerVertically(getHeight() / 2);
        if (last) layer->resolveHorizontalConflicts(last);
        last = layer;
    }
}

void QuestGraph::centerVertically()
{
    int height = 0;
    for (QuestGraphItemLayer* layer : layers)
    {
        if (layer->getLeyerHeight() > height)
        {
            height = layer->getLeyerHeight();
        }
    }
    if (height > getInnerHeight())
    {
        addStyle(DrawableBackground::Style::VScroll);
        setVerticalClickScroll(10);
        vertikalScrollBar->update(height, getInnerHeight());
    }
    if (layers.getEntryCount() * 150 - 80 > getInnerWidth())
    {
        addStyle(DrawableBackground::Style::HScroll);
        setHorizontalClickScroll(10);
        horizontalScrollBar->update(
            layers.getEntryCount() * 150 - 80, getInnerWidth());
    }
    QuestGraphItemLayer* last = 0;
    for (int i = layers.getEntryCount() - 1; i >= 0; i--)
    {
        QuestGraphItemLayer* layer = layers.z(i);
        layer->centerVertically(getHeight() / 2);
        if (last) layer->resolveHorizontalConflicts(last);
        last = layer;
    }
}

void QuestGraph::addItem(QuestGraphItem* item)
{
    int layerInex = item->getLayerIndex();
    if (layerInex >= 0)
    {
        while (layerInex >= layers.getEntryCount())
        {
            layers.add(new QuestGraphItemLayer());
        }
        layers.z(layerInex)->addItem(item);
    }
    else
    {
        invalidNodes.addItem(item);
    }
}
