#include <fstream>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <unordered_map>
#include <vector>

#include <vtkm/filter/flow/Tracer.h>
#include <vtkm/filter/flow/vtkm_filter_flow_export.h>

//in cpp 17 version, use filesystem direactly
//#include <filesystem>
//in cpp 14 file exist
//#include <experimental/filesystem>

#include <sys/stat.h>
#include <sys/types.h>
#include <cstring>
#include <errno.h>


namespace vtkm
{
namespace filter
{
namespace flow
{

vtkm::filter::flow::Tracer* GetTracer::g_Tracer = new Tracer;

VTKM_FILTER_FLOW_EXPORT
vtkm::filter::flow::Tracer* GetTracer::Get()
{
  return g_Tracer;
}

static Buffer* buffer = NULL;

VTKM_FILTER_FLOW_EXPORT bool Tracer::SetTracingStatus(bool iftracing) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetTracingStatus");
  }
  return buffer->m_iftracingParticle = iftracing;
}

VTKM_FILTER_FLOW_EXPORT bool Tracer::GetTracingStatus() const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetTracingStatus");
  }
  return buffer->m_iftracingParticle;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::SetTraceParticleId(int particleId) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call SetTraceParticleId");
  }
  buffer->m_tracingParticleId = particleId;
}

VTKM_FILTER_FLOW_EXPORT int Tracer::GetTraceParticleId() const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetTraceParticleId");
  }
  return buffer->m_tracingParticleId;
}

VTKM_FILTER_FLOW_EXPORT bool Tracer::IfTracingCustomized(vtkm::Id pid) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call IfTracingCustomized");
  }

  return buffer->m_tracingParticleId == pid;
}

VTKM_FILTER_FLOW_EXPORT int Tracer::GetIterationStep() const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetIterationStep");
  }
  return buffer->m_iterationStep;
}

VTKM_FILTER_FLOW_EXPORT int Tracer::GetRankID() const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetRankID");
  }
  return buffer->m_rank;
}

//this elapsed time start from the go start time
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetElapsedTime() const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call GetElapsedTime");
  }

  //the original vtkm timer returns milli second
  //the elapsedTime here is micro second after timing 1000

  //return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
  //                                                             buffer->InitTime)
  //  .count();
  // vtkm timer return second, we get the milli second here

  return 1000000 * (buffer->timer.GetElapsedTime());
}

VTKM_FILTER_FLOW_EXPORT void Tracer::SetTraceID(bool val) const
{
  buffer->TraceIDOn = val;
}
VTKM_FILTER_FLOW_EXPORT bool Tracer::GetTraceID() const
{
  return buffer->TraceIDOn;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::SetCurrGangSize(vtkm::Id sz) const
{
  buffer->PrevGangSize = buffer->CurrGangSize;
  buffer->CurrGangSize = sz;
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetCurrGangSize() const
{
  return buffer->CurrGangSize;
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetPrevGangSize() const
{
  return buffer->PrevGangSize;
}

VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::TimeTraceToBuffer(const std::string& nm)
{

  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call TimeTraceToBuffer");
  }

  //auto endTime = std::chrono::steady_clock::now();
  //auto elapsedTime =
  //  std::chrono::duration_cast<std::chrono::milliseconds>(endTime - buffer->InitTime).count();

  //measure the elapsed time since the timer start
  //the start is setted by the vis reader
  //the time keeps running
  //the elapsedTime time is milli seconds here
  double elapsedTime = static_cast<double>(this->GetElapsedTime());

  //char str[256];
  //the key is defined as iterationStep_round_<user specified key>
  //sprintf(str, "%d_%d_%s %lf\n", nm.c_str(), buffer->m_iterationStep, round, elapsedTime);
  //std::string timeStr(str);
  //std::stringstream ss;
  buffer->bufferTime << nm << "_" << buffer->m_iterationStep << " " << elapsedTime << std::endl;
  //for (char c : ss.str())
  //{
  //  buffer->bufferTime.push_back(c);
  //}

  return elapsedTime;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::CounterToBuffer(vtkm::Id round,
                                                     vtkm::Id AdvectedSteps,
                                                     std::unordered_map<int, int> sendParticleInfo)
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call TimeTraceToBuffer");
  }

  buffer->bufferCounter << buffer->m_iterationStep << "," << round << "," << AdvectedSteps << ",";
  buffer->bufferCounter << "[";
  for (auto it = sendParticleInfo.begin(); it != sendParticleInfo.end(); it++)
  {
    buffer->bufferCounter << it->first << ":" << it->second << ",";
  }
  buffer->bufferCounter << "]";
  buffer->bufferCounter << std::endl;

  //for (char c : ss.str())
  //{
  //  buffer->bufferCounter.push_back(c);
  //}
}

VTKM_FILTER_FLOW_EXPORT void Tracer::Init(int rank)
{
  if (buffer == NULL)
  {
    //only create new tracer for the first time
    buffer = new Buffer;
  }

  buffer->bufferCounter << "SimCycle,Round,AdvectedSteps,Dest:Particles" << std::endl;

  buffer->bufferTerminatedParticles
    << "SimCycle,ParticleID,RemovedReason,ActiveTime,NumComm,"
       "TraversedNumofBlocks,AccBO,AccEO,AccAdv,AccAllAdv,AccWait,AccWB,NumSteps,NumSmallSteps,"
       "AccGangSize,AccPrevGangSize,AccSmallA"
    << std::endl;

  buffer->bufferParticlesInBlock << "SimCycle,RankID,ParticleID,NumAdvcStep,Px,Py,Pz,TotalAliveTime"
                                 << std::endl;


  buffer->bufferParticleDetailsInBlock << "Event,SimCycle,BlockID(RankId),ParticleID,CurrTime,"
                                          "AdvectedSteps,ParticleNum,PrevParticleNum"
                                       << std::endl;

  buffer->bufferAlgorithmRecorder << "Time, Event, Data" << std::endl;

  buffer->m_rank = rank;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::StartTimer()
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call StartTimer");
  }
  buffer->timer.Start();
  //buffer->InitTime = std::chrono::steady_clock::now();
}

VTKM_FILTER_FLOW_EXPORT void Tracer::StopTimer()
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null when call StopTimer");
  }
  buffer->timer.Stop();
  //buffer->InitTime = std::chrono::steady_clock::now();
}


VTKM_FILTER_FLOW_EXPORT void Tracer::AlgorithmRecorder(const std::string& event,
                                                       const std::string& data)
{
  if (buffer == nullptr)
    throw std::runtime_error("buffer is null in AlgorithmRecorder");

  buffer->bufferAlgorithmRecorder << this->GetElapsedTime() << "," << event << ", " << data
                                  << std::endl;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::ResetIterationStep(int step)
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null");
  }
  buffer->m_iterationStep = step;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::ParticleToBuffer(vtkm::Id simCycle,
                                                      vtkm::Id particleID,
                                                      vtkm::Id numSteps,
                                                      vtkm::Id numSmallSteps,
                                                      char removedReason,
                                                      vtkm::FloatDefault activeTime,
                                                      vtkm::Id numComm,
                                                      vtkm::Id traversedNumofBlocks,
                                                      vtkm::FloatDefault accBO,
                                                      vtkm::FloatDefault accEO,
                                                      vtkm::FloatDefault accAdv,
                                                      vtkm::FloatDefault accAllAdv,
                                                      vtkm::FloatDefault accWait,
                                                      vtkm::FloatDefault accWB,
                                                      vtkm::Id gangSize,
                                                      vtkm::Id prevGangSize,
                                                      vtkm::Id accSmallA) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleToBuffer");
  }


  buffer->bufferTerminatedParticles
    << "s" << simCycle << "," << particleID << "," << removedReason << "," << activeTime << ","
    << numComm << "," << traversedNumofBlocks << "," << accBO << "," << accEO << "," << accAdv
    << "," << accAllAdv << "," << accWait << "," << accWB << "," << numSteps << "," << numSmallSteps
    << "," << gangSize << "," << prevGangSize << "," << accSmallA << std::endl;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::ParticleEventToBuffer(vtkm::Id simCycle,
                                                           int rank,
                                                           int particleID,
                                                           const std::string& event,
                                                           vtkm::FloatDefault currTime) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleToBuffer");
  }

  int advS = -1, pnum = -1, prevpnum = -1;
  buffer->bufferParticleDetailsInBlock << event << "," << simCycle << "," << rank << ","
                                       << particleID << "," << currTime << "," << advS << ","
                                       << pnum << "," << prevpnum << std::endl;
}


VTKM_FILTER_FLOW_EXPORT void Tracer::ParticleInBlockToBuffer(vtkm::Id simCycle,
                                                             vtkm::Id rank,
                                                             vtkm::Id particleID,
                                                             vtkm::Id numAdvecSteps,
                                                             const vtkm::Vec3f& pos,
                                                             vtkm::FloatDefault aliveTime) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleInBlockToBuffer");
  }

  // SimCycle,RankID,ParticleID,NumAdvcStep,Px,Py,Pz,TotalAliveTime
  buffer->bufferParticlesInBlock << "s" << simCycle << "," << rank << "," << particleID << ","
                                 << numAdvecSteps << "," << pos[0] << "," << pos[1] << "," << pos[2]
                                 << "," << aliveTime << std::endl;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::ParticleDetailsToBuffer(vtkm::Id simCycle,
                                                             vtkm::Id rankId,
                                                             vtkm::Id particleID,
                                                             const std::string& Event,
                                                             vtkm::FloatDefault currTime,
                                                             vtkm::Id AdvectedSteps,
                                                             vtkm::Id gangSize,
                                                             vtkm::Id prevGangSize) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleDetailsToBuffer");
  }

  buffer->bufferParticleDetailsInBlock << Event << "," << simCycle << "," << rankId << ","
                                       << particleID << "," << currTime << "," << AdvectedSteps
                                       << "," << gangSize << "," << prevGangSize << std::endl;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::SetBegOverheadStart() const
{
  buffer->BegOverhead[0] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetBegOverheadEnd() const
{
  buffer->BegOverhead[1] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetEndOverheadStart() const
{
  buffer->EndOverhead[0] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetEndOverheadEnd() const
{
  buffer->EndOverhead[1] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetAdvectStart() const
{
  buffer->Advect[0] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetAdvectEnd() const
{
  buffer->Advect[1] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetAllAdvectStart() const
{
  buffer->AllAdvect[0] = this->GetElapsedTime();
}
VTKM_FILTER_FLOW_EXPORT void Tracer::SetAllAdvectEnd() const
{
  buffer->AllAdvect[1] = this->GetElapsedTime();
}


VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetBegOverheadStart() const
{
  return buffer->BegOverhead[0];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetBegOverheadEnd() const
{
  return buffer->BegOverhead[1];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetEndOverheadStart() const
{
  return buffer->EndOverhead[0];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetEndOverheadEnd() const
{
  return buffer->EndOverhead[1];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetAdvectStart() const
{
  return buffer->Advect[0];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetAdvectEnd() const
{
  return buffer->Advect[1];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetAllAdvectStart() const
{
  return buffer->AllAdvect[0];
}
VTKM_FILTER_FLOW_EXPORT vtkm::Id Tracer::GetAllAdvectEnd() const
{
  return buffer->AllAdvect[1];
}


bool dirExists(const std::string& path) {
    struct stat info;
    if (stat(path.c_str(), &info) != 0) {
        return false; // Cannot access path
    } else if (info.st_mode & S_IFDIR) {
        return true; // Path is a directory
    }
    return false; // Path exists but is not a directory
}

bool createDir(const std::string& path) {
    if (mkdir(path.c_str(), 0777) == 0) {
        return true; // Directory created
    } else {
        std::cerr << "Error creating directory: " << strerror(errno) << std::endl;
        return false; // Error occurred
    }
}

//only call this for one rank
VTKM_FILTER_FLOW_EXPORT void Tracer::CreateDir(std::string prefix){

  //create the dir if the prefix dir is not right
  std::string dirPath = "./" + prefix;

  // Check if directory exists
  if (dirExists(dirPath) == false)
  {
    // Create the directory if it doesn't exist
    if (createDir(dirPath) == false)
    {
      throw std::runtime_error("Failed to create directory:"+dirPath);
    }
    std::cout << "ok to create " << dirPath << std::endl;
  }
  return;
}

VTKM_FILTER_FLOW_EXPORT void Tracer::OutputBuffer(int rank, std::string prefix)
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in OutputBuffer");
  }

  //create the dir if the prefix dir is not right
  std::string dirPath = "./" + prefix;

  // Check if directory exists
  if (dirExists(dirPath) == false)
  {
    throw std::runtime_error(dirPath+" not exist");
  }

  std::ofstream timeTraceStream;
  char temp[128];
  sprintf(temp, "%s/timetrace.%d.out", prefix.c_str(), rank);
  std::cout << "---trace output buffer is: " << temp << std::endl;
  timeTraceStream.open(temp, std::ofstream::out);
  timeTraceStream << buffer->bufferTime.rdbuf();
  timeTraceStream.close();

  std::ofstream counterStream;
  sprintf(temp, "%s/counter.%d.out", prefix.c_str(), rank);
  counterStream.open(temp, std::ofstream::out);
  counterStream << buffer->bufferCounter.rdbuf();
  counterStream.close();

  std::ofstream particleStream;
  sprintf(temp, "%s/particle.%d.out", prefix.c_str(), rank);
  particleStream.open(temp, std::ofstream::out);
  particleStream << buffer->bufferTerminatedParticles.rdbuf();
  particleStream.close();

  std::ofstream particlePathStream;
  sprintf(temp, "%s/particle_path.%d.out", prefix.c_str(), rank);
  particlePathStream.open(temp, std::ofstream::out);
  particlePathStream << buffer->bufferParticlesInBlock.rdbuf();
  particlePathStream.close();

  std::ofstream particleDetailsStream;
  sprintf(temp, "%s/particle_tracing_details.%d.out", prefix.c_str(), rank);
  particleDetailsStream.open(temp, std::ofstream::out);
  particleDetailsStream << buffer->bufferParticleDetailsInBlock.rdbuf();
  particleDetailsStream.close();

  std::ofstream algorithmRecorderStream;
  sprintf(temp, "%s/algorithmRecorder.%d.out", prefix.c_str(), rank);
  algorithmRecorderStream.open(temp, std::ofstream::out);
  algorithmRecorderStream << buffer->bufferAlgorithmRecorder.rdbuf();
  algorithmRecorderStream.close();
};


VTKM_FILTER_FLOW_EXPORT void Tracer::Finalize()
{
  if (buffer != NULL)
  {
    delete buffer;
  }
  buffer = NULL;
};


}
}
}
