#ifndef INTERVALS_H
#define INTERVALS_H

#include <vector>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>

#include "ogdf/basic/GraphAttributes.h"

using namespace std;

typedef pair<int, ogdf::edge> eip;

struct IntervalInfo
{
    int id;
    int start;
    int end;
    int weight;
    int next_nested;
    int first_after;

    ogdf::edge edge;
    unordered_set<int> nested_map;
    unordered_set<int> intersection_map;
    vector<shared_ptr<IntervalInfo>> nested;
    vector<shared_ptr<IntervalInfo>> left_intersections;
    vector<shared_ptr<IntervalInfo>> right_intersections;
};

ostream& operator<<(ostream& stream, const IntervalInfo& info)
{
    stream << "[" << info.start << "," << info.end << "]" << endl;
    stream << "Nestings: ";
    for(auto i : info.nested)
        stream << i->id << " [" << i->start << "," << i->end << "], ";
    cout << endl;

    stream << "Left intersections: ";
    for(auto i : info.left_intersections)
        stream << i->id << "[" << i->start << "," << i->end << "], ";
    cout << endl;

    stream << "Right intersections: ";
    for(auto i : info.right_intersections)
        stream << i->id << "[" << i->start << "," << i->end << "], ";
    cout << endl;

    return stream;
 }

//Get ordered list of edges for node u based on node order node_pos
void edge_order_around_vertex(vector<ogdf::edge>& edge_order, const ogdf::node u, const ogdf::Graph& g, const ogdf::NodeArray<int>& node_pos)
{
    vector<eip> end_edges;

    for(auto adj : u->adjEntries)
    {
        auto v = adj->theEdge()->opposite(u);
        int pos = node_pos[v] - node_pos[u];
        if(pos < 0) pos += g.numberOfNodes();
        end_edges.push_back(make_pair(pos, adj->theEdge()));
    }

    sort(end_edges.begin(), end_edges.end(), [](const eip& e1, const eip& e2) {return e1.first < e2.first;});
    for(auto ep : end_edges)
        edge_order.push_back(ep.second);
}

void interval_representation(vector<int>& intervals, unordered_map<int, std::shared_ptr<IntervalInfo>>& info, const ogdf::GraphAttributes& ga,
                             const vector<ogdf::node>& order, const ogdf::NodeArray<int>& node_pos, const ogdf::EdgeArray<int>& crossings, const bool& add_zero = true)
{
    auto next_nested = [&](const int& start, const int& end)
    {
        int i = start + 1;
        auto i_info = info.at(intervals[start]);
        while(i < end)
        {
            if(intervals[i] >= 0 && i_info->nested_map.find(intervals[i]) != i_info->nested_map.end())
                return i;
            ++i;
        }

        return end;
    };

    ogdf::NodeArray<vector<ogdf::edge>> edge_orders(ga.constGraph());
    for(auto n : order)
    {
        vector<ogdf::edge> edge_order;
        edge_order_around_vertex(edge_order, n, ga.constGraph(), node_pos);
        edge_orders[n] = edge_order;
    }

    unordered_set<int> started;
    unordered_set<int> ended;

    auto dummy = std::make_shared<IntervalInfo>();
    if(add_zero)
    {
        dummy->id = 0;
        dummy->start = 0;
        dummy->edge = nullptr;
        info[dummy->id] = dummy;
        intervals.push_back(dummy->id);
        started.insert(dummy->id);
    }
    for(auto it = order.rbegin(); it < order.rend(); ++it)
    {
        auto n = (*it);
        for(auto e : edge_orders[n])
        {
            int id = stoi(ga.label(e));
            if(info.find(id) == info.end())
            {
                auto i = std::make_shared<IntervalInfo>();
                i->id = id;
                i->start = intervals.size();
                i->edge = e;
                info[id] = i;

                intervals.push_back(id);
                started.insert(id);
            }
            else
            {
                started.erase(id);
                info[id]->end = intervals.size();
                intervals.push_back(-id);

                for(auto s_id : started)
                {
                    if(info[s_id]->start < info[id]->start)
                    {
                        info[s_id]->nested.push_back(info[id]);
                        info[s_id]->nested_map.insert(id);
                    }
                    else
                        info[id]->right_intersections.push_back(info[s_id]);
                }

                for(auto e_id : ended)
                    if(info[e_id]->start < info[id]->start && info[e_id]->end > info[id]->start && info[e_id]->end < info[id]->end)
                        info[id]->left_intersections.push_back(info[e_id]);

                ended.insert(id);
            }
        }
    }

    if(add_zero)
    {
        info[dummy->id]->end = intervals.size();
        intervals.push_back(-dummy->id);
    }

    for(auto i : info)
    {
        if(i.first == 0)
            i.second->weight = 0;
        else
            i.second->weight = crossings[i.second->edge];

        i.second->next_nested = next_nested(i.second->start, i.second->end);
    }

//    for(auto i : info)
//        cout << i.second->id << ": " << (*i.second) << endl;
}

typedef pair<int,int> Span;

struct PartialSolution
{
    int one_dms_cd;
    vector<Span> one_dms_cd_spans;
    unordered_map<int, int> one_dms_cd_ef;
    unordered_map<int, vector<Span>> one_dms_cd_ef_spans;

    void add_value(const int& W, vector<Span>& spans)
    {
        one_dms_cd = W;
        one_dms_cd_spans.swap(spans);
    }

    void add_value(const Span& span, const int& W, vector<Span>& spans)
    {
        assert(span.second != -1);
        one_dms_cd_ef[span.second] = W;
        one_dms_cd_ef_spans[span.second].swap(spans);
    }
};

int degree_0_subgraph(vector<int>& solution, const vector<int>& intervals, const unordered_map<int, std::shared_ptr<IntervalInfo>>& info)
{
    unordered_map<int, PartialSolution> one_dms;

    auto length = [&](std::shared_ptr<IntervalInfo> info) {return unsigned(info->end - info->start);};

    auto get_nested = [&](const int& l, const int& r)
    {
        unordered_set<int> nested;
        unordered_set<int> candidates;
        for(auto it = intervals.begin() + l + 1; it < intervals.begin() + r; ++it)
        {
            int id = *it;
            if(id > 0)
                candidates.insert(id);
            else if(candidates.find(abs(id)) != candidates.end())
                nested.insert(abs(id));
        }

        return nested;
    };

    std::function<void (const Span&, int&)> compute_solution = [&](const Span& span, int& W_c)
    {
        W_c = W_c + info.at(span.first)->weight;
        if(span.first != 0)
            solution.push_back(span.first);

        for(const auto& s : one_dms[span.first].one_dms_cd_spans)
            compute_solution(s, W_c);
    };

    std::function<int (const int&, const int&, vector<Span>&, unordered_set<int>&, const int&)> recurrence = [&](const int&a, const int& b,
            vector<Span>& p_solution, unordered_set<int>& is_nested, const int& last_ignored_end)
    {
        //There is an independent interval to the left
        if(last_ignored_end < a)
            return 0;

        int cd = -1;
        int cd_idx = 0;
        for(int i = a; i < b; ++i)
            if(intervals[i] >= 0 && is_nested.find(intervals[i]) != is_nested.end())
            {
                cd = intervals[i];
                cd_idx = i;
                break;
            }

        //Base case
        if(cd == -1)
            return 0;

        int W = 0;
        vector<Span> solution_candidate;
        vector<Span> solution_best;

        auto check_for_improvement = [&](const int& W_n)
        {
            if(W < W_n)
            {
                W = W_n;
                solution_best.swap(solution_candidate);
            }
        };

        //Do not include the first interval
        check_for_improvement(recurrence(info.at(cd)->start + 1, b, solution_candidate, is_nested, info.at(cd)->end));

        //Include the first interval
        solution_candidate.clear();
        solution_candidate.push_back(Span(info.at(cd)->id, -1));
        check_for_improvement(recurrence(info.at(cd)->end + 1, b, solution_candidate, is_nested, last_ignored_end) + one_dms[cd].one_dms_cd);

        p_solution.reserve(p_solution.size() + solution_best.size());
        p_solution.insert(p_solution.end(), solution_best.begin(), solution_best.end());
        return W;
    };

    auto compute_one_dms_for_single_interval = [&](const int& interval)
    {
        int W = info.at(interval)->weight;
        int a = info.at(interval)->start;
        int b = info.at(interval)->end;

        auto nested = get_nested(a, b);
        vector<Span> p_solution;
        W += recurrence(a, b, p_solution, nested, 10000);
        one_dms[interval].add_value(W, p_solution);
    };

    unsigned int max_length = intervals.size();
    vector<vector<Span>> spans_by_length(max_length);
    for(auto i1 : info)
        spans_by_length[length(i1.second)].push_back(Span(i1.second->id, -1));

    for(unsigned int l = 0; l < max_length; ++l)
        for(unsigned int i = 0; i < spans_by_length[l].size(); ++i)
        {
            auto& span = spans_by_length[l][i];
            compute_one_dms_for_single_interval(span.first);
        }

    int W_0 = one_dms.at(0).one_dms_cd;
    int W_c = 0;
    compute_solution(Span(0, -1), W_c);

    assert(W_0 == W_c);

    return W_0;
}


int degree_1_subgraph(vector<int>& solution, const vector<int>& intervals,
                      const unordered_map<int, std::shared_ptr<IntervalInfo>>& info, const int& w_outer)
{
    unordered_map<int, PartialSolution> one_dms;

    auto length = [&](std::shared_ptr<IntervalInfo> info) {return unsigned(info->end - info->start);};
    auto length2 = [&](std::shared_ptr<IntervalInfo> l_info, std::shared_ptr<IntervalInfo> r_info) {return unsigned(r_info->end - l_info->start);};

    auto get_nested = [&](const int& l, const int& r)
    {
        unordered_set<int> nested;
        unordered_set<int> candidates;
        for(auto it = intervals.begin() + l + 1; it < intervals.begin() + r; ++it)
        {
            int id = *it;
            if(id > 0)
                candidates.insert(id);
            else if(candidates.find(abs(id)) != candidates.end())
                nested.insert(abs(id));
        }

        return nested;
    };

    std::function<void (const Span&, int&, const int&)> compute_solution = [&](const Span& span, int& W_c, const int& w_outer)
    {     
        if(span.second == -1)
        {
            W_c = W_c + info.at(span.first)->weight;
            if(span.first != 0)
                solution.push_back(span.first);

            for(const auto& s : one_dms[span.first].one_dms_cd_spans)
                compute_solution(s, W_c, w_outer);
        }
        else
        {
            int cd = span.first;
            int ef = span.second;

            if(one_dms.at(ef).one_dms_cd_ef.find(cd) != one_dms.at(ef).one_dms_cd_ef.end())
                swap(cd, ef);

            W_c = W_c + info.at(span.first)->weight + info.at(span.second)->weight - w_outer;
            solution.push_back(cd);
            solution.push_back(ef);
            for(const auto& s : one_dms.at(cd).one_dms_cd_ef_spans.at(ef))
                compute_solution(s, W_c, w_outer);
        }
    };

    std::function<int (const int&, const int&, vector<Span>&, unordered_set<int>&, const int&, const int&)> recurrence = [&](const int&a, const int& b,
            vector<Span>& p_solution, unordered_set<int>& is_nested, const int& last_ignored_end, const int& w_outer)
    {
        //There is an independent interval to the left
        if(last_ignored_end < a)
            return 0;

        int cd = -1;
        int cd_idx = 0;
        for(int i = a; i < b; ++i)
            if(intervals[i] >= 0 && is_nested.find(intervals[i]) != is_nested.end())
            {
                cd = intervals[i];
                cd_idx = i;
                break;
            }

        //Base case
        if(cd == -1)
            return 0;

        int W = 0;
        vector<Span> solution_candidate;
        vector<Span> solution_best;

        auto check_for_improvement = [&](const int& W_n)
        {
            if(W < W_n)
            {
                W = W_n;
                solution_best.swap(solution_candidate);
            }
        };

        //Do not include the first interval
        check_for_improvement(recurrence(info.at(cd)->start + 1, b, solution_candidate, is_nested, info.at(cd)->end, w_outer));

        //Include the first interval
        solution_candidate.clear();
        solution_candidate.push_back(Span(info.at(cd)->id, -1));
        check_for_improvement(recurrence(info.at(cd)->end + 1, b, solution_candidate, is_nested, last_ignored_end, w_outer) + one_dms[cd].one_dms_cd);

        //Include the first interval and its right intersections
        for(auto& ef_info : info.at(cd)->right_intersections)
            if(is_nested.find(ef_info->id) != is_nested.end())
            {
                solution_candidate.clear();
                solution_candidate.push_back(Span(info.at(cd)->id, ef_info->id));
                check_for_improvement(recurrence(ef_info->end + 1, b, solution_candidate, is_nested, last_ignored_end, w_outer) + one_dms[cd].one_dms_cd_ef[ef_info->id]);
            }

        p_solution.reserve(p_solution.size() + solution_best.size());
        p_solution.insert(p_solution.end(), solution_best.begin(), solution_best.end());
        return W;
    };

    auto compute_one_dms_for_single_interval = [&](const int& interval, const int& w_outer)
    {
        int W = info.at(interval)->weight;
        int a = info.at(interval)->start;
        int b = info.at(interval)->end;

        auto nested = get_nested(a, b);
        vector<Span> p_solution;
        W += recurrence(a, b, p_solution, nested, 10000, w_outer);
        one_dms[interval].add_value(W, p_solution);
    };

    auto compute_one_dms_for_intersection = [&](const Span& span, const int& w_outer)
    {
        int W = info.at(span.first)->weight + info.at(span.second)->weight - w_outer;
        int a = info.at(span.first)->start;
        int b = info.at(span.first)->end;
        int c = info.at(span.second)->start;
        int d = info.at(span.second)->end;

        assert(a < c && c < b && b < d);

        auto nested_L = get_nested(a, c);
        auto nested_M = get_nested(c, b);
        auto nested_R = get_nested(b, d);

        vector<Span> p_solution_L;
        vector<Span> p_solution_M;
        vector<Span> p_solution_R;

        auto W_L = recurrence(a, c, p_solution_L, nested_L, 10000, w_outer);
        auto W_M = recurrence(c, b, p_solution_M, nested_M, 10000, w_outer);
        auto W_R = recurrence(b, d, p_solution_R, nested_R, 10000, w_outer);

        W = W + W_L + W_M + W_R;

        vector<Span> p_solution;
        p_solution.reserve(p_solution_L.size() + p_solution_M.size() + p_solution_R.size());
        p_solution.insert(p_solution.end(), p_solution_L.begin(), p_solution_L.end());
        p_solution.insert(p_solution.end(), p_solution_M.begin(), p_solution_M.end());
        p_solution.insert(p_solution.end(), p_solution_R.begin(), p_solution_R.end());

        one_dms[span.first].add_value(span, W, p_solution);
    };

    auto compute_one_dms_for_span = [&](const Span& span)
    {
        if(span.second == -1)
            compute_one_dms_for_single_interval(span.first, w_outer);
        else
            compute_one_dms_for_intersection(span, w_outer);
    };

    unsigned int max_length = intervals.size();
    vector<vector<Span>> spans_by_length(max_length);
    for(auto i1 : info)
    {
        spans_by_length[length(i1.second)].push_back(Span(i1.second->id, -1));
        for(auto& i2 : i1.second->right_intersections)
            spans_by_length[length2(i1.second,i2)].push_back(Span(i1.second->id, i2->id));
    }

    for(unsigned int l = 0; l < max_length; ++l)
        for(unsigned int i = 0; i < spans_by_length[l].size(); ++i)
        {
            auto& span = spans_by_length[l][i];
            compute_one_dms_for_span(span);
        }

    int W_0 = one_dms.at(0).one_dms_cd;
    int W_c = 0;
    compute_solution(Span(0, -1), W_c, w_outer);

    assert(W_0 == W_c);

    return W_0;
}

int degree_k_subgraph(vector<int>& solution, const vector<int>& intervals, const unordered_map<int,
                      std::shared_ptr<IntervalInfo>>& info, const int& k, const int& w_outer)
{
    if(k == 0)
        return degree_0_subgraph(solution, intervals, info);
    else if(k == 1)
        return degree_1_subgraph(solution, intervals, info, w_outer);
    else
        return -1;
}

int compute_nesting_level(std::shared_ptr<IntervalInfo> i_info, vector<int> intervals)
{
    auto start = i_info->start;
    auto end = i_info->end;

    int lvl = 0;
    unordered_set<int> active;
    for(auto it = intervals.begin() + start + 1; it < intervals.begin() + end; ++it)
    {
        int id = *it;
        if(id < 0)
            active.erase(abs(id));
        else
        {
            active.insert(id);
            lvl = max((int) active.size(), lvl);
        }
    }

    return lvl;
}

#endif // INTERVALS_H
