#ifndef SPIDER_LAYOUT_H
#define SPIDER_LAYOUT_H

#include <functional>
#include <chrono>
#include <cairo/cairo.h>
#include <cairo/cairo-svg.h>

#include "intervals.h"
#include "ogdffunctions.h"
#include "draw.h"
#include "ogdf/basic/extended_graph_alg.h"

struct Run
{
    int m_id = -1;
    string m_graph_name = "-1";
    int m_n = -1;
    int m_e = -1;
    long int m_circular_t = -1;
    long int m_ksubgraph_t = -1;
    int m_n_inside = -1;
    int m_n_outside = -1;
    int m_saved_crossings = -1;
    int m_crossings = -1;
    int m_crossings_inside = -1;
    int m_crossings_outside = -1;

    Run()
    { }

    void init(const int& id, const string& graph_name)
    {
        m_id = id;
        m_n = 0;
        m_e = 0;
        m_graph_name = graph_name;
        m_circular_t = 0;
        m_ksubgraph_t = 0;
        m_n_inside = 0;
        m_n_outside = 0;
        m_saved_crossings = 0;
        m_crossings = 0;
        m_crossings_inside = 0;
        m_crossings_outside = 0;
    }

    string get_csv_string(const string& del) const
    {
        stringstream ss;
        ss << m_n << del
           << m_e << del
           << m_n_inside << del
           << m_n_outside << del
           << m_crossings_inside << del
           << m_crossings_outside << del
           << m_saved_crossings << del
           << m_crossings << del
           << m_circular_t << del
           << m_ksubgraph_t << del
           << m_id << del
           << m_graph_name;

//        cout << ss.str() << endl;
        return ss.str();
    }
};

//void find_level(ogdf::EdgeArray<int>& lvl, ogdf::GraphAttributes& ga, const vector<ogdf::node>& order, const ogdf::NodeArray<int>& node_pos, const bool cw = true)
//{
//    vector<int> intervals;
//    unordered_map<int, std::shared_ptr<IntervalInfo>> interval_info;
//    interval_representation(intervals, interval_info, ga, order, node_pos, false);
//    for(auto i_info : interval_info)
//        lvl[i_info.second->edge] = compute_nesting_level(i_info.second, intervals) + 1;

//    for(auto e : ga.constGraph().edges)
//        cout << ga.label(e) << ": " << lvl[e] << endl;
//}

void hide_edges(const ogdf::EdgeArray<bool>& flag, ogdf::Graph::HiddenEdgeSet& hidden_edges, const ogdf::Graph& g, const bool invert = false)
{
    vector<ogdf::edge> to_hide;
    for(auto e : g.edges)
    {
        if((!invert && flag[e]) || (invert && !flag[e]))
            to_hide.push_back(e);
    }

    for(auto e : to_hide)
        hidden_edges.hide(e);
}

void set_labels(ogdf::GraphAttributes& ga, const vector<ogdf::node>& order)
{
    for(auto it = order.begin(); it < order.end(); ++it)
    {
        ogdf::node n = *it;
        ga.label(n) = to_string(it - order.begin());
    }

    int i = 1;
    for(auto e : ga.constGraph().edges)
    {
        stringstream ss;
        ss << i;
        ga.label(e) = ss.str();
        ++i;
    }
}

void hide_circle_edges(ogdf::Graph::HiddenEdgeSet& hidden_set, const ogdf::GraphAttributes& ga, const ogdf::NodeArray<int>& node_pos)
{
    ogdf::EdgeArray<bool> circle_edge_flag(ga.constGraph(), false);
    for(auto e : ga.constGraph().edges)
        if((abs(node_pos[e->source()] - node_pos[e->target()])) % (ga.constGraph().numberOfNodes() - 1) <= 1)
            circle_edge_flag[e] = true;

    hide_edges(circle_edge_flag, hidden_set, ga.constGraph());
}

void get_directions(ogdf::EdgeArray<bool>& edge_direction, ogdf::EdgeArray<int>& edge_length, const ogdf::Graph& g, const ogdf::NodeArray<int>& node_pos)
{
    for(auto e : g.edges)
    {
        int s = min(node_pos[e->source()],node_pos[e->target()]);
        int t = max(node_pos[e->source()],node_pos[e->target()]);

        int cw_distance = t - s;
        int ccw_distance = g.numberOfNodes() - t + s;

        //if cw distance is smaller set direction to true
        if(cw_distance < ccw_distance)
        {
            edge_direction[e] = true;
            edge_length[e] = cw_distance;
        }
        else
        {
            edge_direction[e] = false;
            edge_length[e] = ccw_distance;
        }
    }
}

void circle_segment(ogdf::DPolyline& c_segment, const ogdf::DPoint& center, const ogdf::DPoint& start, const ogdf::DPoint& end, const bool& edge_direction)
{
    ogdf::DSegment cs(center, start);
    ogdf::DSegment ce(center, end);
    auto s_angle = get_rad(cs.dx(), cs.dy()).second;
    auto t_angle = get_rad(ce.dx(), ce.dy()).second;

    cout << s_angle * 180/3.141 << " " << t_angle * 180/3.141 << endl;
    auto arc_length = edge_direction ? abs(t_angle - s_angle) : 2*3.141 - abs(t_angle - s_angle);
    auto sign = edge_direction ? 1 : -1;

    for(double t = 0.0; abs(t) < 1; t += sign * 0.01)
    {
        auto a = s_angle + arc_length * t;
        c_segment.pushBack(center + cs.length() * ogdf::DPoint(cos(a), sin(a)));
    }
}

void assign_levels(ogdf::EdgeArray<int>& levels, vector<int>& solution, ogdf::Graph& g, ogdf::GraphAttributes& ga, const ogdf::NodeArray<int>& node_pos,
                 unordered_map<int, std::shared_ptr<IntervalInfo>>& interval_info)
{
    for(auto& s : solution)
        if(s < 0)
            ga.strokeColor(interval_info[abs(s)]->edge) = ogdf::Color(ogdf::Color::Name::Red);

    ogdf::EdgeArray<bool> outside(g, false);
    for(auto& id : solution)
        outside[interval_info[abs(id)]->edge] = true;

    ogdf::Graph::HiddenEdgeSet inside(g);
    hide_edges(outside, inside, g, true);

    ogdf::EdgeArray<bool> edge_direction(g, false);
    ogdf::EdgeArray<int> edge_length(g, 0);
    get_directions(edge_direction, edge_length, g, node_pos);

    unordered_map<int, pair<vector<ogdf::edge>,vector<ogdf::edge>>> intervals_on_circle;
    for(auto e : g.edges)
    {
        auto e_s = node_pos[e->source()] < node_pos[e->target()] ? node_pos[e->source()] : node_pos[e->target()];
        auto e_t = node_pos[e->source()] > node_pos[e->target()] ? node_pos[e->source()] : node_pos[e->target()];

        if(edge_direction[e])
        {
            intervals_on_circle[e_s].first.push_back(e);
            intervals_on_circle[e_t].second.push_back(e);
        }
        else
        {
            intervals_on_circle[e_s].second.push_back(e);
            intervals_on_circle[e_t].first.push_back(e);
        }

        int i = e_s;
        while(true)
        {           
            i = (i + (edge_direction[e] ? 1 : -1));
            if(i == -1)
                i = g.numberOfNodes() - 1;

            if(i == e_t)
                break;

            intervals_on_circle[i].first.push_back(e);
            intervals_on_circle[i].second.push_back(e);

        }
    }

    auto sort_function = [&](ogdf::edge e, ogdf::edge f) {
        if(edge_length[e] == edge_length[f])
            return stoi(ga.label(e)) < stoi(ga.label(f));

        return edge_length[e] < edge_length[f];
    };

    for(auto& pos : intervals_on_circle)
    {
        sort(pos.second.first.begin(), pos.second.first.end(), sort_function);
        sort(pos.second.second.begin(), pos.second.second.end(), sort_function);
    }

    for(auto& cp : intervals_on_circle)
    {
        for(auto it = cp.second.first.begin(); it < cp.second.first.end() ;++it)
            levels[*it] = max(levels[*it], (int)(it - cp.second.first.begin()));

        for(auto it = cp.second.second.begin(); it < cp.second.second.end() ;++it)
            levels[*it] = max(levels[*it], (int)(it - cp.second.second.begin()));
    }

    for(auto e : g.edges)
        levels[e]++;

    inside.restore();
}

void spider_layout(ogdf::Graph& c_g, ogdf::GraphAttributes& c_ga, Run& stat, const string& filename = "drawing", const int& k = 0, const int& w_outer = 1)
{
    int num_biccs = -1;
    ogdf::EdgeArray<int> bicc(c_g,0);
    ogdf::biconnectedComponents(c_g, bicc, num_biccs);
    vector<int> component_size(num_biccs, 0);

    for(auto e : c_g.edges)
        component_size[bicc[e]]++;

    for(int i = 0; i < num_biccs; ++i)
    {
        if(component_size[i] > 1)
        {
            ogdf::Graph g;
            ogdf::GraphAttributes ga(g, ogdf::GraphAttributes::nodeGraphics |
                                     ogdf::GraphAttributes::edgeGraphics |
                                     ogdf::GraphAttributes::nodeLabel |
                                     ogdf::GraphAttributes::edgeLabel |
                                     ogdf::GraphAttributes::edgeStyle |
                                     ogdf::GraphAttributes::nodeStyle |
                                     ogdf::GraphAttributes::nodeTemplate |
                                     ogdf::GraphAttributes::nodeLabel |
                                     ogdf::GraphAttributes::nodeWeight);

            vector<ogdf::edge> g_edges;
            vector<ogdf::node> g_nodes;
            unordered_set<ogdf::node> done;
            for(auto e : c_g.edges)
            {
                if(bicc[e] == i)
                {
                    g_edges.push_back(e);
                    if(done.find(e->source()) == done.end())
                    {
                        g_nodes.push_back(e->source());
                        done.insert(e->source());
                    }

                    if(done.find(e->target()) == done.end())
                    {
                        g_nodes.push_back(e->target());
                        done.insert(e->target());
                    }
                }
            }

            ogdf::NodeArray<ogdf::node> node_c_g_to_g(c_g, nullptr);
            for(auto n : g_nodes)
            {
                auto n_c = g.newNode();
                node_c_g_to_g[n] = n_c;
            }

            for(auto e : g_edges)
                g.newEdge(node_c_g_to_g[e->source()], node_c_g_to_g[e->target()]);

            assert(ogdf::isBiconnected(g));

            stat.m_n += g.numberOfNodes();
            stat.m_e += g.numberOfEdges();

            vector<ogdf::node> c_order;
            vector<int> intervals;
            unordered_map<int, std::shared_ptr<IntervalInfo>> interval_info;
            ogdf::NodeArray<int> node_pos(ga.constGraph(), 0);
            ogdf::Graph::HiddenEdgeSet circle_edges(g);
            ogdf::EdgeArray<int> levels(g, 0);
            ogdf::EdgeArray<bool> edge_direction(g, false);

            auto begin = chrono::high_resolution_clock::now();
            compute_circular_layout(ga, c_order, node_pos);
            auto end = chrono::high_resolution_clock::now();
            auto c_t = chrono::duration_cast<chrono::nanoseconds>(end-begin).count();
            stat.m_circular_t += c_t;

            set_labels(ga, c_order);
            stringstream base_file;
            base_file << filename << i << "_" << k << "_base.svg";
            draw_graph_to_svg(g, ga, node_pos, levels, edge_direction, base_file.str());
            hide_circle_edges(circle_edges, ga, node_pos);

            ogdf::EdgeArray<int> crossings(g, 0);
            stat.m_crossings += num_crossings(crossings, g, ga, node_pos);

            //Get the interval representation
            interval_representation(intervals, interval_info, ga, c_order, node_pos, crossings);

            //Compute outside edges
            begin = chrono::high_resolution_clock::now();

            vector<int> solution;
            int W = degree_k_subgraph(solution, intervals, interval_info, k, w_outer);

            end = chrono::high_resolution_clock::now();
            auto c_k = chrono::duration_cast<chrono::nanoseconds>(end-begin).count();
            stat.m_ksubgraph_t += c_k;

            stat.m_n_outside += solution.size();
            stat.m_n_inside += ga.constGraph().numberOfEdges() - solution.size();
            stat.m_saved_crossings += W;

            ogdf::EdgeArray<bool> in_solution(g, false);
            for(auto id : solution)
                in_solution[interval_info.at(id)->edge] = true;

            ogdf::Graph::HiddenEdgeSet hidden(g);
            hide_edges(in_solution, hidden, g);
            ogdf::EdgeArray<int> crossings_inside(g, 0);
            stat.m_crossings_inside = stat.m_crossings_inside = num_crossings(crossings_inside, g, ga, node_pos);
            hidden.restore();

            hide_edges(in_solution, hidden, g, true);
            ogdf::EdgeArray<int> crossings_outside(g, 0);
            stat.m_crossings_outside = num_crossings(crossings_outside, g, ga, node_pos);
            hidden.restore();

            //Compute levels for outside edges
            assign_levels(levels, solution, g, ga, node_pos, interval_info);

            ogdf::EdgeArray<int> edge_length(g, 0);
            get_directions(edge_direction, edge_length, g, node_pos);

            circle_edges.restore();

            stringstream file;
            file << filename << i << "_" << k << "_all.svg";
            draw_graph_to_svg(g, ga, node_pos, levels, edge_direction, file.str());
        }
    }
}

#endif // SPIDER_LAYOUT_H
