#include "core/pcm_sample_conversion.h"

#include <algorithm>
#include <cmath>
#include <cstring>

namespace aptt {
namespace {

std::int32_t ClampPcm32(double value) {
  const double clamped = std::max(-1.0, std::min(1.0, value));
  return static_cast<std::int32_t>(std::lrint(clamped * 2147483647.0));
}

void AppendInt16(std::vector<std::uint8_t>& output, std::int32_t value) {
  const auto sample = static_cast<std::int16_t>(value >> 16);
  output.push_back(static_cast<std::uint8_t>(sample & 0xFF));
  output.push_back(static_cast<std::uint8_t>((sample >> 8) & 0xFF));
}

void AppendInt24(std::vector<std::uint8_t>& output, std::int32_t value) {
  const std::int32_t sample = value >> 8;
  output.push_back(static_cast<std::uint8_t>(sample & 0xFF));
  output.push_back(static_cast<std::uint8_t>((sample >> 8) & 0xFF));
  output.push_back(static_cast<std::uint8_t>((sample >> 16) & 0xFF));
}

void AppendUint32(std::vector<std::uint8_t>& output, std::uint32_t value) {
  output.push_back(static_cast<std::uint8_t>(value & 0xFF));
  output.push_back(static_cast<std::uint8_t>((value >> 8) & 0xFF));
  output.push_back(static_cast<std::uint8_t>((value >> 16) & 0xFF));
  output.push_back(static_cast<std::uint8_t>((value >> 24) & 0xFF));
}

void AppendInt32(std::vector<std::uint8_t>& output, std::int32_t value) {
  AppendUint32(output, static_cast<std::uint32_t>(value));
}

void AppendInt24In32(std::vector<std::uint8_t>& output, std::int32_t value) {
  AppendUint32(output, static_cast<std::uint32_t>(value) & 0xFFFFFF00u);
}

void AppendFloat32(std::vector<std::uint8_t>& output, float value) {
  const std::size_t offset = output.size();
  output.resize(offset + sizeof(float));
  std::memcpy(output.data() + offset, &value, sizeof(float));
}

}  // namespace

bool IsRenderablePcmFormat(const FormatDescriptor& format) {
  if (format.signal_mode != SignalMode::kPcm) {
    return false;
  }
  switch (format.sample_type) {
    case SampleType::kInt16:
      return format.valid_bits_per_sample == 16 &&
             format.container_bits_per_sample == 16;
    case SampleType::kInt24Packed:
      return format.valid_bits_per_sample == 24 &&
             format.container_bits_per_sample == 24;
    case SampleType::kInt24In32:
      return format.valid_bits_per_sample == 24 &&
             format.container_bits_per_sample == 32;
    case SampleType::kInt32:
      return format.valid_bits_per_sample == 32 &&
             format.container_bits_per_sample == 32;
    case SampleType::kFloat32:
      return format.valid_bits_per_sample == 32 &&
             format.container_bits_per_sample == 32;
    case SampleType::kFloat64:
    case SampleType::kDsd1BitLsbFirst:
    case SampleType::kDsd1BitMsbFirst:
    case SampleType::kDsd8Bit:
      return false;
  }
  return false;
}

std::vector<std::uint8_t> ConvertFloat32InterleavedToPcmBytes(
    const std::vector<float>& interleaved_samples,
    std::uint32_t channel_count,
    const FormatDescriptor& format) {
  if (channel_count == 0 ||
      (interleaved_samples.size() % channel_count) != 0 ||
      format.channels.channel_count != channel_count ||
      !IsRenderablePcmFormat(format)) {
    return {};
  }

  const std::uint32_t bytes_per_sample = format.container_bits_per_sample / 8;
  std::vector<std::uint8_t> output;
  output.reserve(interleaved_samples.size() * bytes_per_sample);
  for (const auto sample : interleaved_samples) {
    const std::int32_t pcm32 = ClampPcm32(sample);
    switch (format.sample_type) {
      case SampleType::kInt16:
        AppendInt16(output, pcm32);
        break;
      case SampleType::kInt24Packed:
        AppendInt24(output, pcm32);
        break;
      case SampleType::kInt24In32:
        AppendInt24In32(output, pcm32);
        break;
      case SampleType::kInt32:
        AppendInt32(output, pcm32);
        break;
      case SampleType::kFloat32:
        AppendFloat32(output, sample);
        break;
      case SampleType::kFloat64:
      case SampleType::kDsd1BitLsbFirst:
      case SampleType::kDsd1BitMsbFirst:
      case SampleType::kDsd8Bit:
        return {};
    }
  }
  return output;
}

}  // namespace aptt
