#include <iostream>

#ifdef INCLUDE_OPEN_MP
    #include <omp.h>
#endif

#include "Event.h"
#include "Jikan.h"

using namespace std;

EventData::EventData()
{
    this->double_start = 0.0;
    this->start = chrono::steady_clock::now();
    this->elapsed_time = 0.0; 
    this->count = 0;
    this->ContiniousTime = 0.0;
    // this->ifCUDA = 0;
    this->event_name = "Unnamed";
    this->ifStart = false, this->ifEnd = false;
    this->if_mode_set = false;

    this->time_series = vector<double>();
    this->mode = vector<bool>();
    
    #ifdef INCLUDE_GPU_TIMER
        this->InitEventsCUDA();
    #else
        this->ifCUDAinit = false;
    #endif
}

EventData::~EventData()
{
    #ifdef INCLUDE_OPEN_MP
    #pragma omp master
    {
    #endif
        this->time_series.clear();
        this->mode.clear();

        #ifdef INCLUDE_GPU_TIMER
            this->DeinitEventsCUDA();
        #endif
    #ifdef INCLUDE_OPEN_MP
    }
    #endif
}

EventData::EventData(const string& name)
{
    this->double_start = 0.0;
    this->start = chrono::steady_clock::now();
    this->elapsed_time = 0.0; 
    this->count = 0;
    this->ContiniousTime = 0.0;
    this->event_name = name;
    this->ifStart = false, this->ifEnd = false;
    this->if_mode_set = false;

    this->time_series = vector<double>();
    this->mode = vector<bool>();

    #ifdef INCLUDE_GPU_TIMER
        this->InitEventsCUDA();
    #else
        this->ifCUDAinit = false;
    #endif
}

EventData& EventData::operator=(const EventData& src)
{
    this->double_start = src.double_start;
    this->start = src.start;
    this->elapsed_time = src.elapsed_time; 
    this->count = src.count;
    this->ContiniousTime = src.ContiniousTime;
    this->event_name = src.event_name;
    this->ifStart = src.ifStart, this->ifEnd = src.ifEnd;
    this->if_mode_set = src.if_mode_set;
    this->ifCUDAinit = src.ifCUDAinit;

    this->time_series = src.time_series;
    this->mode = src.mode;

    #ifdef INCLUDE_GPU_TIMER
        this->InitEventsCUDA();
        this->gpu_start = src.gpu_start;
        this->gpu_end   = src.gpu_end;

        // printf("Copied %e, ori %e\n", Ellapsed(this->gpu_start, this->gpu_end), Ellapsed(src.gpu_start, src.gpu_end));
    #endif

    return *this;
}

double EventData::GetMeanElapsedTime()
{
    return this->elapsed_time / this->count;
}

void EventData::GetModeVals(const int& mode)
{   
    if((mode == TimerMode::MPI_mode)    || (mode == TimerMode::MPI_OpenMP) || (mode == TimerMode::MPI_CUDA)    || (mode == TimerMode::MPI_OpenMP_CUDA))
        this->mode.push_back(true);
    else
        this->mode.push_back(false);

    if((mode == TimerMode::OpenMP_mode) || (mode == TimerMode::MPI_OpenMP) || (mode == TimerMode::OpenMP_CUDA) || (mode == TimerMode::MPI_OpenMP_CUDA))
        this->mode.push_back(true);
    else
        this->mode.push_back(false);
    
    if((mode == TimerMode::CUDA_mode)   || (mode == TimerMode::MPI_CUDA)   || (mode == TimerMode::OpenMP_CUDA) || (mode == TimerMode::MPI_OpenMP_CUDA))
        this->mode.push_back(true);
    else
        this->mode.push_back(false);

    this->if_mode_set = true;
}

string EventData::GetEventModeName()
{
    bool ifMPI = this->mode[0], ifOpenMP = this->mode[1], ifCUDA = this->mode[2]; 
    int flag = 0;

    string EventModeName = "";

    if(ifMPI)
    {
        EventModeName += "MPI";
        flag = 1;
    }
    if(ifOpenMP)
    {
        if(flag == 1)
            EventModeName += "_";

        EventModeName += "OpenMP";
        flag = 1;
    }
    if(ifCUDA)
    {
        if(flag == 1)
            EventModeName += "_";

        EventModeName += "CUDA";
    }
    if(!ifMPI && !ifOpenMP && !ifCUDA)
        EventModeName += "Asynchronous";

    return EventModeName;
}

// void EventData::MPISync()
// {
// #ifdef INCLUDE_MPI
//     int init_flag, fin_flag;

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

//     if((!fin_flag) && init_flag)
//         MPI_Barrier(MPI_COMM_WORLD);
// #endif
// }

// void EventData::MPISync()
// {
// #ifdef INCLUDE_MPI
//     int init_flag, fin_flag;

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

//     if((!fin_flag) && init_flag)
//         MPI_Barrier(MPI_COMM_WORLD);
// #endif
// }

// void EventData::OpenMPSync()
// {
//     #pragma omp barrier
// }