#ifndef vtk_m_tracer_h
#define vtk_m_tracer_h

#include <chrono>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <vector>

#include <vtkm/cont/Timer.h>
#include <vtkm/filter/flow/vtkm_filter_flow_export.h>

namespace vtkm
{
namespace filter
{
namespace flow
{

class Buffer
{
public:
  //the step of the simulation, it might contains multiple step
  //this value is set by reader

  //std::vector<char> bufferTime;
  //std::vector<char> bufferCounter;

  std::stringstream bufferTime;
  std::stringstream bufferCounter;
  std::stringstream bufferTerminatedParticles;
  std::stringstream bufferParticlesInBlock;
  std::stringstream bufferParticleDetailsInBlock;
  std::stringstream bufferAlgorithmRecorder;


  //the start point of the timer is the reader's program
  vtkm::cont::Timer timer;
  //std::chrono::time_point<std::chrono::steady_clock> InitTime;

  int m_iterationStep=0;
  int m_tracingParticleId = -1;
  int m_rank = -1;
  bool m_iftracingParticle = false;

  //particle timers.
  //for each particle, we record the start and end time
  vtkm::Id2 BegOverhead = { 0, 0 }, EndOverhead = { 0, 0 }, Advect = { 0, 0 }, AllAdvect = {0,0};
  vtkm::Id CurrGangSize = 0, PrevGangSize = 0;
  bool TraceIDOn = false;
};

class VTKM_FILTER_FLOW_EXPORT Tracer
{

public:
  VTKM_FILTER_FLOW_EXPORT Tracer(){};

  VTKM_FILTER_FLOW_EXPORT void Init(int rank);

  VTKM_FILTER_FLOW_EXPORT void StartTimer();

  VTKM_FILTER_FLOW_EXPORT void StopTimer();

  VTKM_FILTER_FLOW_EXPORT void ResetIterationStep(int step);

  VTKM_FILTER_FLOW_EXPORT void OutputBuffer(int rank, std::string prefix="trace_cycle0");

  VTKM_FILTER_FLOW_EXPORT void CreateDir(std::string prefix="trace_cycle0");


  //the particle advection may contains multiple round in one operation
  //which round it is now
  VTKM_FILTER_FLOW_EXPORT
  vtkm::Id TimeTraceToBuffer(const std::string& nm);

  VTKM_FILTER_FLOW_EXPORT
  void CounterToBuffer(vtkm::Id round,
                       vtkm::Id AdvectedSteps,
                       std::unordered_map<int, int> sendParticleInfo);

  VTKM_FILTER_FLOW_EXPORT void SetTraceID(bool val) const;
  VTKM_FILTER_FLOW_EXPORT bool GetTraceID() const;
  VTKM_FILTER_FLOW_EXPORT void SetCurrGangSize(vtkm::Id sz) const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetPrevGangSize() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetCurrGangSize() const;

  VTKM_FILTER_FLOW_EXPORT
  void AlgorithmRecorder(const std::string& event, const std::string& data);
  void AlgorithmRecorderV(const std::string& event, const std::vector<int>& vals)
  {
    std::stringstream sstr;
    std::size_t n = vals.size();
    for (std::size_t i = 0; i < n; i++)
    {
      if (i > 0) sstr<<",";
      sstr<<vals[i];
    }
    this->AlgorithmRecorder(event, sstr.str());
  }

  template <typename ParticleType>
  VTKM_FILTER_FLOW_EXPORT
  void ParticleToBuffer(const ParticleType& p) const
  {
    this->ParticleToBuffer(this->GetIterationStep(),
                           p.GetID(),
                           p.GetNumberOfSteps(),
                           p.GetNumSmallSteps(),
                           p.TermReason,
                           p.ActiveTime,
                           p.GetNumComm(),
                           p.GetNumTraveledBlocks(),
                           p.BO,
                           p.EO,
                           p.A,
                           p.AllA,
                           p.W,
                           p.WB,
                           p.GangSize,
                           p.PrevGangSize,
                           p.smallA);
  }

  VTKM_FILTER_FLOW_EXPORT
  void 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;

  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetElapsedTime() const;

  VTKM_FILTER_FLOW_EXPORT void Finalize();

  VTKM_FILTER_FLOW_EXPORT int GetIterationStep() const;

  VTKM_FILTER_FLOW_EXPORT void ParticleEventToBuffer(vtkm::Id simCycle,
                                                     int rank,
                                                     int particleID,
                                                     const std::string& event,
                                                     vtkm::FloatDefault currTime) const;

  VTKM_FILTER_FLOW_EXPORT void ParticleInBlockToBuffer(vtkm::Id simCycle,
                                                       vtkm::Id rank,
                                                       vtkm::Id particleID,
                                                       vtkm::Id numAdvecSteps,
                                                       const vtkm::Vec3f& pos,
                                                       vtkm::FloatDefault aliveTime) const;

  VTKM_FILTER_FLOW_EXPORT void SetTraceParticleId(int particleId) const;

  VTKM_FILTER_FLOW_EXPORT int GetTraceParticleId() const;

  VTKM_FILTER_FLOW_EXPORT void ParticleDetailsToBuffer(vtkm::Id simCycle,
                                                       vtkm::Id rank,
                                                       vtkm::Id particleID,
                                                       const std::string& Event,
                                                       vtkm::FloatDefault currTime,
                                                       vtkm::Id AdvectedSteps,
                                                       vtkm::Id gangSize,
                                                       vtkm::Id prevGangSize) const;

  VTKM_FILTER_FLOW_EXPORT bool IfTracingCustomized(vtkm::Id pid) const;

  VTKM_FILTER_FLOW_EXPORT bool SetTracingStatus(bool iftracing) const;

  VTKM_FILTER_FLOW_EXPORT bool GetTracingStatus() const;

  VTKM_FILTER_FLOW_EXPORT int GetRankID() const;

  VTKM_FILTER_FLOW_EXPORT void SetBegOverheadStart() const;
  VTKM_FILTER_FLOW_EXPORT void SetBegOverheadEnd() const;
  VTKM_FILTER_FLOW_EXPORT void SetEndOverheadStart() const;
  VTKM_FILTER_FLOW_EXPORT void SetEndOverheadEnd() const;
  VTKM_FILTER_FLOW_EXPORT void SetAdvectStart() const;
  VTKM_FILTER_FLOW_EXPORT void SetAdvectEnd() const;
  VTKM_FILTER_FLOW_EXPORT void SetAllAdvectStart() const;
  VTKM_FILTER_FLOW_EXPORT void SetAllAdvectEnd() const;

  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetBegOverheadStart() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetBegOverheadEnd() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetEndOverheadStart() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetEndOverheadEnd() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetAdvectStart() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetAdvectEnd() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetAllAdvectStart() const;
  VTKM_FILTER_FLOW_EXPORT vtkm::Id GetAllAdvectEnd() const;
};

class VTKM_FILTER_FLOW_EXPORT GetTracer
{
  public:
  GetTracer() = default;

  static vtkm::filter::flow::Tracer* Get();

  static vtkm::filter::flow::Tracer* g_Tracer;
};


}
}
}

#endif
