#include <iostream>
#include <boost/filesystem.hpp>
#include <omp.h>

#include "ogdffunctions.h"
#include "spider_layout.h"

using namespace std;
namespace fs = boost::filesystem;

int REPORT_EVERY = 10;

void write_header(const fs::path& filename, const string& del = ",")
{
    ofstream f;
    f.open(filename.string());

    f << "nodes" << del
      << "edges" << del
      << "inside" << del
      << "outside" << del
      << "crossings_inside" << del
      << "crossings_outside" << del
      << "saved_crossings" << del
      << "crossings_circular_layout" << del
      << "circular_time" << del
      << "subgraph_time" << del
      << "id" << del
      << "graph_name"
      << endl;

    f.close();
}

void write_stats(const Run& run, const fs::path& filename, const string& del = ",")
{
    ofstream f;
    f.open(filename.string(), ios::app);
    f << run.get_csv_string(del) << endl;

    f.close();
}

void run_test(ogdf::Graph& g, ogdf::GraphAttributes& ga, Run& run0, Run& run1, Run& run12, const fs::path& out_folder, const fs::path& name)
{
    auto path_0 = out_folder / "k_0" / name;
    auto path_1 = out_folder / "k_1_1" / name;
    auto path_12 = out_folder / "k_1_2" / name;

    spider_layout(g, ga, run0, path_0.string(), 0);
    spider_layout(g, ga, run1, path_1.string(), 1);
    spider_layout(g, ga, run12, path_12.string(), 1, 2);
}

int load_graphs(const fs::path& in_dir, vector<fs::path>& names, vector<ogdf::Graph>& graphs, vector<ogdf::GraphAttributes>& graph_attributes)
{
    auto is_graph_file = [](const fs::path& p) {return p.extension() == ".graphml" || p.extension() == ".gml";};
    unsigned int num_graphs = count_if(fs::directory_iterator(in_dir), fs::directory_iterator(), [&](const fs::path& p){return is_graph_file(p);});
    auto keep_biggest_biconnected_component = [](ogdf::Graph& g)
    {
        ogdf::EdgeArray<int> bicc(g, 0);
        int num_comp;
        ogdf::biconnectedComponents(g, bicc, num_comp);

        unordered_map<int, int> count;
        for(auto e : g.edges)
        {
            if(count.find(bicc[e]) == count.end())
                count[bicc[e]] = 0;

            count[bicc[e]]++;
        }

        int biggest = -1;
        int biggest_size = -1;
        for(auto c : count)
        {
            if(c.second > biggest_size)
            {
                biggest_size = c.second;
                biggest = c.first;
            }
        }

        vector<ogdf::edge> to_delete;
        for(auto e : g.edges)
            if(bicc[e] != biggest)
                to_delete.push_back(e);

        for(auto e : to_delete)
            g.delEdge(e);

        vector<ogdf::node> to_delete_n;
        for(auto n : g.nodes)
            if(n->degree() == 0)
                to_delete_n.push_back(n);

        for(auto n : to_delete_n)
            g.delNode(n);

        assert(ogdf::isBiconnected(g) && g.numberOfNodes() > 1);
    };

    names.reserve(num_graphs);
    graphs.reserve(num_graphs);

    for(const auto& file : fs::directory_iterator(in_dir))
    {
        if(!is_graph_file(file))
        {
            cout << "Skipping " << file.path().extension() << endl;
            continue;
        }

        ogdf::Graph g;

        if(file.path().extension() == ".graphml")
            ogdf::GraphIO::readGraphML(g, file.path().string());
        else if(file.path().extension() == ".gml")
            ogdf::GraphIO::readGML(g, file.path().string());

        graphs.push_back(g);
        names.push_back(file.path().filename());
    }

    graph_attributes.resize(graphs.size());
    for(unsigned int i = 0; i < graphs.size(); ++i)
        graph_attributes[i].init(graphs[i], 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);

    return num_graphs;
}

void run_test_in_directories(const fs::path& in_dir, const fs::path& out_dir)
{
    auto stat_path_0 = out_dir / "stats_0.csv";
    auto stat_path_1 = out_dir / "stats_1_1.csv";
    auto stat_path_12 = out_dir / "stats_1_2.csv";
    write_header(stat_path_0);
    write_header(stat_path_1);
    write_header(stat_path_12);

    auto path_0 = out_dir / "k_0";
    auto path_1 = out_dir / "k_1_1";
    auto path_12 = out_dir / "k_1_2";
    fs::create_directory(path_0);
    fs::create_directory(path_1);
    fs::create_directory(path_12);

    vector<fs::path> graph_names;
    vector<ogdf::Graph> graphs;
    vector<ogdf::GraphAttributes> graph_attributes;

    size_t num_graphs = load_graphs(in_dir, graph_names, graphs, graph_attributes);

    auto t_begin = chrono::high_resolution_clock::now();
    size_t num_done = 0;
    #pragma omp parallel for
    for(unsigned int i = 0; i < graphs.size(); ++i)
    {
        if(num_done % REPORT_EVERY == 1)
        {
            #pragma omp critical
            {
                auto t_end = chrono::high_resolution_clock::now();
                auto t = chrono::duration_cast<chrono::seconds>(t_end - t_begin).count();
                cout << "Done " << num_done << "/" << num_graphs << " (" << 100.0*num_done/num_graphs << "%) in "
                     << t << " seconds." << endl;
            }
        }

        Run run0;
        run0.init(i, graph_names[i].filename().string());
        Run run1;
        run1.init(i, graph_names[i].filename().string());
        Run run12;
        run12.init(i, graph_names[i].filename().string());

        run_test(graphs[i], graph_attributes[i], run0, run1, run12, out_dir, graph_names[i].filename());

        #pragma omp critical
        {
            write_stats(run0, stat_path_0);
            write_stats(run1, stat_path_1);
            write_stats(run12, stat_path_12);
            ++num_done;
        }
    }
    auto t_end = chrono::high_resolution_clock::now();
    auto t = chrono::duration_cast<chrono::seconds>(t_end-t_begin).count();
    cout << "The whole ordeal took " << t << " seconds." << endl;
}

void generate_randoms(const fs::path& gml_dir, const int& l, const int& u, const int& d_l, const int& d_u, const int& num)
{
//    #pragma omp parallel for
    for(int i = 0; i < num; ++i)
    {
        ogdf::Graph g;

        while(g.numberOfNodes() == 0)
            get_random_graph(g, l, u, d_l, d_u);

        stringstream ss;
        ss << g.numberOfNodes() << "_" << g.numberOfEdges() << "_" << i << "_" << d_l << "_" << d_u << "_" << l << "_" << u << ".gml";
        auto out = gml_dir / ss.str();
        ogdf::GraphIO::writeGML(g, out.string());
    }
    }

//void run_singular_test()
//{
//    stringstream s_ss;
//    s_ss << "out/" << "stats.csv";

//    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);

//    ogdf::GraphIO::readGML(g, "../out/35_45_2_6/1.gml");

//    stringstream ss_g;
//    ss_g << "../out/" << "test" << ".gml";

//    ogdf::GraphIO::writeGML(g, ss_g.str());

//    Run run;
//    run.init(ga, 0);
//    cout << "Running test with " << g.numberOfNodes() << " nodes and " << g.numberOfEdges() << " edges" << endl << endl;
//    stringstream ss;
//    ss << "../out/" << "test";
//    spider_layout(g, ga, run, ss.str(), 1);
//    cout << endl;

//    write_stats(run, s_ss.str(), ",");
//}

int main(int argc, char *argv[])
{
    if(argc < 2)
        return 0;

    string mode(argv[1]);

    if(mode == "f")
    {
        if(argc < 4)
        {
            cout << "Error not enough arguments" << endl;
            return 0;
        }

        if(argc == 5)
            REPORT_EVERY = atoi(argv[4]);

        if(!boost::filesystem::is_directory(argv[2]))
        {
            cout << "Error " << argv[2] << "is not a directory" << endl;
            return 0;
        }

        if(!boost::filesystem::is_directory(argv[3]))
        {
            cout << "Error " << argv[3] << "is not a directory" << endl;
            return 0;
        }

        fs::path in_dir(argv[2]);
        fs::path out_dir(argv[3]);

        cout << "Run with graphs from " << in_dir.string() << endl;
        run_test_in_directories(in_dir, out_dir);
        cout << endl;
    }
    else if(mode == "r")
    {
        if(!fs::is_directory(argv[2]))
        {
            cout << "Error " << argv[2] << "is not a directory" << endl;
            return 0;
        }

        fs::path out_dir(argv[2]);
        generate_randoms(out_dir, atoi(argv[3]), atoi(argv[4]), atoi(argv[5]), atoi(argv[6]), atoi(argv[7]));

    }
//    else if(strcmp(argv[1], "s"))
//        run_singular_test();
    else
        cout << "Error did not recognize " << argv[1] << endl;
}
