// Symbol Encoding Layout (32-bit UInt)
// [31..28] Tag  [27..0] Payload
//
// 1. Short String (Tag is 0~4, and every char is fit in 7bit)
//    length = Tag 
//    Payload = [c0, c1, c2, c3]
//
// 3. Generated Symbol (Tag is 5)
//    Payload = global counter
//
// 2. Long String (Tag is 8~15)
//    index = self & 0x7fff_ffff
//    name = id_to_name[index]
//
// 4. Unuse (Tag = 6, 7)
//
// Decode: value < 0 ? Table : (tag < 5 ? Unpack : Gensym)

///|
/// A unique symbolic identifier providing O(1) equality, comparison, and hashing.
struct Symbol(UInt) derive(Eq, Hash, Compare, Default)

///|
pub impl Debug for Symbol with fn to_repr(self) {
  Repr::opaque_("Symbol", Repr::string(self.to_string()))
}

///|
fn Symbol::tag(self : Symbol) -> UInt {
  self.0 >> 28
}

///|
/// Creates a symbol from a string.
pub fn Symbol::of(name : String) -> Symbol {
  let len = name.length()
  if len > 4 {
    put_in_table(name)
  } else {
    for i in 0.. Symbol {
  name_to_sym.get_or_init(name, () => {
    let id = id_to_name.length()
    id_to_name.push(name)
    Symbol(0x8000_0000 | id.reinterpret_as_uint())
  })
}

///|
/// Global counter incremented to produce unique IDs for generated symbols.
let gen_counter : Ref[Int] = { val: -1 }

///|
/// Generates a unique symbolic identifier using a global counter.
pub fn Symbol::generate() -> Symbol {
  gen_counter.val += 1
  let id = gen_counter.val
  guard! id < 0x0fff_ffff
  Symbol(0x5000_0000 | gen_counter.val.reinterpret_as_uint())
}

///|
/// if this symbol was created via `generate()` rather than from a string.
pub fn Symbol::is_generated(self : Symbol) -> Bool {
  self.tag() == 5
}

///|
pub impl Show for Symbol with fn output(self, logger) {
  match self.tag() {
    0..<5 as len =>
      for i in len.reinterpret_as_int()>..0 {
        let c = (self.0 >> (7 * i)) & 0x7f
        logger.write_char(c.reinterpret_as_int().unsafe_to_char())
      }
    5 => logger.write_string("#{gensym \{self.0 & 0x0fff_ffff}}")
    6 | 7 => panic()
    8..<_ => {
      let i = (self.0 & 0x7fff_ffff).reinterpret_as_int()
      logger.write_string(id_to_name[i])
    }
  }
}

///|
test "inner" {
  let names : Array[String] = [
    "", "a", "ab", "abc", "abcd", "abcde", "abcdef", "δΈ­",
  ]
  let ss = names.map(Symbol::of) + [Symbol::generate(), Symbol::generate()]
  inspect(
    ss.map(x => x.0.to_string(radix=16)).join("\n"),
    content=(
      #|0
      #|10000061
      #|200030e2
      #|30187163
      #|4c38b1e4
      #|80000000
      #|80000001
      #|80000002
      #|50000000
      #|50000001
    ),
  )
}