#pragma once
#include "concurrent/waitfree.h"
#include <cstddef>
#include <cstdint>
#include <emscripten/val.h>
#include <map>
#include <timing/time_units.h>
#include <vector>

EMSCRIPTEN_DECLARE_VAL_TYPE(PlaybackSpanList);

struct Uuid {
  uint64_t lower;
  uint64_t upper;

  Uuid() : lower(0), upper(0) {}
  Uuid(uint64_t lower, uint64_t upper) : lower(lower), upper(upper) {}
  Uuid(const std::string &str);

  bool operator==(const Uuid &other) const {
    return lower == other.lower && upper == other.upper;
  }
  bool operator!=(const Uuid &other) const { return !(*this == other); }

  bool operator<(const Uuid &other) const {
    if (upper != other.upper) {
      return upper < other.upper;
    }
    return lower < other.lower;
  }

  std::string toString() const;
};

struct PlaybackSpan {
  Uuid arrangement;
  time_units::Seconds<double> begin;
  time_units::Seconds<double> end;
  uint64_t renderCycleIndex;

  PlaybackSpan() : arrangement{}, begin{0.0}, end{0.0}, renderCycleIndex(0) {}
  PlaybackSpan(const Uuid &arrangement, time_units::Seconds<double> begin,
               uint64_t renderCycleIndex)
      : arrangement(arrangement), begin(begin), end(begin),
        renderCycleIndex(renderCycleIndex) {}

  std::string getArrangementIdString() const { return arrangement.toString(); }
  double getBeginSeconds() const noexcept { return begin.raw(); }
  double getEndSeconds() const noexcept { return end.raw(); }

  void setArrangementIdString(const std::string &str) {
    arrangement = Uuid(str);
  }
  void setBeginSeconds(double begin) {
    this->begin = time_units::Seconds<double>(begin);
  }
  void setEndSeconds(double end) {
    this->end = time_units::Seconds<double>(end);
  }
};

class AnalyticsObserver {
private:
  std::map<Uuid, PlaybackSpan> activeSpans;
  WaitFreeWrap<std::vector<PlaybackSpan>> inactiveSpans;
  uint64_t currentRenderCycleIndex;

public:
  AnalyticsObserver();
  ~AnalyticsObserver();

  // Called from real-time thread only
  void trackPlayback(const Uuid &arrangementId,
                     time_units::Seconds<double> position);
  void trackLooped(const Uuid &arrangementId);
  void trackRenderCycleEnd();

  // Called from non-real-time thread only
  std::vector<PlaybackSpan> flushSpans();
  PlaybackSpanList flushSpansJs();
};