//
// Created by martin on 16.11.21.
//

#include "SyncTrack.h"
#include "catch.hpp"

TEST_CASE("Basic", "[SyncTrack]") {
  SyncTrack track_A;
  track_A.m_num_intervals = 2;
  track_A.m_duration = 2.0;
  track_A.m_interval_start[0] = 0.f;
  track_A.m_interval_ratio[0] = 0.7;
  track_A.m_interval_start[1] = 0.7f;
  track_A.m_interval_ratio[1] = 0.3;

  SyncTrack track_B;
  track_B.m_num_intervals = 2;
  track_B.m_duration = 1.5;
  track_B.m_interval_start[0] = 0.0f;
  track_B.m_interval_ratio[0] = 0.6;
  track_B.m_interval_start[1] = 0.6f;
  track_B.m_interval_ratio[1] = 0.4;

  WHEN("Calculating sync time of track_B at 0.5 duration") {
    float sync_time_at_0_75 =
        track_B.CalcSyncFromAbsTime(0.5 * track_B.m_duration);
    REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333));
  }

  WHEN("Calculating sync time of track_B at 0.6 duration") {
    float sync_time_at_0_6 =
        track_B.CalcSyncFromAbsTime(0.6 * track_B.m_duration);
    REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0));
  }

  WHEN("Calculating sync time of track_B at 0.7 duration") {
    float sync_time_at_0_7 =
        track_B.CalcSyncFromAbsTime(0.7 * track_B.m_duration);
    REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25));
  }

  WHEN("Calculating sync time of track_B at 0.0 duration") {
    float sync_time_at_1_0 =
        track_B.CalcSyncFromAbsTime(0.0 * track_B.m_duration);
    REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0));
  }

  WHEN("Calculating sync time of track_B at 1.0 duration") {
    float sync_time_at_1_0 =
        track_B.CalcSyncFromAbsTime(0.9999 * track_B.m_duration);
    REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f));
  }

  WHEN("Calculating ratio from sync time on track_A at 0.83333") {
    float ratio = track_A.CalcRatioFromSyncTime(0.83333333);
    REQUIRE(ratio == Catch::Detail::Approx(0.5833333));
  }

  WHEN("Calculating ratio from sync time on track_A at 0.83333") {
    float ratio = track_A.CalcRatioFromSyncTime(1.25);
    REQUIRE(ratio == Catch::Detail::Approx(0.775));
  }

  WHEN("Blending two synctracks with weight 0.") {
    SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);

    THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
  }

  WHEN("Blending two synctracks with weight 1.") {
    SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);

    THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
  }
}

TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") {
  SyncTrack track_A;
  track_A.m_num_intervals = 2;
  track_A.m_duration = 2.0;
  track_A.m_sync_markers[0] = 0.9;
  track_A.m_sync_markers[1] = 0.2;

  WHEN("Calculating intervals") {
    track_A.CalcIntervals();

    CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f));
    CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f));

    CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f));
    CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f));

    WHEN("Querying ratio at sync time at 1.001") {
      float ratio = track_A.CalcRatioFromSyncTime(1.0001f);
      CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
    }

    WHEN("Querying ratio at sync time at 1.001") {
      float ratio = track_A.CalcRatioFromSyncTime(0.0001f);
      CHECK(ratio == Catch::Detail::Approx(0.9).epsilon(0.001));
    }

    WHEN("Querying ratio at sync time at 1.9999") {
      float ratio = track_A.CalcRatioFromSyncTime(0.9999f);
      CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
    }
  }

  WHEN("Blending sync track with 3 events") {
    track_A.m_num_intervals = 3;
    track_A.m_duration = 2.0;
    track_A.m_sync_markers[0] = 0.;
    track_A.m_sync_markers[1] = 0.3;
    track_A.m_sync_markers[2] = 0.9;
    track_A.CalcIntervals();

    SyncTrack track_B;
    track_B.m_num_intervals = 3;
    track_B.m_duration = 1.5;
    track_B.m_sync_markers[0] = 0.7;
    track_B.m_sync_markers[1] = 0.9;
    track_B.m_sync_markers[2] = 0.2;
    track_B.CalcIntervals();

    WHEN("Calculating A's durations") {
      CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3));
      CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.6));
      CHECK(track_A.m_interval_ratio[2] == Catch::Detail::Approx(0.1));
    }

    WHEN("Calculating B's durations") {
      CHECK(track_B.m_interval_ratio[0] == Catch::Detail::Approx(0.2));
      CHECK(track_B.m_interval_ratio[1] == Catch::Detail::Approx(0.3));
      CHECK(track_B.m_interval_ratio[2] == Catch::Detail::Approx(0.5));
    }

    WHEN("Blending two synctracks with weight 0.") {
      SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);

      THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
    }

    WHEN("Blending two synctracks with weight 1.") {
      SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);

      THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
    }

    WHEN("Blending with weight 0.2") {
      float weight = 0.2f;
      SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B);

      REQUIRE(
          blended.m_duration
          == (1.0f - weight) * track_A.m_duration
                 + weight * track_B.m_duration);
      REQUIRE(
          blended.m_interval_start[0]
          == fmodf(
              (1.0f - weight) * (track_A.m_interval_start[0] + 1.0f)
                  + weight * (track_B.m_interval_start[0]),
              1.0f));
      REQUIRE(
          blended.m_interval_ratio[1]
          == (1.0f - weight) * (track_A.m_interval_ratio[1])
                  + weight * (track_B.m_interval_ratio[1])
              );
      REQUIRE(
          blended.m_interval_ratio[2]
          == (1.0f - weight) * (track_A.m_interval_ratio[2])
                 + weight * (track_B.m_interval_ratio[2])
      );
    }

    WHEN("Inverted blending with weight 0.2") {
      float weight = 0.2f;
      SyncTrack blended = SyncTrack::Blend(weight, track_B, track_A);

      REQUIRE(
          blended.m_duration
          == (1.0f - weight) * track_B.m_duration
                 + weight * track_A.m_duration);
      REQUIRE(
          blended.m_interval_start[0]
          == fmodf(
              (1.0f - weight) * (track_B.m_interval_start[0])
                  + weight * (track_A.m_interval_start[0] + 1.0f),
              1.0f));
      REQUIRE(
          blended.m_interval_ratio[1]
          == (1.0f - weight) * (track_B.m_interval_ratio[1])
                 + weight * (track_A.m_interval_ratio[1])
      );
      REQUIRE(
          blended.m_interval_ratio[2]
          == (1.0f - weight) * (track_B.m_interval_ratio[2])
                 + weight * (track_A.m_interval_ratio[2])
      );
    }
  }
}