//============================================================================
// Copyright 2009-2020 ECMWF.
// This software is licensed under the terms of the Apache Licence version 2.0
// which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
// In applying this licence, ECMWF does not waive the privileges and immunities
// granted to it by virtue of its status as an intergovernmental organisation
// nor does it submit to any jurisdiction.
//
//============================================================================

#include "TriggerGraphLayoutBuilder.hpp"

#include <algorithm>
#include <cassert>
#include <iostream>

#include "UiLog.hpp"


//========================================================
//
// SimpleGraphLayoutBuilder
//
//========================================================

SimpleGraphLayoutBuilder::~SimpleGraphLayoutBuilder() {
    clear();
}

void SimpleGraphLayoutBuilder::clear()
{
    for(auto n: nodes_) {
        delete n;
    }
    nodes_.clear();
}

void SimpleGraphLayoutBuilder::build(std::vector<GraphLayoutNode*>& nodes,
                                     std::vector<GraphLayoutEdge*>& edges, int focus)
{
    clear();
    for(auto n: nodes) {
        nodes_.emplace_back(new SimpleGraphLayoutNode(n));
    }

    // additional (dummy) nodes might be addedd during build
    size_t num = nodes_.size();
    focus_ = focus;
    buildIt(true);

    assert(num <= nodes_.size());
    for(size_t i=0; i < num; i++) {
        nodes[i]->x_ = nodes_[i]->x_;
        nodes[i]->y_ = nodes_[i]->y_;

        // add edge points via dummy nodes
        for(auto j: nodes_[i]->children_) {
            auto ch = nodes_[j];
            if (ch->dummy_) {
                std::vector<int> x, y, w;
                while(ch->hasChildren()) {
                    x.push_back(ch->x_);
                    y.push_back(ch->y_);
                    w.push_back(ch->width_);
                    int chIndex = ch->children_[0];
                    ch = nodes_[chIndex];
                    if (!ch->dummy_) {
                        auto e=new GraphLayoutEdge(i, chIndex);
                        e->x_ = x;
                        e->y_ = y;
                        e->width_ = w;
                        edges.push_back(e);
                        break;
                    }
                }
            }
        }
    }

    clear();
}

bool SimpleGraphLayoutBuilder::hasManagedParent(SimpleGraphLayoutNode* n) const
{
    for (auto i: n->parents_) {
        auto p = nodes_[i];
        if (p->managed_)
            return true;
    }
    return false;
}

bool SimpleGraphLayoutBuilder::hasManagedChild(SimpleGraphLayoutNode* n)  const
{
    for (auto i: n->children_) {
        auto p = nodes_[i];
        if (p->managed_)
            return true;
    }
    return false;
}

int SimpleGraphLayoutBuilder::compute_level(SimpleGraphLayoutNode *item)
{
    if (item->visited_)
        return -1;

    item->visited_ = true;

    int lvl = 0;
    for (auto i: item->parents_) {
        auto p = nodes_[i];
        if (p->managed_) {
            int lev = compute_level(p) + 1;
            lvl = std::max(lvl, lev);
        }
    }

    item->level_ = lvl;
    item->visited_ = false;

    return lvl;
}

void SimpleGraphLayoutBuilder::compute_level_pass2(SimpleGraphLayoutNode *item)
{
    int lvl1 = 12000;
    for(auto i: item->children_) {
        auto ch = nodes_[i];
        if(ch->managed_)
            lvl1 = std::min(lvl1, ch->level_);
    }
    item->level_ = lvl1 - 1;
}

void SimpleGraphLayoutBuilder::compute_y(
        const std::vector<int>& nodes, const std::vector<int>& levels,
        std::vector<int>& positions, int max_in_a_level, int levelNum,
        int v_dist)
{
    int max = max_in_a_level * v_dist;

    for(int i=0; i< levelNum; i++)
        positions[i] = ((max/levels[i]) - (max/max_in_a_level))/2;

    for(size_t i=0; i< nodes.size(); i++) {
        auto item = nodes_[nodes[i]];
        item->y_ = positions[item->level_];
        positions[item->level_] += (max / levels[item->level_]);
    }
}

void SimpleGraphLayoutBuilder::set_arc(SimpleGraphLayoutNode* item, int arc)
{
    if(item->visited_)
        return;

    item->visited_ = true;
    item->arc_ = arc;

    for(auto i: item->parents_) {
        auto p = nodes_[i];
        if(p->managed_)
            set_arc(p, arc);
    }

    item->visited_ = false;
}

int SimpleGraphLayoutBuilder::compute_arc(SimpleGraphLayoutNode *item)
{
    int a = item->arc_;

    if(item->visited_)
        return 0;

    item->visited_ = true;

    if(!item->parents_.empty()) {
        for(auto i: item->parents_) {
            auto p = nodes_[i];
            if (p->managed_) {
                int b = compute_arc(p);
                a = std::max(a, b);
            }
        }
        set_arc(item, a);
    }

    item->visited_ = false;
    return a;
}


int SimpleGraphLayoutBuilder::insertDummyNode(int parentIndex, int chIndex, int level)
{
    auto p = nodes_[parentIndex];
    auto ch = nodes_[chIndex];
    int chInParentIndex = p->indexOfChild(chIndex);
    int parentInChIndex = ch->indexOfParent(parentIndex);

    assert(chInParentIndex != -1);
    assert(parentInChIndex != -1);

    auto d = new SimpleGraphLayoutNode();
    d->dummy_ = true;
    d->level_ = level;
    int dummyIndex = static_cast<int>(nodes_.size());
    nodes_.push_back(d);

    p->children_[chInParentIndex] = dummyIndex;
    d->parents_.push_back(parentIndex);

    ch->parents_[parentInChIndex] = dummyIndex;
    d->children_.push_back(chIndex);

    return dummyIndex;
}

bool SimpleGraphLayoutBuilder::addDummy(int nodeIndex)
{
    auto n = nodes_[nodeIndex];
    assert(n);
    int more = false;
    int level = n->level_;

    if (n->visited_)
        return false;

    n->visited_ = true;

    for (auto i: n->children_) {
        auto ch = nodes_[i];
        if (ch->managed_) {
            int chLevel = ch->level_;
            int dist = chLevel - level;
            int actLevel = level;

            // insert dummy nodes between the node and the child
            int currentIndex = nodeIndex;
            int chIndex= i;

            while(dist-- > 1) {
                currentIndex = insertDummyNode(currentIndex, chIndex, ++actLevel);
                more = true;
            }
        }

        more = addDummy(i) || more;
    }

    n->visited_ = false;
    return more;
}

bool SimpleGraphLayoutBuilder::addDummyNodes()
{
    bool more = false;
    // nodes can grow in the loop!
    size_t num = nodes_.size();
    for(size_t i=0; i < num; i++) {
        more = addDummy(i) || more;
    }
    return more;
}

void SimpleGraphLayoutBuilder::printState(const std::vector<int>& nodes)
{
    for(size_t i=0; i < nodes.size(); i++) {
        auto n = nodes_[nodes[i]];
        std::cout << i << "[" << nodes[i] << "] " << " arc=" << n->arc_ << " lev=" << n->level_ <<
                     " " << n->width_ << " " << n->height_ <<
                     " x=" << n->x_ << " y=" << n->y_ <<
                     " d=" << n->dummy_ << std::endl;
    }
}

void SimpleGraphLayoutBuilder::buildIt(bool dummy)
{
    int H_DIST = 10;
    int V_DIST = 10;
    int max_in_a_level = 0;
    int max_level = 0;
    int levelNum = 0;
    int num_nodes = 0;

    SimpleGraphLayoutNode* focus = nullptr;

    std::vector<int> nodes;
    for(size_t i=0;i < nodes_.size() ;i++) {
        auto item = nodes_[i];
        if (item->managed_)
            nodes.push_back(i);

        //item->x_ = item->y_ = 0;
        item->level_ = item->arc_ = -1;
        item->visited_ = false;
    }

    if(nodes.empty())
        return;

    if(!focus)
        focus = nodes_[focus_];

    std::vector<int> levels(nodes.size(),0);
    std::vector<int> widths(nodes.size(),0);
    std::vector<int> heights(nodes.size(),0);
    std::vector<int> children(nodes.size(),0);
    std::vector<int> positions(nodes.size(),0);

    for(auto item: nodes_) {
        item->level_ = 0;
    }

    int arc = 1;
    for(auto i: nodes) {
        auto item = nodes_[i];
        compute_level(item);
        item->arc_ = arc++;
        H_DIST = std::max(H_DIST,item->width_);
        V_DIST = std::max(V_DIST,item->height_);
    }

    H_DIST += xMinGap_;
    V_DIST += yMinGap_;

    for(auto i: nodes) {
        auto item = nodes_[i];
        if (!hasManagedParent(item) && hasManagedChild(item))
            compute_level_pass2(item);
    }

    //printState(nodes);

    if (dummy && addDummyNodes()) {
        buildIt(false);
        return;
    }

    // sort by level
    std::sort(nodes.begin(), nodes.end(), [this](int idx1, int idx2)
    {
        return nodes_[idx1]->level_ < nodes_[idx2]->level_;
    });

    //qsort(nodes, num_nodes, sizeof(int), by_level);

    for(auto i:nodes) {
        auto item = nodes_[i];
        levels[item->level_]++;

        if(levels[item->level_] > max_in_a_level) {
            max_in_a_level = levels[item->level_];
            max_level = item->level_;
        }

        levelNum = std::max(levelNum, item->level_);

        widths[item->level_] = std::max(widths[item->level_],item->width_);
        heights[item->level_] = std::max(heights[item->level_], item->height_);
        children[item->level_] = std::max(children[item->level_], static_cast<int>(item->children_.size()));
    }

    levelNum++;

    //printState(nodes);

    //int a = 0;
    for (int i = 0, a = 0 ;i < levelNum;i ++) {
        int b = widths[i] + xMinGap_;

        b += children[i] * 2 ; /* +5 pixels per node with max kids in level */
        widths[i] = a;
        a += b;
        heights[i] += yMinGap_;
    }

    for (auto i: nodes) {
        auto item = nodes_[i];
        item->x_ = widths[item->level_];
    }

    //printState(nodes);

    for (size_t a=0; a<2; a++) {
        for (auto i: nodes) {
            auto item = nodes_[i];
            if (!hasManagedChild(item))
                compute_arc(item);
        }
        for (auto i: nodes) {
            auto item = nodes_[i];
            if (!hasManagedChild(item))
                set_arc(item, item->arc_);
        }
    }

    // sort by arc
    std::sort(nodes.begin(), nodes.end(), [this](int idx1, int idx2)
    {
        if(nodes_[idx1]->level_ != nodes_[idx2]->level_)
            return nodes_[idx1]->level_ < nodes_[idx2]->level_;

        return nodes_[idx1]->arc_ < nodes_[idx2]->arc_;
    });

    for (int a=0; a < levelNum; a++)
        if (levels[a] == 0)
            levels[a] = 1;

    compute_y(nodes, levels, positions, max_in_a_level, levelNum, V_DIST);

    //sort by tmpY
    std::sort(nodes.begin(), nodes.end(), [this](int idx1, int idx2)
    {
        if(nodes_[idx1]->level_ != nodes_[idx2]->level_)
            return nodes_[idx1]->level_ < nodes_[idx2]->level_;

        return nodes_[idx1]->y_ < nodes_[idx2]->y_;
    });

    //printState(nodes);

    int move_it = 0;
    int more = 1;
    int count = 0;

    while (more--) {
        //UiLog().dbg() << "more=" << more;
        count = static_cast<int>(nodes.size());
        while (count--) {
            //UiLog().dbg() << "  count=" << count;
            for (auto i: nodes) {
                int n = 0;
                int y = 0;
                auto item = nodes_[i];
                if ((max_level != item->level_) ^ move_it) {
                    for (auto j: item->parents_) {
                        auto p = nodes_[j];
                        if (p->managed_ && p != focus) {
                            y += p->y_;
                            n++;
                        }
                    }

                    for (auto j: item->children_) {
                        auto p = nodes_[j];
                        if(p->managed_ && p != focus) {
                            y += p->y_;
                            n++;
                        }
                    }
                    item->y_ = n?y/n:y;
                }
            }

            //sort by tmpY
            std::sort(nodes.begin(), nodes.end(), [this](int idx1, int idx2)
            {
                 if(nodes_[idx1]->level_ != nodes_[idx2]->level_)
                     return nodes_[idx1]->level_ < nodes_[idx2]->level_;

                 return nodes_[idx1]->y_ < nodes_[idx2]->y_;
            });

            move_it = !move_it;

            bool chg = true;
            int a = 10000;
            while (chg && a--) {
                chg = false;
                for (size_t i=1; i < nodes.size() ;i++) {
                    auto item = nodes_[nodes[i]];
                    auto itemPrev = nodes_[nodes[i-1]];
                    if(itemPrev->level_ == item->level_)
                        if((item->y_ - itemPrev->y_) < heights[item->level_]) {
                            if(itemPrev != focus)
                                itemPrev->y_ -= heights[item->level_]/2;
                            if(item != focus)
                                item->y_ += heights[item->level_]/2;
                            chg = true;
                        }
                }
            }
        }
    }

    int minY = nodes_[nodes[0]]->y_;
    int minX = nodes_[nodes[0]]->x_;

    for (size_t i=1; i < nodes.size() ;i++) {
        auto item = nodes_[nodes[i]];
        if (item->x_ < minX)
            minX = item->x_;
        if (item->y_ < minY)
            minY = item->y_;
    }

    minX -= 20;
    minY -= 10;
    if (!nodes_.empty()) {
        minY -= nodes_[0]->height_/2;
    }

    UiLog().dbg() << "focus="  << focus_;

    for (auto i: nodes) {
        auto item = nodes_[i];
        item->x_ -= minX;
        item->y_ -= minY;
    }

    // set width for dummy nodes
    std::vector<int> minW(levelNum, 100000);
    for (auto n: nodes_) {
        if (!n->dummy_ && n->level_ >=0)
           minW[n->level_] = std::min(minW[n->level_], n->width_);
    }

    for (auto n: nodes_) {
        if (n->dummy_ && n->level_ >=0 && n->level_ < static_cast<int>(minW.size()))
            n->width_ = minW[n->level_];
    }

    //printState(nodes);

}
