#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>

#include "core/dop_generator.h"

namespace {

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

std::string Hex(const std::vector<std::uint8_t>& bytes) {
  std::ostringstream out;
  out << std::uppercase << std::hex << std::setfill('0');
  for (const auto byte : bytes) {
    out << std::setw(2) << static_cast<int>(byte);
  }
  return out.str();
}

void TestCarrierRatesAndFormat() {
  aptt::DopConfig config;
  config.dsd_rate_multiplier = 64;
  auto format = aptt::MakeDopCarrierFormat(config);
  Expect(format.signal_mode == aptt::SignalMode::kDop,
         "carrier signal mode is DoP");
  Expect(format.sample_rate_hz == 176400, "DSD64 carrier is 176.4 kHz");
  Expect(format.sample_type == aptt::SampleType::kInt24In32,
         "carrier uses 24-bit payload in 32-bit container");
  Expect(format.valid_bits_per_sample == 24, "carrier valid bits are 24");
  Expect(format.container_bits_per_sample == 32,
         "carrier container bits are 32");

  config.dsd_rate_multiplier = 128;
  Expect(aptt::MakeDopCarrierFormat(config).sample_rate_hz == 352800,
         "DSD128 carrier is 352.8 kHz");
  config.dsd_rate_multiplier = 256;
  Expect(aptt::MakeDopCarrierFormat(config).sample_rate_hz == 705600,
         "DSD256 carrier is 705.6 kHz");
  config.dsd_rate_multiplier = 512;
  Expect(aptt::MakeDopCarrierFormat(config).sample_rate_hz == 1411200,
         "DSD512 carrier is 1.4112 MHz");
  config.dsd_rate_multiplier = 1024;
  Expect(aptt::MakeDopCarrierFormat(config).sample_rate_hz == 2822400,
         "DSD1024 carrier is 2.8224 MHz");
  config.dsd_rate_multiplier = 2048;
  Expect(aptt::MakeDopCarrierFormat(config).sample_rate_hz == 5644800,
         "DSD2048 carrier is 5.6448 MHz");
}

void TestMarkersAlternate() {
  Expect(aptt::DopMarkerForFrame(0, true) == 0x05,
         "default first marker is 0x05");
  Expect(aptt::DopMarkerForFrame(1, true) == 0xFA,
         "second marker is 0xFA");
  Expect(aptt::DopMarkerForFrame(0, false) == 0xFA,
         "alternate first marker can be 0xFA");
  Expect(aptt::DopMarkerForFrame(1, false) == 0x05,
         "alternate second marker can be 0x05");
}

void TestSilenceFixtureMatchesGoldenHex() {
#ifndef APTT_SOURCE_DIR
#error "APTT_SOURCE_DIR must be defined"
#endif
  aptt::DopConfig config;
  config.dsd_rate_multiplier = 64;
  config.channel_count = 2;
  const auto result = aptt::GenerateDopSilence(config, 4);
  Expect(result.ok, "DoP silence generation succeeds");
  Expect(result.bytes.size() == 32, "4 stereo DoP frames are 32 bytes");

  const auto fixture_path = std::string(APTT_SOURCE_DIR) +
      "/tests/fixtures/dop_silence_dsd64_stereo_4frames.hex";
  std::ifstream fixture(fixture_path, std::ios::binary);
  Expect(fixture.good(), "DoP silence golden fixture exists");
  std::string expected;
  fixture >> expected;
  Expect(Hex(result.bytes) == expected, "DoP silence matches golden fixture");
}

void TestChannelPackingOrder() {
  aptt::DopConfig config;
  config.channel_count = 2;
  std::vector<std::vector<std::uint8_t>> payloads = {
      {0x10, 0x11, 0x12, 0x13},
      {0x20, 0x21, 0x22, 0x23},
  };
  const auto result = aptt::PackDopPayload(config, payloads);

  Expect(result.ok, "payload packing succeeds");
  const std::vector<std::uint8_t> expected = {
      0x10, 0x11, 0x05, 0x00,
      0x20, 0x21, 0x05, 0x00,
      0x12, 0x13, 0xFA, 0x00,
      0x22, 0x23, 0xFA, 0x00,
  };
  Expect(result.bytes == expected,
         "DoP packing is frame-major then channel-major");
}

void TestByteOrderAffectsIdlePattern() {
  Expect(aptt::DopIdleByte(aptt::SampleType::kDsd1BitLsbFirst) == 0x69,
         "LSB-first DSD idle byte is 0x69");
  Expect(aptt::DopIdleByte(aptt::SampleType::kDsd1BitMsbFirst) == 0x96,
         "MSB-first DSD idle byte is bit-reversed idle");

  aptt::DopConfig config;
  config.dsd_byte_order = aptt::SampleType::kDsd1BitMsbFirst;
  const auto result = aptt::GenerateDopSilence(config, 1);
  Expect(result.ok, "MSB-first silence generation succeeds");
  Expect(result.bytes.front() == 0x96,
         "MSB-first silence uses MSB-first idle byte");
}

void TestSilenceCanStartAtFrameOffset() {
  aptt::DopConfig config;
  config.channel_count = 2;
  const auto result = aptt::GenerateDopSilence(config, 2, 1);

  Expect(result.ok, "offset silence generation succeeds");
  Expect(result.bytes[2] == 0xFA && result.bytes[6] == 0xFA,
         "offset first frame starts with 0xFA marker");
  Expect(result.bytes[10] == 0x05 && result.bytes[14] == 0x05,
         "offset second frame returns to 0x05 marker");
}

void TestDopSineGeneratorIsDeterministicCarrierPayload() {
  aptt::DopSineConfig config;
  config.dop.channel_count = 2;
  config.frequency_hz = 1000.0;
  config.amplitude_dbfs = -20.0;

  aptt::DopSineGenerator first;
  aptt::DopSineGenerator second;
  const auto first_result = first.GenerateSine(config, 4);
  const auto second_result = second.GenerateSine(config, 4);
  const auto silence = aptt::GenerateDopSilence(config.dop, 4);

  Expect(first_result.ok, "DoP sine generation succeeds");
  Expect(first_result.bytes.size() == 32,
         "4 stereo DoP sine frames are 32 bytes");
  Expect(first_result.bytes == second_result.bytes,
         "DoP sine generation is deterministic");
  Expect(first_result.bytes != silence.bytes,
         "DoP sine payload is not the idle silence fixture");
}

void TestDopStreamGeneratorKeepsMarkerContinuityIntoSilence() {
  aptt::DopSineConfig config;
  config.dop.channel_count = 2;

  aptt::DopSineGenerator generator;
  const auto signal = generator.GenerateSine(config, 1);
  const auto silence = generator.GenerateSilence(config.dop, 1);

  Expect(signal.ok && silence.ok, "signal followed by silence succeeds");
  Expect(signal.bytes[2] == 0x05 && signal.bytes[6] == 0x05,
         "first generated stream frame uses 0x05 marker");
  Expect(silence.bytes[2] == 0xFA && silence.bytes[6] == 0xFA,
         "keep-warm silence continues marker alternation");
}

void TestUnsupportedCombinationsFailClearly() {
  aptt::DopConfig unsupported_rate;
  unsupported_rate.dsd_rate_multiplier = 4096;
  Expect(!aptt::GenerateDopSilence(unsupported_rate, 1).ok,
         "unsupported DSD rate fails");

  aptt::DopConfig stereo;
  stereo.channel_count = 2;
  Expect(!aptt::PackDopPayload(stereo, {{0x00, 0x01}}).ok,
         "channel mismatch fails");

  Expect(!aptt::PackDopPayload(stereo, {{0x00}, {0x01}}).ok,
         "odd byte count fails");

  aptt::DopSineConfig invalid_signal;
  invalid_signal.frequency_hz = 0.0;
  aptt::DopSineGenerator generator;
  Expect(!generator.GenerateSine(invalid_signal, 1).ok,
         "invalid DoP sine frequency fails");
}

}  // namespace

int main() {
  TestCarrierRatesAndFormat();
  TestMarkersAlternate();
  TestSilenceFixtureMatchesGoldenHex();
  TestChannelPackingOrder();
  TestByteOrderAffectsIdlePattern();
  TestSilenceCanStartAtFrameOffset();
  TestDopSineGeneratorIsDeterministicCarrierPayload();
  TestDopStreamGeneratorKeepsMarkerContinuityIntoSilence();
  TestUnsupportedCombinationsFailClearly();

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