// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// An ordered set of digit characters defining a positional numeral system.
/// The character at index `i` is the digit of value `i`, so the first
/// character is always the zero digit. Build one with [`Alphabet::new`] and
/// hand it to [`encode`] / [`decode`]; the `base58` and `base62` packages are
/// thin wrappers that pin a specific alphabet.
pub struct Alphabet {
  digits : Array[Char]
  lookup : Array[Int]
  base : Int
}

///|
/// Build an [`Alphabet`] from its ordered digit characters, e.g.
/// `Alphabet::new("0123456789abcdef")` for lowercase hexadecimal.
///
/// Fails rather than aborts: an alphabet often comes from configuration, and a
/// library has no business killing the program over a value it was handed.
pub fn Alphabet::new(chars : String) -> Alphabet raise BadAlphabet {
  let digits = chars.to_array()
  let base = digits.length()
  if base < 2 {
    raise TooFew(count=base)
  }
  let lookup = Array::make(128, -1)
  for i in 0..= 128 {
      raise NotAscii(at=i, char=digits[i])
    }
    if lookup[code] != -1 {
      raise Repeated(at=i, char=digits[i])
    }
    lookup[code] = i
  }
  { digits, lookup, base, }
}

///|
/// An alphabet written as a literal in source, where a mistake is a
/// programming error rather than bad input.
///
/// This is the one place the library still aborts: `new` exists for alphabets
/// that arrive at runtime, and this one for the six that ship with the library
/// and are covered by its tests.
pub fn Alphabet::of(chars : String) -> Alphabet {
  try Alphabet::new(chars) catch {
    failure => abort(spell(failure))
  } noraise {
    alphabet => alphabet
  }
}

///|
/// What went wrong with an alphabet, in words, for the one place that aborts.
fn spell(failure : BadAlphabet) -> String {
  match failure {
    NotAscii(at~, ..) =>
      "moonbase: alphabet character " + at.to_string() + " is not ASCII"
    Repeated(at~, ..) =>
      "moonbase: alphabet character " +
      at.to_string() +
      " repeats an earlier one"
    TooFew(count~) =>
      "moonbase: an alphabet needs two digits, got " + count.to_string()
  }
}

///|
/// The radix of this alphabet — the number of distinct digits.
pub fn Alphabet::base(self : Alphabet) -> Int {
  self.base
}

///|
/// The character for digit value `v`, where `0 <= v < base`.
pub fn Alphabet::char_of(self : Alphabet, v : Int) -> Char {
  self.digits[v]
}

///|
/// The digit value of `c` in this alphabet, or `None` if `c` is not one of its
/// characters.
pub fn Alphabet::value_of(self : Alphabet, c : Char) -> Int? {
  let code = c.to_int()
  if code < 0 || code >= 128 {
    return None
  }
  let v = self.lookup[code]
  if v < 0 {
    None
  } else {
    Some(v)
  }
}

///|
/// Encode raw bytes to a string over `alphabet`.
///
/// Leading zero bytes map to leading zero-digit characters; the rest is read as
/// a big-endian integer and rewritten in the target base. Round-trips with
/// [`decode`].
pub fn encode(input : BytesView, alphabet : Alphabet) -> String {
  let n = input.length()
  let base = alphabet.base
  let mut zeros = 0
  while zeros < n && input[zeros].to_int() == 0 {
    zeros = zeros + 1
  }
  // Little-endian digits of the big integer; leading zeros are never stored, so
  // an all-zero payload leaves this empty.
  let out_digits : Array[Int] = []
  for i in 0.. 0 {
      out_digits.push(carry % base)
      carry = carry / base
    }
  }
  let out = StringBuilder()
  for _ in 0..= 0 {
    out.write_char(alphabet.digits[out_digits[k]])
    k = k - 1
  }
  out.to_string()
}

///|
/// The same encoding, as the ASCII bytes it would be written with.
pub fn encode_bytes(input : BytesView, alphabet : Alphabet) -> Bytes {
  ascii(encode(input, alphabet))
}

///|
/// Decode a string over `alphabet` back to the original bytes.
///
/// Fails at the first character outside the alphabet, naming where it was.
/// Round-trips with [`encode`].
pub fn decode(input : StringView, alphabet : Alphabet) -> Bytes raise Malformed {
  read(input, alphabet)
}

///|
/// Decode from ASCII bytes, for callers who never had a string.
pub fn decode_bytes(
  input : BytesView,
  alphabet : Alphabet,
) -> Bytes raise Malformed {
  read(text_of(input), alphabet)
}

///|
/// Decode, skipping anything outside the alphabet.
///
/// Never fails, which is what a payload wrapped in newlines or punctuation
/// needs. What it cannot do is tell you the input was wrong.
pub fn decode_lossy(input : StringView, alphabet : Alphabet) -> Bytes {
  let kept = StringBuilder()
  for i in 0..= 0 && code < 128 && alphabet.lookup[code] >= 0 {
      kept.write_char(code.unsafe_to_char())
    }
  }
  // Everything left is in the alphabet, so the reader has nothing to raise.
  try read(kept.to_string(), alphabet) catch {
    _ => b""
  } noraise {
    bytes => bytes
  }
}

///|
/// Write a whole number in this base, without the leading-zero rule that the
/// byte-oriented faces keep.
pub fn encode_int(value : UInt64, alphabet : Alphabet) -> String {
  let base = alphabet.base.to_uint64()
  if value == 0 {
    return alphabet.digits[0].to_string()
  }
  let digits : Array[Char] = []
  let mut left = value
  while left > 0 {
    digits.push(alphabet.digits[(left % base).to_int()])
    left = left / base
  }
  let out = StringBuilder()
  let mut i = digits.length() - 1
  while i >= 0 {
    out.write_char(digits[i])
    i = i - 1
  }
  out.to_string()
}

///|
/// Read a whole number in this base.
pub fn decode_int(
  input : StringView,
  alphabet : Alphabet,
) -> UInt64 raise Malformed {
  if input.length() == 0 {
    raise Truncated(at=0)
  }
  let base = alphabet.base.to_uint64()
  let mut value = 0UL
  for i in 0..= 0 && code < 128 { alphabet.lookup[code] } else { -1 }
    if digit < 0 {
      raise Bad(at=i, char=code.unsafe_to_char())
    }
    value = value * base + digit.to_uint64()
  }
  value
}

///|
fn read(input : StringView, alphabet : Alphabet) -> Bytes raise Malformed {
  let base = alphabet.base
  let zero = alphabet.digits[0]
  let mut zeros = 0
  let mut counting = true
  let out_bytes : Array[Int] = []
  for i in 0..= 0 && code < 128 { alphabet.lookup[code] } else { -1 }
    if digit < 0 {
      raise Bad(at=i, char=code.unsafe_to_char())
    }
    if counting && code == zero.to_int() {
      zeros = zeros + 1
      continue
    }
    counting = false
    let mut carry = digit
    for j in 0.. 0 {
      out_bytes.push(carry % 256)
      carry = carry / 256
    }
  }
  let bytes : Array[Byte] = []
  for _ in 0..= 0 {
    bytes.push(out_bytes[k].to_byte())
    k = k - 1
  }
  Bytes::from_array(bytes)
}

///|
/// A string of ASCII, as the bytes it is written with.
pub fn ascii(text : String) -> Bytes {
  let out = Array::make(text.length(), b'')
  for i in 0.. String {
  let out = StringBuilder()
  for i in 0..