#include "JikanDepths.h"
#include "ToJSON.h"

#include <iostream>
#include <map>
#include <string>

using namespace std;

class Jikan Timer;

Jikan::Jikan()
{
    this->EventType["Synchronous events"] = set<string>();
    this->EventType["Asynchronous events"] = set<string>();
    this->EventType["CUDA events"] = set<string>();

    this->JSONname = OUTPUT_NAME + string(".json");
    this->JSONdata = "";
    this->Error = "";
};

#ifdef INCLUDE_MPI
    bool Jikan::ifWriteProc(MPI_Comm comm, int id)
    {
        bool if_write = false;
        int init_flag, fin_flag, iproc;

        MPI_Initialized(&init_flag);
        MPI_Finalized(&fin_flag);

        bool if_MPI = (init_flag) && (!fin_flag);
        if(if_MPI)
        {
            MPI_Comm_rank(comm, &iproc);
            if( iproc == id)
                if_write = true;
        }
        else
            if_write = true;


        return if_write;
    }
#else
    bool Jikan::ifWriteProc()
    {
        return true;
    }
#endif

Jikan::~Jikan()
{
    this->Events.clear();
    map<string, set<string> >::iterator it;

    for(it = this->EventType.begin(); it!=this->EventType.end(); ++it)
        it->second.clear();
    this->EventType.clear();
}

bool Jikan::ifContains(const string& Name)
{
    map<string, EventData>::iterator it;

    it = this->Events.find(Name);
    if (it == this->Events.end())
        return false;

    return true;
}

void Jikan::Jikan_sync_start(const string& name)
{
    bool ExistFlag;
    ExistFlag = this->ifContains(name);

    if(ExistFlag)
    {
        chrono_time_type start = chrono::steady_clock::now();
        (this->Events)[name].start = start;
    }
    else
    {
        this->EventType["Synchronous events"].insert(name);

        (this->Events)[name] = EventData(name);
        chrono_time_type start = chrono::steady_clock::now();
        (this->Events)[name].start = start;
    }

    (this->Events)[name].ifStart = true;
}

void Jikan::Jikan_sync_end(const string& name)
{
    bool ExistFlag, ifStart;
    ExistFlag = this->ifContains(name);
    ifStart = (this->Events)[name].ifStart;

    if((!ExistFlag) || (!ifStart))
        return;

    chrono_time_type end = chrono::steady_clock::now();
    chrono_time_type start = (this->Events)[name].start;

    double main_elapsed = chrono::duration_cast<chrono::nanoseconds>(end - start).count() * 1e-9;

    (this->Events)[name].elapsed_time += main_elapsed;
    (this->Events)[name].count ++;

#ifdef SAVE_TIME_SERIES
    (this->Events)[name].time_series.push_back(main_elapsed);
#endif

    (this->Events)[name].ifStart = false;
}

void Jikan::Jikan_async_start(const string& name)
{
    bool ExistFlag;
    ExistFlag = this->ifContains(name);

    if(ExistFlag)
    {
        chrono_time_type start = chrono::steady_clock::now();
        (this->Events)[name].start = start;
    }
    else
    {
        this->EventType["Asynchronous events"].insert(name);

        (this->Events)[name] = EventData(name);
        chrono_time_type start = chrono::steady_clock::now();
        (this->Events)[name].start = start;
    }

    (this->Events)[name].ifStart = true;
}

void Jikan::Jikan_async_end(const string& name)
{
    bool ExistFlag, ifStart;
    ExistFlag = this->ifContains(name);
    ifStart = (this->Events)[name].ifStart;

    if((!ExistFlag) || (!ifStart))
        return;

    chrono_time_type end = chrono::steady_clock::now();
    chrono_time_type start = (this->Events)[name].start;

    double main_elapsed = chrono::duration_cast<chrono::nanoseconds>(end - start).count() * 1e-9;

    (this->Events)[name].elapsed_time += main_elapsed;
    (this->Events)[name].count ++;

#ifdef SAVE_TIME_SERIES
    (this->Events)[name].time_series.push_back(main_elapsed);
#endif

    (this->Events)[name].ifStart = false;
}

void Jikan::Jikan_start(const string& name, const int& mode)
{
    bool ExistFlag;
    ExistFlag = this->ifContains(name);

    if(!ExistFlag)
    {
        (this->Events)[name] = EventData(name);
        (this->Events)[name].GetModeVals(mode);
    }
    
    bool ifMPI = (this->Events)[name].mode[0], ifOpenMP = (this->Events)[name].mode[1], ifCUDA = (this->Events)[name].mode[2];

    if(ifMPI)
    {
        int init_flag, fin_flag;

        MPI_Initialized(&init_flag);
        MPI_Finalized(&fin_flag);

        if((!fin_flag) && init_flag)
            MPI_Barrier(MPI_COMM_WORLD);
    }

    #pragma omp master if(mode == TimerMode::OpenMP)
    {
        
    }
}

void Jikan::GenerateTypedOutputData(const string& EventType, string& EventTypeString)
{
    const int n = this->EventType[EventType].size();
    EventTypeString = JSON::StartTypedBlock(EventType);

    if(n == 0)
    {
        EventTypeString += "\n\t}";
        return;
    }

    string Row;
    string Name;
    set<string>::iterator it;
    set<string>::iterator end_m = this->EventType[EventType].end();
    --end_m;

    for (it = this->EventType[EventType].begin(); it != end_m; ++it)
    {
        Name = *it;
        JSON::GenerateRow(this->Events[Name], Row, JSON::SeparateRows);
        EventTypeString += Row;
    }

    Name = *end_m;
    JSON::GenerateRow(this->Events[Name], Row, JSON::FinishRows);
    EventTypeString += Row;
}

void Jikan::GenerateOutputData()
{
    map<string, set<string> >::iterator it;
    map<string, set<string> >::iterator end_m = this->EventType.end();
    --end_m;

    string Name;
    string Out = "{ ";
    string TypedOutput;

    for(it = this->EventType.begin(); it!=end_m; ++it)
    {    
        Name = it->first;
        TypedOutput = "";
        this->GenerateTypedOutputData(Name, TypedOutput);
        Out += TypedOutput + ",\n";
    }

    Name = end_m->first;
    TypedOutput = "";
    this->GenerateTypedOutputData(Name, TypedOutput);
    Out += TypedOutput + "\n}";

    this->JSONdata = Out;

    bool OutputRes = JSON::WriteOutput(this->JSONname, this->JSONdata);

    if(OutputRes != true)
    {
        this->Error = JSON::Error;
    }

    #ifdef INCLUDE_GPU_TIMER
        this->FreeCudaEvents();
    #endif
}

void Jikan::SetDumpFilename(const std::string& name)
{
    this->JSONname = name;
}