#ifndef OGDFFUNCTIONS_H
#define OGDFFUNCTIONS_H

#include <functional>
#include <random>
#include <utility>
#include <complex>

#include "ogdf/basic/GraphAttributes.h"
#include "ogdf/misclayout/CircularLayout.h"
#include "ogdf/basic/graph_generators.h"
#include "ogdf/fileformats/GraphIO.h"
#include "ogdf/basic/simple_graph_alg.h"

using namespace std;

typedef pair<double, double> doublep;
typedef pair<doublep, ogdf::node> dnodep;

ogdf::DPoint find_center(const doublep& p1, const doublep& p2, const doublep& p3)
{
    auto x_term = [](const doublep& p1, const doublep& p2, const doublep& p3)
    {
        return (p1.first * p1.first + p1.second * p1.second) * (p2.second - p3.second);
    };

    auto y_term = [](const doublep& p1, const doublep& p2, const doublep& p3)
    {
        return (p1.first * p1.first + p1.second * p1.second) * (p3.first - p2.first);
    };


    double D = 2*(p1.first * (p2.second - p3.second) + p2.first * (p3.second - p1.second) + p3.first * (p1.second - p2.second));
    double x = 1.0/D * (x_term(p1, p2, p3) + x_term(p2, p3, p1) + x_term(p3, p1, p2));
    double y = 1.0/D * (y_term(p1, p2, p3) + y_term(p2, p3, p1) + y_term(p3, p1, p2));

    return ogdf::DPoint(x,y);
}

ogdf::DPoint find_center(const ogdf::GraphAttributes& ga)
{
    int i = 0;
    vector<ogdf::node> three_nodes;
    for(auto n : ga.constGraph().nodes)
    {
        three_nodes.push_back(n);
        i++;

        if(i == 3)
            break;
    }

    auto n1 = three_nodes[0];
    auto n2 = three_nodes[1];
    auto n3 = three_nodes[2];

    return find_center(doublep(ga.x(n1), ga.y(n1)), doublep(ga.x(n2), ga.y(n2)), doublep(ga.x(n3), ga.y(n3)));
}

double get_radius(const ogdf::GraphAttributes& ga)
{
    auto center = find_center(ga);
    auto n = ga.constGraph().firstNode();

    ogdf::DPoint n_pos(ga.x(n), ga.y(n));
    ogdf::DSegment nc(center, n_pos);

    return nc.length();
}

doublep get_rad(const double& x, const double& y)
{
    return make_pair(sqrt(x*x + y*y), atan2(y,x));
}

void get_order(const ogdf::GraphAttributes& ga, std::vector<ogdf::node>& order)
{
    for(auto n : ga.constGraph().nodes)
    {
        order.push_back(n);
    }

    auto comp = [&](ogdf::node u, ogdf::node v) {
        auto radU = get_rad(ga.x(u), ga.y(u));
        auto radV = get_rad(ga.x(v), ga.y(v));

        return radU < radV;
    };

    std::sort(order.begin(), order.end(), comp);
}

void get_random_graph(ogdf::Graph& g, const int& l, const int &u, const int& e_l, const int& e_u)
{
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<int> n_dist(l, u);
    int nodes = n_dist(gen);
    uniform_int_distribution<int> e_dist(e_l*nodes, min(nodes*nodes, e_u*nodes));
    int edges = e_dist(gen);
    ogdf::randomBiconnectedGraph(g, nodes, edges);
    ogdf::makeSimpleUndirected(g);

    assert(ogdf::isBiconnected(g) == true);
}

void get_tiny_dense_graph(ogdf::Graph& g)
{
    ogdf::GraphIO::readGML(g, "../graphs/tiny_dense.gml");
}

void get_tiny_graph(ogdf::Graph& g)
{
    ogdf::GraphIO::readGML(g, "../graphs/tiny.gml");
}

void get_medium_graph(ogdf::Graph& g)
{
    ogdf::GraphIO::readGML(g, "../graphs/medium.gml");
}

void save_graph(const ogdf::GraphAttributes& ga, const string& basename, const string& name)
{
    stringstream ss;
    ss << basename << "_" << name << ".svg";
    ogdf::GraphIO::SVGSettings settings;
    ogdf::GraphIO::drawSVG(ga, ss.str(), settings);
}

void compute_circular_layout(ogdf::GraphAttributes& ga, vector<ogdf::node>& order, ogdf::NodeArray<int>& node_pos)
{
    ogdf::CircularLayout cl;
    cl.call(ga);

    auto center = find_center(ga);
    for(auto n : ga.constGraph().nodes)
    {
        ga.x(n) = ga.x(n) - center.m_x;
        ga.y(n) = ga.y(n) - center.m_y;
    }

    vector<dnodep> rads;
    for(auto n : ga.constGraph().nodes)
        rads.push_back(make_pair(get_rad(ga.x(n), ga.y(n)), n));

    sort(rads.begin(), rads.end(), [](const dnodep& u, const dnodep& v) {return u.first.second < v.first.second;});
    for(auto p : rads)
        order.push_back(p.second);

    for(auto it = order.begin(); it < order.end(); ++it)
        node_pos[*it] = it - order.begin();
}

void draw_to_svg(ogdf::GraphAttributes& ga, const string& filename = "../out/drawing.svg")
{
    ogdf::GraphIO::drawSVG(ga, filename);
}

int num_crossings(ogdf::EdgeArray<int>& crossings, const ogdf::Graph& g, const ogdf::GraphAttributes& ga, const ogdf::NodeArray<int>& node_pos)
{   
    auto in_between = [&](ogdf::edge e, ogdf::node n)
    {
        auto np = node_pos[n];
        auto sp = node_pos[e->source()];
        auto tp = node_pos[e->target()];
        if(sp > tp)
            swap(sp, tp);

        if(sp < np && np < tp)
            return true;

        return false;
    };

    auto share_endpoint = [&](ogdf::edge e, ogdf::edge f)
    {
        if(e->source() == f->source() || e->source() == f->target() || e->target() == f->source() || e->target() == f->target())
            return true;

        return false;
    };

    auto do_cross = [&](ogdf::edge e, ogdf::edge f)
    {
        if(share_endpoint(e,f) || (!in_between(e, f->source()) && !in_between(e, f->target())) || (in_between(e, f->source()) && in_between(e, f->target())))
            return false;

        return true;
    };

    int num_crossings = 0;
    for(auto e : g.edges)
        for(auto f : g.edges)
            if(do_cross(e,f))
            {
                ++num_crossings;
                crossings[e]++;
                crossings[f]++;
            }

    for(auto e : g.edges)
        crossings[e] = crossings[e] / 2;

    assert(num_crossings % 2 == 0);
    return num_crossings/2;
}

#endif // OGDFFUNCTIONS_H
