#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string_view>

#include "core/signal_generator.h"

namespace {

void Expect(bool condition, std::string_view message) {
  if (!condition) {
    std::cerr << "FAILED: " << message << "\n";
    std::exit(1);
  }
}

void ExpectNear(double actual, double expected, double tolerance, std::string_view message) {
  if (std::abs(actual - expected) > tolerance) {
    std::cerr << "FAILED: " << message << " actual=" << actual
              << " expected=" << expected << "\n";
    std::exit(1);
  }
}

void TestDbfsConversion() {
  ExpectNear(aptt::DbfsToLinear(-20.0), 0.1, 0.000001, "-20 dBFS converts to 0.1");
}

void TestFrameAndChannelCount() {
  aptt::PcmSineGenerator generator;
  aptt::PcmSineConfig config;
  config.sample_rate_hz = 48000;
  config.channel_count = 2;
  const auto samples = generator.GenerateInterleavedFloat32(config, 480);
  Expect(samples.size() == 960, "interleaved stereo sample count");
}

void TestAmplitudeBound() {
  aptt::PcmSineGenerator generator;
  aptt::PcmSineConfig config;
  config.amplitude_dbfs = -20.0;
  const auto samples = generator.GenerateInterleavedFloat32(config, 48000);
  float peak = 0.0F;
  for (float sample : samples) {
    peak = std::max(peak, std::abs(sample));
  }
  ExpectNear(peak, 0.1, 0.0001, "peak amplitude");
}

void TestRightOnlyAssignment() {
  aptt::PcmSineGenerator generator;
  aptt::PcmSineConfig config;
  config.channel_assignment = aptt::ChannelAssignment::kRightOnly;
  const auto samples = generator.GenerateInterleavedFloat32(config, 128);

  bool saw_right_signal = false;
  for (std::size_t i = 0; i < samples.size(); i += 2) {
    Expect(samples[i] == 0.0F, "left channel remains silent");
    if (std::abs(samples[i + 1]) > 0.0001F) {
      saw_right_signal = true;
    }
  }
  Expect(saw_right_signal, "right channel carries signal");
}

void TestRightChannelPhaseOffset() {
  aptt::PcmSineGenerator generator;
  aptt::PcmSineConfig config;
  config.frequency_hz = 1000.0;
  config.sample_rate_hz = 48000;
  config.amplitude_dbfs = 0.0;
  config.right_channel_phase_offset_degrees = 90.0;

  const auto samples = generator.GenerateInterleavedFloat32(config, 1);

  ExpectNear(samples[0], 0.0, 0.000001,
             "left channel starts at zero phase");
  ExpectNear(samples[1], 1.0, 0.000001,
             "right channel applies requested phase offset");
}

void TestPhaseContinuity() {
  aptt::PcmSineGenerator chunked;
  aptt::PcmSineGenerator whole;
  aptt::PcmSineConfig config;

  const auto first = chunked.GenerateInterleavedFloat32(config, 100);
  const auto second = chunked.GenerateInterleavedFloat32(config, 100);
  const auto full = whole.GenerateInterleavedFloat32(config, 200);

  Expect(first.size() + second.size() == full.size(), "chunk sizes match full size");
  for (std::size_t i = 0; i < first.size(); ++i) {
    ExpectNear(first[i], full[i], 0.000001, "first chunk phase continuity");
  }
  for (std::size_t i = 0; i < second.size(); ++i) {
    ExpectNear(second[i], full[first.size() + i], 0.000001,
               "second chunk phase continuity");
  }
}

}  // namespace

int main() {
  TestDbfsConversion();
  TestFrameAndChannelCount();
  TestAmplitudeBound();
  TestRightOnlyAssignment();
  TestRightChannelPhaseOffset();
  TestPhaseContinuity();

  std::cout << "signal generator tests passed\n";
  return 0;
}
