#include "Inputs.hpp"

#include <algorithm>
#include <iostream>
#include <fstream>

Inputs::Inputs(std::string node_name){
    // Here we do all the boring stuff
    FILE* input_file = fopen(INPUTS_FILE, "rb");
    char input_file_buffer[JSON_BUFFER_SIZE];
    rapidjson::FileReadStream is(input_file, input_file_buffer, sizeof(input_file_buffer));
    d.ParseStream(is);
    fclose(input_file);
    
    // Init all variables
    is_sender=d["nodes"][node_name.c_str()]["is_sender"].GetBool();
    use_hint=d["nodes"][node_name.c_str()]["use_hint"].GetBool();
    data_size=d["nodes"][node_name.c_str()]["data_size"].GetInt();
    extended=d["extended"].GetBool();
    seed=d["seed"].GetInt();
    hint_size=d["hint_size"].GetInt();
    n_nodes=d["nodes"].MemberCount();
    latency=d["latency"].GetDouble();

    // Instantiate wake_ts
    for(auto& v:d["nodes"][node_name.c_str()]["wake_ts"].GetArray()){
        wake_ts.push_back(v.GetDouble());
    }

    // Instantiate wake_duration
    for(auto& v:d["nodes"][node_name.c_str()]["wake_duration"].GetArray()){
        wake_duration.push_back(v.GetDouble());
    }
    
    // Identity check
    if(wake_ts.size()<1){
        std::cerr << "Invalid node configuration: wake_ts.size() == 0" <<std::endl;
        exit(1);
    }
    if(wake_ts.size()!=wake_duration.size()){
        std::cerr << "Invalid node configuration: wake_ts.size() != wake_duration.size()" <<std::endl;
        exit(1);
    }
    if(!std::is_sorted(wake_ts.begin(),wake_ts.end())){
        std::cerr << "Invalid node configuration: wake_ts is not sorted" <<std::endl;
        exit(1);
    }

    // Ensure events are merged
    MergeEvents();
}

void Inputs::MergeEvents(){
    for(int i=0;i<(wake_ts.size()-1);i++){
        double cur_ts=wake_ts[i];
        double next_ts=wake_ts[i+1];
        double cur_duration=wake_duration[i];
        double next_duration=wake_duration[i+1];
        // If we should merge then
        if((cur_ts+cur_duration)>=next_ts){
            // Create variable for convenience
            double start=cur_ts;
            double end=std::max(cur_ts+cur_duration,next_ts+next_duration);
            wake_duration[i]=end-start;
            // Now remove next event
            wake_ts.erase(wake_ts.begin() + i + 1);
            wake_duration.erase(wake_duration.begin() + i +1);
            // This is not optimal. Yet it is simple :D
            MergeEvents();
        }
    }
}

double Inputs::GetNextTS(){
    // Ensure that the caller is smart
    if(wake_duration.size()<2){
        std::cerr << "You are trying to access to the next timestamp but it does not exists" <<std::endl;
        exit(1);
    }
    return wake_ts[1];
}

double Inputs::GetNextDuration(){
    // Ensure that the caller is smart
    if(wake_duration.size()<2){
        std::cerr << "You are trying to access to the next duration but it does not exists" <<std::endl;
        exit(1);
    }
    return wake_duration[1];
}

void Inputs::GotoNextEvent(){
    wake_ts.erase(wake_ts.begin());
    wake_duration.erase(wake_duration.begin());
}

void Inputs::DumpEvents(){
    std::cout << "Timestamps:     ";
    for(auto a:wake_ts){
        std::cout << std::setw(5) << a << " ";
    }
    std::cout << std::endl;
    std::cout << "Wake Durations: ";
    for(auto a:wake_duration){
        std::cout << std::setw(5) << a << " ";
    }
    std::cout << std::endl;
}   

void Inputs::AddEvent(double ts, double duration){
    // First handle timestamp
    int pos=0;
    for(auto it = std::begin(wake_ts); it != std::end(wake_ts); ++it) {
        if(*it>=ts){
            wake_ts.insert(it,ts);
            break;
        }
        pos++;
    }

    // Ensure that ts and duration should not go to the end
    if(pos==wake_ts.size()){
        wake_ts.push_back(ts);
        wake_duration.push_back(duration);
    }
    else {
        // Handle durations here
        int pos2=0;
        for(auto it = std::begin(wake_duration); it != std::end(wake_duration); ++it) {
            if(pos==pos2){
                wake_duration.insert(it,duration);
                break;
            }
            else if (it+1==std::end(wake_duration)) {
                wake_duration.push_back(duration);
            }
            pos2++;
        }
    }
    // Don't forget
    MergeEvents();
}

void Inputs::GeneratePlatform(std::string p){
    
    // The boring stuff
    FILE* input_file = fopen(INPUTS_FILE, "rb");
    char input_file_buffer[JSON_BUFFER_SIZE];
    rapidjson::FileReadStream is(input_file, input_file_buffer, sizeof(input_file_buffer));
    rapidjson::Document d;
    d.ParseStream(is);
    fclose(input_file);
    
    // Write platform file
    std::ofstream pf;
    pf.open (p);
    pf << "<?xml version='1.0'?>\n";
    pf << "<!DOCTYPE platform SYSTEM \"http://simgrid.gforge.inria.fr/simgrid/simgrid.dtd\">\n";
    pf << "<platform version=\"4.1\">\n    <AS id=\"AS0\" routing=\"Full\">\n";
    pf << "    <link id=\"link\" bandwidth=\""<<d["bitrate"].GetString()<<"\" latency=\"0ms\" sharing_policy=\"SHARED\"></link>\n";
    for (Value::ConstMemberIterator itr = d["nodes"].MemberBegin(); itr != d["nodes"].MemberEnd(); ++itr)
    {
        std::string name=itr->name.GetString();
        double power_off=d["nodes"][itr->name.GetString()]["power_off"].GetDouble();
        double power_on=d["nodes"][itr->name.GetString()]["power_on"].GetDouble();
        double power_rx=power_on+d["nodes"][itr->name.GetString()]["power_rx"].GetDouble();
        double power_tx=power_on+d["nodes"][itr->name.GetString()]["power_tx"].GetDouble();

        // Create node
        pf << "    <host id=\""<<name<<"\" speed=\"100.0f,100.0f,100.0f,100.0f\" pstate=\"0\">\n";
        pf << "        <prop id=\"wattage_per_state\" value=\""<< power_off<<":"<<power_off<<", "<< power_on<<":"<<power_on<<", "<<power_rx<<":"<<power_rx<<", "<<power_tx<<":"<<power_tx<<"\" />\n";
        pf << "        <prop id=\"wattage_off\" value=\"0\" />\n    </host>\n";
    }
    for (Value::ConstMemberIterator src = d["nodes"].MemberBegin(); src != d["nodes"].MemberEnd(); ++src)
    {
        for (Value::ConstMemberIterator dst = d["nodes"].MemberBegin(); dst != d["nodes"].MemberEnd(); ++dst)
        {
            if(src->name.GetString() != dst->name.GetString())
                pf << "    <route src=\""<<src->name.GetString()<<"\" dst=\""<<dst->name.GetString()<<"\" symmetrical=\"no\"><link_ctn id=\"link\"/></route>\n";
        }
    }
    pf << "    </AS>\n</platform>\n";
    pf.close();
}