#ifndef TIMER_HPP
#define TIMER_HPP

#include <chrono>
#include <iostream>

class Timer
{
 public:
  enum class Status
  {
    running,
    paused,
    stopped
  };

 private:
  std::chrono::time_point<std::chrono::high_resolution_clock> m_start;
  std::chrono::duration<double> m_elapsed_sum;

  Status m_status;

 public:
  Status
  status() const
  {
    return m_status;
  }

  double
  seconds() const
  {
    switch (m_status) {
    case Status::running: {
      return (m_elapsed_sum + std::chrono::duration<double>{std::chrono::system_clock::now().time_since_epoch() -
                                                            m_start.time_since_epoch()})
        .count();
    }
    case Status::paused:
    case Status::stopped: {
      return m_elapsed_sum.count();
    }
      // LCOV_EXCL_START
    default: {
      return 0;
    }
      // LCOV_EXCL_STOP
    }
  }

  friend std::ostream&
  operator<<(std::ostream& os, const Timer& timer)
  {
    os << timer.seconds() << 's';
    return os;
  }

  void
  reset()
  {
    m_start       = std::chrono::high_resolution_clock::now();
    m_elapsed_sum = std::chrono::duration<double>::zero();
  }

  void
  stop()
  {
    m_start       = std::chrono::high_resolution_clock::now();
    m_elapsed_sum = std::chrono::duration<double>::zero();
    m_status      = Status::stopped;
  }

  void
  pause()
  {
    if (m_status == Status::running) {
      m_elapsed_sum += std::chrono::high_resolution_clock::now() - m_start;
      m_start  = std::chrono::high_resolution_clock::now();
      m_status = Status::paused;
    }
  }

  void
  start()
  {
    switch (m_status) {
    case Status::running: {
      return;
    }
    case Status::paused:
    case Status::stopped: {
      m_start  = std::chrono::high_resolution_clock::now();
      m_status = Status::running;
    }
    }
  }

  Timer& operator=(const Timer&) = default;
  Timer& operator=(Timer&&)      = default;

  Timer(const Timer&) = default;
  Timer(Timer&&)      = default;

  Timer()
    : m_start{std::chrono::high_resolution_clock::now()},
      m_elapsed_sum{std::chrono::duration<double>::zero()},
      m_status{Status::running}
  {}

  Timer(const std::chrono::time_point<std::chrono::high_resolution_clock>& start)
    : m_start{start}, m_elapsed_sum{std::chrono::duration<double>::zero()}, m_status{Status::running}
  {}

  ~Timer() = default;
};

#endif   // TIMER_HPP