#include "core/dop_generator.h"

#include <cstddef>
#include <cmath>
#include <sstream>
#include <utility>

namespace aptt {
namespace {

constexpr std::uint32_t kDsdBaseRateHz = 44100;
constexpr std::uint32_t kDopDsdBitsPerPcmFrame = 16;
constexpr std::uint8_t kDopMarker05 = 0x05;
constexpr std::uint8_t kDopMarkerFa = 0xFA;
constexpr std::uint8_t kDsdIdleLsbFirst = 0x69;
constexpr std::uint8_t kDsdIdleMsbFirst = 0x96;
constexpr double kPi = 3.14159265358979323846;

DopPackResult MakeDopFailure(const DopConfig& config, std::string error) {
  DopPackResult result;
  result.ok = false;
  result.error = std::move(error);
  result.carrier_format = MakeDopCarrierFormat(config);
  return result;
}

bool IsSupportedDsdByteOrder(SampleType sample_type) {
  return sample_type == SampleType::kDsd1BitLsbFirst ||
         sample_type == SampleType::kDsd1BitMsbFirst;
}

std::string ChannelCountMessage(std::uint32_t expected, std::size_t actual) {
  std::ostringstream out;
  out << "DoP payload channel count mismatch: expected " << expected
      << " got " << actual;
  return out.str();
}

std::uint32_t DsdBitRateHz(std::uint32_t dsd_rate_multiplier) {
  return kDsdBaseRateHz * dsd_rate_multiplier;
}

double DbfsToLinearAmplitude(double dbfs) {
  return std::pow(10.0, dbfs / 20.0);
}

}  // namespace

std::uint32_t DopCarrierSampleRateHz(std::uint32_t dsd_rate_multiplier) {
  if (!IsSupportedDopRate(dsd_rate_multiplier)) {
    return 0;
  }
  return (kDsdBaseRateHz * dsd_rate_multiplier) / kDopDsdBitsPerPcmFrame;
}

bool IsSupportedDopRate(std::uint32_t dsd_rate_multiplier) {
  return dsd_rate_multiplier == 64 ||
         dsd_rate_multiplier == 128 ||
         dsd_rate_multiplier == 256 ||
         dsd_rate_multiplier == 512 ||
         dsd_rate_multiplier == 1024 ||
         dsd_rate_multiplier == 2048;
}

std::uint8_t DopMarkerForFrame(std::uint64_t frame_index,
                               bool marker_starts_with_0x05) {
  const bool even = (frame_index % 2) == 0;
  if (marker_starts_with_0x05) {
    return even ? kDopMarker05 : kDopMarkerFa;
  }
  return even ? kDopMarkerFa : kDopMarker05;
}

std::uint8_t DopIdleByte(SampleType dsd_byte_order) {
  return dsd_byte_order == SampleType::kDsd1BitMsbFirst
      ? kDsdIdleMsbFirst
      : kDsdIdleLsbFirst;
}

FormatDescriptor MakeDopCarrierFormat(const DopConfig& config) {
  FormatDescriptor descriptor;
  descriptor.signal_mode = SignalMode::kDop;
  descriptor.sample_rate_hz = DopCarrierSampleRateHz(config.dsd_rate_multiplier);
  descriptor.sample_type = SampleType::kInt24In32;
  descriptor.valid_bits_per_sample = 24;
  descriptor.container_bits_per_sample = 32;
  descriptor.channels.channel_count = config.channel_count == 0
      ? 1
      : config.channel_count;
  descriptor.channels.channel_mask =
      descriptor.channels.channel_count == 2 ? 3 : 0;
  descriptor.dsd_rate_multiplier = config.dsd_rate_multiplier;
  descriptor.dop_marker_starts_with_0x05 = config.marker_starts_with_0x05;
  return descriptor;
}

DopPackResult PackDopPayload(
    const DopConfig& config,
    const std::vector<std::vector<std::uint8_t>>& channel_payloads,
    std::uint64_t starting_dop_frame_index) {
  if (!IsSupportedDopRate(config.dsd_rate_multiplier)) {
    return MakeDopFailure(config, "unsupported DoP DSD rate multiplier");
  }
  if (config.channel_count == 0) {
    return MakeDopFailure(config, "DoP channel count must be non-zero");
  }
  if (!IsSupportedDsdByteOrder(config.dsd_byte_order)) {
    return MakeDopFailure(config, "unsupported DoP DSD byte order");
  }
  if (channel_payloads.size() != config.channel_count) {
    return MakeDopFailure(
        config,
        ChannelCountMessage(config.channel_count, channel_payloads.size()));
  }
  if (channel_payloads.empty()) {
    return MakeDopFailure(config, "DoP payload requires at least one channel");
  }

  const std::size_t bytes_per_channel = channel_payloads.front().size();
  if ((bytes_per_channel % 2) != 0) {
    return MakeDopFailure(
        config,
        "DoP payload requires an even number of DSD bytes per channel");
  }
  for (const auto& channel : channel_payloads) {
    if (channel.size() != bytes_per_channel) {
      return MakeDopFailure(
          config,
          "DoP payload channels must have equal byte lengths");
    }
  }

  DopPackResult result;
  result.ok = true;
  result.carrier_format = MakeDopCarrierFormat(config);
  const std::uint32_t dop_frames =
      static_cast<std::uint32_t>(bytes_per_channel / 2);
  result.bytes.reserve(
      static_cast<std::size_t>(dop_frames) * config.channel_count * 4);

  for (std::uint32_t frame = 0; frame < dop_frames; ++frame) {
    const std::uint8_t marker =
        DopMarkerForFrame(starting_dop_frame_index + frame,
                          config.marker_starts_with_0x05);
    for (std::uint32_t channel = 0; channel < config.channel_count; ++channel) {
      const auto& payload = channel_payloads[channel];
      result.bytes.push_back(payload[static_cast<std::size_t>(frame) * 2]);
      result.bytes.push_back(payload[static_cast<std::size_t>(frame) * 2 + 1]);
      result.bytes.push_back(marker);
      result.bytes.push_back(0x00);
    }
  }
  return result;
}

DopPackResult GenerateDopSilence(const DopConfig& config,
                                 std::uint32_t dop_frame_count,
                                 std::uint64_t starting_dop_frame_index) {
  if (config.channel_count == 0) {
    return MakeDopFailure(config, "DoP channel count must be non-zero");
  }
  const std::uint8_t idle = DopIdleByte(config.dsd_byte_order);
  std::vector<std::vector<std::uint8_t>> payloads(
      config.channel_count,
      std::vector<std::uint8_t>(
          static_cast<std::size_t>(dop_frame_count) * 2,
          idle));
  return PackDopPayload(config, payloads, starting_dop_frame_index);
}

DopPackResult GenerateDopTestPattern(const DopConfig& config,
                                     std::uint32_t dop_frame_count) {
  if (config.channel_count == 0) {
    return MakeDopFailure(config, "DoP channel count must be non-zero");
  }

  std::vector<std::vector<std::uint8_t>> payloads(config.channel_count);
  for (std::uint32_t channel = 0; channel < config.channel_count; ++channel) {
    auto& payload = payloads[channel];
    payload.reserve(static_cast<std::size_t>(dop_frame_count) * 2);
    for (std::uint32_t frame = 0; frame < dop_frame_count; ++frame) {
      payload.push_back(static_cast<std::uint8_t>(
          (channel * 0x20u + frame * 2u) & 0xFFu));
      payload.push_back(static_cast<std::uint8_t>(
          (channel * 0x20u + frame * 2u + 1u) & 0xFFu));
    }
  }
  return PackDopPayload(config, payloads);
}

void DopSineGenerator::Reset() {
  dsd_bit_index_ = 0;
  dop_frame_index_ = 0;
  modulators_.clear();
}

void DopSineGenerator::EnsureChannelState(std::uint32_t channel_count) {
  if (modulators_.size() != channel_count) {
    modulators_.assign(channel_count, 0.0);
  }
}

DopPackResult DopSineGenerator::GenerateSine(
    const DopSineConfig& config,
    std::uint32_t dop_frame_count) {
  if (!IsSupportedDopRate(config.dop.dsd_rate_multiplier)) {
    return MakeDopFailure(config.dop, "unsupported DoP DSD rate multiplier");
  }
  if (config.dop.channel_count == 0) {
    return MakeDopFailure(config.dop, "DoP channel count must be non-zero");
  }
  if (!IsSupportedDsdByteOrder(config.dop.dsd_byte_order)) {
    return MakeDopFailure(config.dop, "unsupported DoP DSD byte order");
  }
  if (!std::isfinite(config.frequency_hz) || config.frequency_hz <= 0.0) {
    return MakeDopFailure(config.dop, "DoP sine frequency must be positive");
  }
  if (!std::isfinite(config.amplitude_dbfs) || config.amplitude_dbfs > 0.0) {
    return MakeDopFailure(config.dop, "DoP sine amplitude must be <= 0 dBFS");
  }

  const std::uint32_t dsd_rate_hz = DsdBitRateHz(config.dop.dsd_rate_multiplier);
  if (config.frequency_hz >= static_cast<double>(dsd_rate_hz) / 2.0) {
    return MakeDopFailure(config.dop, "DoP sine frequency exceeds DSD Nyquist");
  }

  EnsureChannelState(config.dop.channel_count);
  std::vector<std::vector<std::uint8_t>> payloads(config.dop.channel_count);
  for (auto& payload : payloads) {
    payload.reserve(static_cast<std::size_t>(dop_frame_count) * 2);
  }

  const double amplitude = DbfsToLinearAmplitude(config.amplitude_dbfs);
  const double phase_step =
      2.0 * kPi * config.frequency_hz / static_cast<double>(dsd_rate_hz);

  for (std::uint32_t frame = 0; frame < dop_frame_count; ++frame) {
    for (std::uint32_t byte_index = 0; byte_index < 2; ++byte_index) {
      std::vector<std::uint8_t> packed(config.dop.channel_count, 0);
      for (std::uint32_t bit_index = 0; bit_index < 8; ++bit_index) {
        const double target =
            amplitude * std::sin(phase_step * static_cast<double>(dsd_bit_index_));
        for (std::uint32_t channel = 0;
             channel < config.dop.channel_count;
             ++channel) {
          double& modulator = modulators_[channel];
          modulator += target;
          const bool one = modulator >= 0.0;
          modulator += one ? -1.0 : 1.0;
          const std::uint32_t output_bit =
              config.dop.dsd_byte_order == SampleType::kDsd1BitMsbFirst
                  ? 7 - bit_index
                  : bit_index;
          if (one) {
            packed[channel] |= static_cast<std::uint8_t>(1u << output_bit);
          }
        }
        ++dsd_bit_index_;
      }
      for (std::uint32_t channel = 0;
           channel < config.dop.channel_count;
           ++channel) {
        payloads[channel].push_back(packed[channel]);
      }
    }
  }

  auto result = PackDopPayload(config.dop, payloads, dop_frame_index_);
  if (result.ok) {
    dop_frame_index_ += dop_frame_count;
  }
  return result;
}

DopPackResult DopSineGenerator::GenerateSilence(
    const DopConfig& config,
    std::uint32_t dop_frame_count) {
  auto result = GenerateDopSilence(config, dop_frame_count, dop_frame_index_);
  if (result.ok) {
    dop_frame_index_ += dop_frame_count;
  }
  return result;
}

}  // namespace aptt
