#include "QuestGraph.h"

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

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

QuestGraphElement::QuestGraphElement() {}

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

Framework::Zeichnung* 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::Zeichnung* 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->getBreite(),
            graph->getHeight(),
            generalFactory);
    }
    // set connection references
    for (MapEntry<Framework::Text, QuestGraphItem*> entry : map)
    {
        QuestGraphItem* item = entry.getValue();
        Framework::Text requirements = item->getRequirements();
        while (requirements.getLength() > 0)
        {
            Framework::Text* requirement;
            if (requirements.hat(","))
            {
                requirement = requirements.getTeilText(
                    0, requirements.positionVon(","));
                requirements.remove(0, requirements.positionVon(",") + 1);
            }
            else
            {
                requirement = new Text(requirements);
                requirements = "";
            }
            QuestGraphItem* requiredItem = map.get(*requirement);
            if (requiredItem)
            {
                requiredItem->addNextLayerRef(item);
                item->addPreviousLayerRef(requiredItem);
            }
            requirement->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();
    return graph;
}

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

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

QuestGraphItemElement::QuestGraphItemElement() {}

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

Framework::Zeichnung* 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.schrift);
    item->setHintergrundBildZ(loadImage(element.getAttributeValue("image")));
    item->finished = (int)element.getAttributeValue("finished") != 0;
    if (item->finished)
    {
        item->setRahmenFarbe(0xFF55FF00);
    }
    item->mainQuest = (int)element.getAttributeValue("mainQuest") != 0;
    if (item->mainQuest)
    {
        item->setRahmenBreite(3);
    }
    item->id = element.getAttributeValue("id");
    item->requirements = element.getAttributeValue("requiredQuests");
    item->virtualNode = 0;
    return item;
}

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

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

QuestGraphItem::QuestGraphItem()
    : ZeichnungHintergrund(),
      layerIndex(-1),
      nodeIndex(-1)
{
    addStyle(ZeichnungHintergrund::Style::Sichtbar
             | ZeichnungHintergrund::Style::Rahmen
             | ZeichnungHintergrund::Style::Erlaubt
             | ZeichnungHintergrund::Style::HBild
             | ZeichnungHintergrund::Style::HBildScale
             | ZeichnungHintergrund::Style::HAlpha
             | ZeichnungHintergrund::Style::Hintergrund);
    setRahmenFarbe(0xFF52525E);
    setRahmenBreite(1);
    setMausEreignis(Framework::_ret1ME);
}

void QuestGraphItem::render(Framework::Bild& rObj)
{
    ZeichnungHintergrund::render(rObj);
    // TODO
}

void QuestGraphItem::doMausEreignis(Framework::MausEreignis& me, bool userRet)
{
    // TODO
}

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 (QuestGraphItem* item : previousLayersConnections)
    {
        if (item->getLayerIndex() < 0) return 0;
        if (item->getLayerIndex() > maxLayerIndex)
        {
            maxLayerIndex = item->getLayerIndex();
        }
    }
    layerIndex = maxLayerIndex + 1;
    return oldLayerIndex != layerIndex;
}

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

void QuestGraphItem::setVirtual(bool virtualNode)
{
    this->virtualNode = virtualNode;
    setStyle(ZeichnungHintergrund::Style::Sichtbar, virtualNode);
}

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

void QuestGraphItem::addNextLayerRef(QuestGraphItem* zItem)
{
    previousLayersConnections.add(zItem);
}

void QuestGraphItem::addPreviousLayerRef(QuestGraphItem* zItem)
{
    nextLayersConnections.add(zItem);
}

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

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

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

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::Bild& rObj)
{
    for (QuestGraphItem* item : items)
    {
        item->render(rObj);
    }
}

void QuestGraphItemLayer::doMausEreignis(Framework::MausEreignis& me)
{
    for (QuestGraphItem* item : items)
    {
        item->doPublicMausEreignis(me);
    }
}

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

bool QuestGraphItemLayer::sortItems()
{
    bool changed = 0;
    for (int index = 0; index < items.getEintragAnzahl(); index++)
    {
        QuestGraphItem* current = items.z(index);
        int beforeConflicts = 0;
        int afterConflicts = 0;
        for (QuestGraphItem* connection : current->getNextLayersConnections())
        {
            for (int j = 0; j < items.getEintragAnzahl(); j++)
            {
                if (index != j)
                {
                    QuestGraphItem* other = items.z(j);
                    for (QuestGraphItem* otherConnection :
                        other->getNextLayersConnections())
                    {
                        if (j < index
                            && otherConnection->getNodeIndex()
                                   > connection->getNodeIndex())
                        {
                            beforeConflicts++;
                        }
                        if ((j > index
                                && otherConnection->getNodeIndex()
                                       < connection->getNodeIndex()))
                        {
                            afterConflicts++;
                        }
                    }
                }
            }
        }
        if (afterConflicts > beforeConflicts)
        {
            // move node down
            QuestGraphItem* after = items.z(index + 1);
            after->setNodeIndex(index);
            current->setNodeIndex(index + 1);
            items.tausch(index, index + 1);
            changed = 1;
        }
        else if (afterConflicts < beforeConflicts)
        {
            // move node up
            QuestGraphItem* before = items.z(index - 1);
            before->setNodeIndex(index);
            current->setNodeIndex(index - 1);
            items.tausch(index - 1, index);
            changed = 1;
        }
    }
    return changed;
}

bool QuestGraphItemLayer::fixItemPositions()
{
    int y = 0;
    for (QuestGraphItem* item : items)
    {
        item->setPosition(
            item->getLayerIndex() * 150 + 10 - item->isMainQuest() * 3, y);
        y += item->getHeight() + 20;
    }
    return 0;
}

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

int QuestGraphItemLayer::getLeyerHeight() const
{
    int start = 0;
    int end = 0;
    for (QuestGraphItem* item : items)
    {
        if (item->getY() < start) start = item->getY();
        if (item->getY() + item->getHeight() > end)
            end = item->getY() + item->getHeight();
    }
    return end - start;
}

void QuestGraphItemLayer::centerVertically(int pos)
{
    int height = getLeyerHeight();
    int y = pos - height / 2;
    for (QuestGraphItem* item : items)
    {
        item->setPosition(item->getX(), y);
        y += item->getHeight() + 20;
    }
}

QuestGraph::QuestGraph()
    : ZeichnungHintergrund()
{
    addStyle(ZeichnungHintergrund::Style::Sichtbar
             | ZeichnungHintergrund::Style::Erlaubt
             | ZeichnungHintergrund::Style::Rahmen);
    setRahmenBreite(1);
    setRahmenFarbe(0xFF52525E);
    setMausEreignis(Framework::_ret1ME);
}

void QuestGraph::render(Framework::Bild& rObj)
{
    if (hatStyle(ZeichnungHintergrund::Style::Sichtbar) && layers.getEintragAnzahl() > 0)
    {
        ZeichnungHintergrund::render(rObj);
        if (rObj.setDrawOptions(pos, gr))
        {
            if (rObj.setDrawOptions(getRahmenBreite(),
                    getRahmenBreite(),
                    getInnenBreite(),
                    getInnenHeight()))
            {
                int hScrollOffset = 0;
                if (hatStyle(ZeichnungHintergrund::Style::HScroll)
                    && horizontalScrollBar)
                {
                    hScrollOffset = horizontalScrollBar->getScroll();
                }
                int vScrollOffset = 0;
                if (hatStyle(ZeichnungHintergrund::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::doMausEreignis(Framework::MausEreignis& me, bool userRet)
{
    userRet &= hatStyle(ZeichnungHintergrund::Style::Sichtbar);
    bool vera = me.verarbeitet;
    if (!userRet)
    {
        me.verarbeitet = 1;
    }
    for (QuestGraphItemLayer* layer : layers)
    {
        layer->doMausEreignis(me);
    }
    if (!userRet)
    {
        me.verarbeitet = 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().getEintragAnzahl(); i++)
        {
            QuestGraphItem* item = layer->getItems().z(i);
            auto iterator = item->getNextLayersConnections().begin();
            QuestGraphItem* virtualItem = 0;
            for (; iterator; iterator++)
            {
                if (iterator->getLayerIndex() > layerIndex + 1)
                {
                    bool remove = 1;
                    if (!virtualItem)
                    {
                        virtualItem = new QuestGraphItem();
                        virtualItem->addPreviousLayerRef(item);
                        virtualItem->setVirtual(true);
                        virtualItem->calculateLaxerIndex();
                        remove = 0;
                    }
                    item->addNextLayerRef(iterator);
                    if (remove)
                    {
                        iterator.remove();
                    }
                    else
                    {
                        iterator.set(virtualItem);
                    }
                }
                else if (iterator->getLayerIndex() < 0)
                { // remove connections to invalid nodes
                    iterator.remove();
                }
            }
            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()
{
    bool changed = 1;
    int height = 0;
    while (changed)
    {
        changed = 0;
        for (QuestGraphItemLayer* layer : layers)
        {
            changed |= layer->fixItemPositions();
            if (layer->getLeyerHeight() > height)
            {
                height = layer->getLeyerHeight();
            }
        }
    }
    if (height > getInnenHeight())
    {
        addStyle(ZeichnungHintergrund::Style::VScroll);
        setVertikalKlickScroll(10);
        vertikalScrollBar->update(height, getInnenHeight());
    }
    if (layers.getEintragAnzahl() * 150 - 80 > getInnenBreite())
    {
        addStyle(ZeichnungHintergrund::Style::HScroll);
        setHorizontalKlickScroll(10);
        horizontalScrollBar->update(
            layers.getEintragAnzahl() * 150 - 80, getInnenBreite());
    }
    for (QuestGraphItemLayer* layer : layers)
    {
        layer->centerVertically(getHeight() / 2);
    }
}

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