///|
fn Encoder::enumeration_count(self : Encoder) -> Int32 raise CodeError {
  let k = self.dimension()
  if k > 16 || self.code.n > 256 {
    raise ResourceLimit("exact decoder: dimension <=16 and length <=256")
  }
  let count = 1 << k
  if count.to_int64() * self.code.n.to_int64() * (k + 1).to_int64() > 20000000L {
    raise ResourceLimit("exact decoder work")
  }
  count
}

///|
pub struct NearestReport {
  word : Array[Int32]?
  distance : Int32
  ties : Int32
  examined : Int32
} derive(Debug, ToJson)

///|
/// Exhaustive nearest codeword for small codes, NOT a scalable LDPC decoder.
/// A tied minimum returns no word rather than silently choosing a correction.
pub fn Encoder::nearest_word(
  self : Encoder,
  received : Array[Int32],
) -> NearestReport raise CodeError {
  ensure_bits(received, self.code.n)
  let count = self.enumeration_count()
  let mut best = self.code.n + 1
  let mut ties = 0
  let mut candidate : Array[Int32]? = None
  for mask in 0..> i) & 1 })
    let word = self.encode(info)
    let distance = bit_distance(word, received)
    if distance < best {
      best = distance
      ties = 1
      candidate = Some(word)
    } else if distance == best {
      ties += 1
      candidate = None
    }
  }
  { word: candidate, distance: best, ties, examined: count, }
}

///|
pub extend NearestReport with @debug.Debug::{to_repr}

///|
pub extend NearestReport with ToJson::{to_json}

///|
fn log_add(left : Double, right : Double) -> Double {
  if left.is_inf() && left < 0.0 {
    return right
  }
  if right.is_inf() && right < 0.0 {
    return left
  }
  left.max(right) + @math.ln_1p(@math.exp(-(left - right).abs()))
}

///|
pub struct ExactDecodeReport {
  word : Array[Int32]
  posterior_llr : Array[Double]
  ties : Int32
  examined : Int32
} derive(Debug, ToJson)

///|
/// Exact sequence ML word and bitwise MAP marginals under a uniform codeword prior.
/// Input LLRs and output marginals saturate at limit; ties select the first information
/// mask deterministically. Bitwise posterior decisions need not form a codeword.
pub fn Encoder::decode_exact(
  self : Encoder,
  llr : Array[Double],
  limit~ : Double = 50.0,
) -> ExactDecodeReport raise CodeError {
  if llr.length() != self.code.n {
    raise LengthMismatch(self.code.n, llr.length())
  }
  validate_limit(limit)
  for value in llr {
    if !finite(value) {
      raise InvalidNumber("nonfinite exact-decoder LLR")
    }
  }
  let values = llr.map(fn(x) { clamp(x, limit) })
  let count = self.enumeration_count()
  let negative_infinity = -1.0 / 0.0
  let zero = Array::make(self.code.n, negative_infinity)
  let one = Array::make(self.code.n, negative_infinity)
  let mut best = negative_infinity
  let mut best_word = Array::make(self.code.n, 0)
  let mut ties = 0
  for mask in 0..> i) & 1 }),
    )
    let mut weight = 0.0
    for i, bit in word {
      if bit == 1 {
        weight -= values[i]
      }
    }
    if weight > best {
      best = weight
      best_word = word
      ties = 1
    } else if weight == best {
      ties += 1
    }
    for i, bit in word {
      if bit == 0 {
        zero[i] = log_add(zero[i], weight)
      } else {
        one[i] = log_add(one[i], weight)
      }
    }
  }
  let posterior_llr = Array::makei(self.code.n, fn(i) {
    clamp(zero[i] - one[i], limit)
  })
  { word: best_word, posterior_llr, ties, examined: count, }
}

///|
pub extend ExactDecodeReport with @debug.Debug::{to_repr}

///|
pub extend ExactDecodeReport with ToJson::{to_json}