#include <fstream>
#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>


namespace vtkm
{
namespace filter
{
namespace flow
{

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_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 elapsed time is milli seconds here
  //  double elapsedTime = 1000 * (buffer->timer.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" << std::endl;

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


  buffer->bufferParticleDetailsInBlock
    << "Event,SimCycle,BlockID(RankId),ParticleID,CurrTime,AdvectedSteps,ParticleNum" << 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::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,
                                                      char removedReason,
                                                      vtkm::FloatDefault activeTime,
                                                      vtkm::Id numComm,
                                                      vtkm::Id traversedNumofBlocks) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleToBuffer");
  }


  buffer->bufferTerminatedParticles << "s" << simCycle << "," << particleID << "," << removedReason
                                    << "," << activeTime << "," << numComm << ","
                                    << traversedNumofBlocks << 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");
  }


  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 pnum) const
{
  if (buffer == NULL)
  {
    throw std::runtime_error("buffer is null in ParticleDetailsToBuffer");
  }

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

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

  std::ofstream timeTraceStream;
  char temp[64];
  sprintf(temp, "timetrace.%d.out", rank);
  timeTraceStream.open(temp, std::ofstream::out);
  timeTraceStream << buffer->bufferTime.rdbuf();
  timeTraceStream.close();

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

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

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

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

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


}
}
}
