/* Distributed under the Apache License, Version 2.0.
   See accompanying NOTICE file for details.*/

#include "utils/TimingProfile.h"

using Clock = std::chrono::high_resolution_clock;

enum class State
{
  Ready,      // Timer has no data and is not running
  Running,    // Timer is currently running
  Ran         // Timer has been stopped and has data
};

struct Timer
{
  Clock::time_point start;
  Clock::time_point end;
  State state = State::Ready;
};

class TimingProfile::pimpl
{
public:
  std::map<std::string, Timer> timers;
};

TimingProfile::TimingProfile()
{
  my = new pimpl();
}
TimingProfile::~TimingProfile()
{
  delete my;
}

void TimingProfile::Clear()
{
  my->timers.clear();
}

void TimingProfile::Reset(const std::string& label)
{
  if (label.empty())
  {
    return;
  }
  if (my->timers[label].state == State::Running)
  {
    my->timers[label].start = Clock::now();
  }
  else if (my->timers[label].state == State::Ran)
  {
    my->timers[label].state = State::Ready;
  }
}

void TimingProfile::Start(const std::string& label)
{
  if (label.empty())
  {
    return;
  }

  my->timers[label].start = Clock::now();
  my->timers[label].state = State::Running;
}

void TimingProfile::Stop(const std::string& label)
{
  if (label.empty())
  {
    return;
  }

  if (my->timers[label].state == State::Running)
  {
    my->timers[label].end = Clock::now();
    my->timers[label].state = State::Ran;
  }
}

template<typename Duration>
typename Duration::rep GetElapsedTime(Timer& timer)
{
  if (timer.state == State::Running)
  {
    return std::chrono::duration_cast<Duration>(Clock::now() - timer.start).count();
  }
  else if (timer.state == State::Ran)
  {
    return std::chrono::duration_cast<Duration>(timer.end - timer.start).count();
  }
  else
  {
    return typename Duration::rep(0);
  }
}
double TimingProfile::GetElapsedTime_s(const std::string& label)
{
  if (label.empty())
  {
    return 0;
  }
  std::chrono::milliseconds::rep milliseconds = GetElapsedTime<std::chrono::milliseconds>(my->timers[label]);
  double seconds = milliseconds / 1000.0;
  return seconds;
}


