///|
pub fn system_symbol_name(id : Int) -> String? {
  match id {
    1 => Some("$ion")
    2 => Some("$ion_1_0")
    3 => Some("$ion_symbol_table")
    4 => Some("name")
    5 => Some("version")
    6 => Some("imports")
    7 => Some("symbols")
    8 => Some("max_id")
    9 => Some("$ion_shared_symbol_table")
    _ => None
  }
}

///|
pub fn system_symbol_id(name : String) -> Int? {
  match name {
    "$ion" => Some(1)
    "$ion_1_0" => Some(2)
    "$ion_symbol_table" => Some(3)
    "name" => Some(4)
    "version" => Some(5)
    "imports" => Some(6)
    "symbols" => Some(7)
    "max_id" => Some(8)
    "$ion_shared_symbol_table" => Some(9)
    _ => None
  }
}

///|
pub fn system_max_id() -> Int {
  9
}

///|
pub(all) struct SymbolTable {
  locals : Array[String]
}

///|
pub fn symbol_table_new() -> SymbolTable {
  { locals: [], }
}

///|
pub fn SymbolTable::intern(self : SymbolTable, name : String) -> Int {
  match system_symbol_id(name) {
    Some(id) => id
    None => {
      let mut found = -1
      let mut i = 0
      while i < self.locals.length() {
        if self.locals[i] == name {
          found = i
        }
        i += 1
      }
      if found >= 0 {
        system_max_id() + 1 + found
      } else {
        self.locals.push(name)
        system_max_id() + self.locals.length()
      }
    }
  }
}

///|
pub fn SymbolTable::define_local(self : SymbolTable, name : String) -> Unit {
  self.locals.push(name)
}

///|
pub fn SymbolTable::name_of(
  self : SymbolTable,
  id : Int,
) -> Result[String, IonError] {
  if id == 0 {
    Ok("")
  } else {
    match system_symbol_name(id) {
      Some(name) => Ok(name)
      None => {
        let idx = id - system_max_id() - 1
        if idx >= 0 && idx < self.locals.length() {
          Ok(self.locals[idx])
        } else {
          Err(InvalidSymbol("id=" + id.to_string()))
        }
      }
    }
  }
}

///|
pub fn SymbolTable::reset_locals(self : SymbolTable) -> Unit {
  while self.locals.length() > 0 {
    let _ = self.locals.pop()
  }
}

///|
pub fn SymbolTable::local_names(self : SymbolTable) -> Array[String] {
  let out : Array[String] = []
  for name in self.locals {
    out.push(name)
  }
  out
}

///|
pub fn needs_quoting(name : String) -> Bool {
  if name.length() == 0 {
    true
  } else if !is_ident_start(name[0].to_int()) {
    true
  } else {
    let mut i = 1
    let mut ok = true
    while i < name.length() {
      if !is_ident_part(name[i].to_int()) {
        ok = false
      }
      i += 1
    }
    !ok
  }
}

///|
pub fn quote_symbol(name : String) -> String {
  if !needs_quoting(name) {
    name
  } else {
    let out = StringBuilder()
    out.write_char('\'')
    let mut i = 0
    while i < name.length() {
      let c = name[i].to_int()
      if c == 39 || c == 92 {
        out.write_char('\\')
      }
      out.write_char(c.unsafe_to_char())
      i += 1
    }
    out.write_char('\'')
    out.to_string()
  }
}