///|
/// An immutable numeric value with explicit object identity for NaN membership.
pub struct LogitKey {
  priv number : ScalarNumber
  priv numeric_hash : Int64
}

///|
pub fn LogitKey::new(value : ScalarNumber) -> LogitKey {
  { number: value, numeric_hash: eng_prefix_hash(value), }
}

///|
pub fn LogitKey::value(self : LogitKey) -> ScalarNumber {
  self.number
}

///|
pub impl Eq for LogitKey with fn equal(self, other) {
  physical_equal(self, other)
}

///|
pub extend LogitKey with Eq::{equal, not_equal}

///|
pub impl @debug.Debug for LogitKey with fn to_repr(self) {
  @debug.Repr::Repr(("LogitKey", self.number))
}

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

///|
// LogitFormatter only clears and adds to this set. There are no deleted slots.
// Its lookup, clean rehash, growth trigger and physical reinsertion order follow
// CPython 3.12.14 setobject.c, not the engineering dictionary's probing.
priv struct LogitLabelled {
  mut table : Array[LogitKey?]
  mut used : Int32
}

///|
fn LogitLabelled::new() -> LogitLabelled {
  { table: Array::make(8, None), used: 0, }
}

///|
fn LogitLabelled::clear(self : LogitLabelled) -> Unit {
  self.table = Array::make(8, None)
  self.used = 0
}

///|
fn LogitLabelled::find(
  self : LogitLabelled,
  key : LogitKey,
) -> (Int32, Bool) raise @core.DataError {
  let mask = (self.table.length() - 1).to_uint64()
  let mut perturb = key.numeric_hash.reinterpret_as_uint64()
  let mut slot = perturb & mask
  while true {
    let probes = if slot + 9UL <= mask { 9 } else { 0 }
    for offset in 0..<(probes + 1) {
      let index = slot.to_int() + offset
      match self.table[index] {
        None => return (index, false)
        Some(stored) =>
          if stored.numeric_hash == key.numeric_hash &&
            (
              physical_equal(stored, key) ||
              eng_prefix_compare(stored.number, key.number) == Some(0)
            ) {
            return (index, true)
          }
      }
    }
    perturb = perturb >> 5
    slot = (slot * 5UL + 1UL + perturb) & mask
  } nobreak {
    (-1, false)
  }
}

///|
fn LogitLabelled::insert_clean(self : LogitLabelled, key : LogitKey) -> Unit {
  let mask = (self.table.length() - 1).to_uint64()
  let mut perturb = key.numeric_hash.reinterpret_as_uint64()
  let mut slot = perturb & mask
  while true {
    let probes = if slot + 9UL <= mask { 9 } else { 0 }
    for offset in 0..<(probes + 1) {
      let index = slot.to_int() + offset
      if self.table[index] is None {
        self.table[index] = Some(key)
        return
      }
    }
    perturb = perturb >> 5
    slot = (slot * 5UL + 1UL + perturb) & mask
  }
}

///|
fn LogitLabelled::resize(
  self : LogitLabelled,
  minimum : Int64,
) -> Unit raise @core.DataError {
  let mut size = 8
  while Int64::from_int(size) <= minimum {
    if size == 1073741824 {
      raise @core.DataError::InvalidTicks(
        message="ResourceError: logit label set table exceeds Array addressability",
      )
    }
    size *= 2
  }
  let old = self.table
  self.table = Array::make(size, None)
  for entry in old {
    if entry is Some(key) {
      self.insert_clean(key)
    }
  }
}

///|
fn LogitLabelled::add(
  self : LogitLabelled,
  key : LogitKey,
) -> Unit raise @core.DataError {
  let (slot, found) = self.find(key)
  if found {
    return
  }
  self.table[slot] = Some(key)
  self.used += 1
  if Int64::from_int(self.used) * 5L >=
    Int64::from_int(self.table.length() - 1) * 3L {
    self.resize(
      Int64::from_int(self.used) * (if self.used > 50000 { 2L } else { 4L }),
    )
  }
}

///|
fn LogitLabelled::contains(
  self : LogitLabelled,
  key : LogitKey,
) -> Bool raise @core.DataError {
  self.find(key).1
}

///|
fn LogitLabelled::keys(self : LogitLabelled) -> Array[LogitKey] {
  let result = Array(capacity=self.used)
  for entry in self.table {
    if entry is Some(key) {
      result.push(key)
    }
  }
  result
}

///|
fn logit_format_ticks_keys(
  base : BaseFormatter,
  keys : Array[LogitKey],
) -> Array[String] raise @core.DataError {
  let state = match base.kind {
    Logit(state) => state
    _ => abort("invalid LogitFormatter kind")
  }
  let input = LogitInput::from_keys(keys)
  logit_named_prepare(base, state, input)
  let labels : Array[String] = []
  let mut index = 0
  while index < input.length() {
    labels.push(logit_named_call(base, state, input.key_at(index), Some(index)))
    index += 1
  }
  labels
}