//
// Created by martin on 19.11.21.
//

#ifndef ANIMTESTBED_SYNCTRACK_H
#define ANIMTESTBED_SYNCTRACK_H

#include <cassert>
#include <cmath>

constexpr int cSyncTrackMaxIntervals = 8;

struct SyncTrack {
  SyncTrack() : m_duration(0.f), m_num_intervals(0) {
    for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
      m_sync_markers[i] = 0.f;
      m_interval_ratio[i] = 0.f;
      m_interval_ratio[i] = 0.f;
    }
  }

  float m_duration;
  int m_num_intervals;
  float m_sync_markers[cSyncTrackMaxIntervals];
  float m_interval_start[cSyncTrackMaxIntervals];
  float m_interval_ratio[cSyncTrackMaxIntervals];

  void CalcIntervals() {
    if (m_num_intervals == 0) {
      m_num_intervals = 1;
      m_sync_markers[0] = 0.f;
    }
    for (int i = 0; i < m_num_intervals; i++) {
      assert(m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f);

      int end_index = i < m_num_intervals - 1 ? i + 1 : 0;

      m_interval_start[i] = m_sync_markers[i];
      float interval_end = m_sync_markers[end_index];

      if (interval_end < m_interval_start[i]) {
        interval_end += 1.0f;
      }

      m_interval_ratio[i] = interval_end - m_interval_start[i];
    }
  }

  float CalcSyncFromAbsTime(float abs_time) {
    float sync_time = fmodf(abs_time, m_duration) / m_duration;

    int interval_index = 0;
    while (sync_time >= m_interval_ratio[interval_index]) {
      sync_time -= m_interval_ratio[interval_index];
      interval_index++;
    }

    return float(interval_index) + sync_time / m_interval_ratio[interval_index];
  }

  float CalcRatioFromSyncTime(float sync_time) {
    float interval_ratio = fmodf(sync_time, 1.0f);
    int interval = int(sync_time - interval_ratio);

    return fmodf(
        m_interval_start[interval]
            + m_interval_ratio[interval] * interval_ratio,
        1.0f);
  }

  bool operator==(const SyncTrack& other) const {
    bool result = m_duration == other.m_duration
                  && m_num_intervals == other.m_num_intervals;

    if (!result) {
      return false;
    }

    for (int i = 0; i < m_num_intervals; i++) {
      if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5)
          || (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i])
              > 1.0e-5)) {
        return false;
      }
    }

    return true;
  }

  static SyncTrack CreateFromMarkers(
      float duration,
      int n_markers,
      float markers[cSyncTrackMaxIntervals]) {
    SyncTrack result;
    result.m_duration = duration;
    result.m_num_intervals = n_markers;
    for (int i = 0; i < n_markers; i++) {
      result.m_sync_markers[i] = markers[i];
    }

    result.CalcIntervals();

    return result;
  }

  static SyncTrack
  Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
    assert(track_A.m_num_intervals == track_B.m_num_intervals);

    SyncTrack result;
    result.m_num_intervals = track_A.m_num_intervals;

    result.m_duration =
        (1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;

    float interval_0_offset =
        track_B.m_interval_start[0] - track_A.m_interval_start[0];
    if (interval_0_offset > 0.5f) {
      interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
    } else if (interval_0_offset < -0.5) {
      interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
    }

    result.m_interval_start[0] = fmodf(
        1.0 + (1.0f - weight) * track_A.m_interval_start[0]
            + weight * (track_A.m_interval_start[0] + interval_0_offset),
        1.0f);
    result.m_sync_markers[0] = result.m_interval_start[0];

    for (int i = 0; i < result.m_num_intervals; i++) {
      float interval_duration_A = track_A.m_interval_ratio[i];
      float interval_duration_B = track_B.m_interval_ratio[i];
      result.m_interval_ratio[i] =
          (1.0f - weight) * interval_duration_A + weight * interval_duration_B;

      if (i < cSyncTrackMaxIntervals) {
        result.m_interval_start[i + 1] =
            result.m_interval_start[i] + result.m_interval_ratio[i];
        if (result.m_interval_start[i + 1] > 1.0f) {
          result.m_interval_start[i + 1] =
              fmodf(result.m_interval_start[i + 1], 1.0f);
        }

        result.m_sync_markers[i + 1] = result.m_interval_start[i + 1];
      }
    }

    assert (result.m_num_intervals < cSyncTrackMaxIntervals);

    return result;
  }

  void DrawDebugUi();
};

#endif  //ANIMTESTBED_SYNCTRACK_H
