#pragma once

#include "RNSkLog.h"
#include <chrono>
#include <string>
#include <utility>

#define NUMBER_OF_DURATION_SAMPLES 10

namespace RNSkia {

using frame = std::chrono::duration<int32_t, std::ratio<1, 60>>;
using ms = std::chrono::duration<float, std::milli>;
using high_resolution_clock = std::chrono::high_resolution_clock;

class RNSkTimingInfo {
public:
  explicit RNSkTimingInfo(const std::string &name) : _name(std::move(name)) {
    reset();
  }

  ~RNSkTimingInfo() {}

  void reset() {
    _lastDurationIndex = 0;
    _lastDurationsCount = 0;
    _lastDuration = 0;
    _prevFpsTimer = -1;
    _frameCount = 0;
    _lastFrameCount = -1;
    _didSkip = false;
    _average = 0;
  }

  void beginTiming() { _start = high_resolution_clock::now(); }

  void stopTiming() {
    std::chrono::time_point<std::chrono::steady_clock> stop =
        high_resolution_clock::now();
    addLastDuration(
        std::chrono::duration_cast<std::chrono::milliseconds>(stop - _start)
            .count());
    tick(stop);
    if (_didSkip) {
      _didSkip = false;
      RNSkLogger::logToConsole("%s: Skipped frame. Previous frame time: %lldms",
                               _name.c_str(), _lastDuration);
    }
  }

  void markSkipped() { _didSkip = true; }

  long getAverage() { return static_cast<long>(_average); }
  long getFps() { return _lastFrameCount; }

  void addLastDuration(long duration) {
    _lastDuration = duration;

    // Average duration
    _lastDurations[_lastDurationIndex++] = _lastDuration;

    if (_lastDurationIndex == NUMBER_OF_DURATION_SAMPLES) {
      _lastDurationIndex = 0;
    }

    if (_lastDurationsCount < NUMBER_OF_DURATION_SAMPLES) {
      _lastDurationsCount++;
    }

    _average = 0;
    for (size_t i = 0; i < _lastDurationsCount; i++) {
      _average = _average + _lastDurations[i];
    }
    _average = _average / _lastDurationsCount;
  }

private:
  void tick(std::chrono::time_point<std::chrono::steady_clock> now) {
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                  now.time_since_epoch())
                  .count();

    if (_prevFpsTimer == -1) {
      _prevFpsTimer = ms;
    } else if (ms - _prevFpsTimer >= 1000) {
      _lastFrameCount = _frameCount;
      _prevFpsTimer = ms;
      _frameCount = 0;
    }
    _frameCount++;
  }

  double _lastTimeStamp;
  long _lastDurations[NUMBER_OF_DURATION_SAMPLES];
  int _lastDurationIndex;
  int _lastDurationsCount;
  long _lastDuration;
  std::atomic<double> _average;
  std::chrono::time_point<std::chrono::steady_clock> _start;
  long _prevFpsTimer;
  double _frameCount;
  double _lastFrameCount;
  double _didSkip;
  std::string _name;
};

} // namespace RNSkia
