// moonaudio — pure MoonBit audio decoding and DSP toolkit.
// Copyright 2026 Sakuraqwq1
// Licensed under the Apache License, Version 2.0.

///|
/// Clamp a normalised sample into `[-1.0, 1.0]`, mapping NaN to `0.0`.
///
/// Every encoder in `moonaudio` funnels integer conversion through this
/// function, so a DSP stage that overshoots can never wrap around to the
/// opposite polarity.
pub fn clamp_sample(value : Double) -> Double {
  if value.is_nan() {
    0.0
  } else if value < -1.0 {
    -1.0
  } else if value > 1.0 {
    1.0
  } else {
    value
  }
}

///|
/// Convert a normalised sample to an integer code in `[min, max]`.
fn quantize_scaled(scaled : Double, min : Double, max : Double) -> Int32 {
  let value = if scaled.is_nan() {
    0.0
  } else if scaled <= min {
    min
  } else if scaled >= max {
    max
  } else {
    scaled
  }
  value.round().to_int()
}

///|
/// Convert a normalised sample to the integer code used by `format`.
///
/// Unsigned 8 bit uses offset binary (`0` is `-1.0`, `255` is `+1.0`); signed
/// formats use two's complement. Float formats return `0` and exist here only
/// so callers can treat every format uniformly.
pub fn quantize(value : Double, format : SampleFormat) -> Int32 {
  let sample = clamp_sample(value)
  match format {
    U8 => quantize_scaled(sample * 127.5 + 127.5, 0.0, 255.0)
    I16 => quantize_scaled(sample * 32768.0, -32768.0, 32767.0)
    I24 => quantize_scaled(sample * 8388608.0, -8388608.0, 8388607.0)
    I32 => quantize_scaled(sample * 2147483648.0, -2147483648.0, 2147483647.0)
    F32 | F64 => 0
  }
}

///|
/// Convert an integer code of `format` back to a normalised sample.
pub fn dequantize(code : Int32, format : SampleFormat) -> Double {
  match format {
    U8 => (code.to_double() - 127.5) / 127.5
    I16 => code.to_double() / 32768.0
    I24 => code.to_double() / 8388608.0
    I32 => code.to_double() / 2147483648.0
    F32 | F64 => 0.0
  }
}

///|
/// Sign extend `raw` from `bits` bits to 32 bits.
///
/// Used by containers that keep samples at their native width but report a
/// different *valid* bit count (WAVE_FORMAT_EXTENSIBLE, 20 bit in 24 bit slots).
pub fn sign_extend(raw : Int32, bits : Int32) -> Int32 {
  if bits <= 0 || bits >= 32 {
    return raw
  }
  let shift = 32 - bits
  raw << shift >> shift
}

///|
/// Decode an IEEE 754 single precision pattern into a `Double`.
///
/// Implemented by hand so the library does not depend on a host float
/// reinterpretation intrinsic; the operation is exact for every finite value.
pub fn f32_bits_to_double(bits : Int64) -> Double {
  let sign = if ((bits >> 31) & 1L) == 1L { -1.0 } else { 1.0 }
  let exponent = ((bits >> 23) & 0xFF).to_int()
  let mantissa = (bits & 0x7FFFFF).to_double()
  if exponent == 0 {
    if mantissa == 0.0 {
      0.0 * sign
    } else {
      sign * (mantissa / 8388608.0) * @math.pow(2.0, -126.0)
    }
  } else if exponent == 255 {
    if mantissa == 0.0 {
      sign * @double.infinity
    } else {
      @double.not_a_number
    }
  } else {
    sign *
    (1.0 + mantissa / 8388608.0) *
    @math.pow(2.0, (exponent - 127).to_double())
  }
}

///|
/// Round to the nearest integer with ties to even, clamped to the `Int32`
/// range.
fn round_half_even(value : Double) -> Int32 {
  let floor = value.floor()
  let fraction = value - floor
  let rounded = if fraction > 0.5 {
    floor + 1.0
  } else if fraction < 0.5 {
    floor
  } else if (floor / 2.0).floor() * 2.0 == floor {
    floor
  } else {
    floor + 1.0
  }
  if rounded > 2147483647.0 {
    2147483647
  } else if rounded < -2147483648.0 {
    -2147483648
  } else {
    rounded.to_int()
  }
}

///|
/// Encode a `Double` as an IEEE 754 single precision bit pattern.
///
/// Values outside the single precision range saturate to infinity and NaN is
/// canonicalised, which keeps encoder output deterministic.
pub fn double_to_f32_bits(value : Double) -> Int64 {
  if value.is_nan() {
    return 0x7FC00000L
  }
  let mut sign = 0L
  let mut magnitude = value
  if magnitude < 0.0 {
    sign = 1L
    magnitude = -magnitude
  }
  if magnitude == 0.0 {
    return sign << 31
  }
  let exponent = @math.log2(magnitude).floor().to_int()
  if exponent > 127 {
    return (sign << 31) | 0x7F800000L
  }
  if exponent < -126 {
    let scaled = magnitude / @math.pow(2.0, -149.0)
    let mantissa = round_half_even(scaled).to_int64()
    if mantissa <= 0L {
      return sign << 31
    }
    return (sign << 31) | mantissa
  }
  let biased = (exponent + 127).to_int64()
  let fraction = (magnitude / @math.pow(2.0, exponent.to_double()) - 1.0) *
    8388608.0
  let mantissa = round_half_even(fraction)
  if mantissa >= 8388608 {
    return (sign << 31) | ((biased + 1) << 23)
  }
  (sign << 31) | (biased << 23) | mantissa.to_int64()
}