// Ensure unique identifiers that don't conflict with predefined
// identifiers or reserved prefixes.

///|
/// A registry of unique names.
pub struct UniqueNames {
  reserved_identifiers : Map[String, Unit]
  reserved_prefixes : Array[String]
  safe_prefix : String
  translations : Map[String, String]
  reverse_translations : Map[String, String]
}

///|
/// Create a registry.
pub fn UniqueNames::new(
  reserved_identifiers~ : Array[String],
  reserved_prefixes~ : Array[String],
  safe_prefix~ : String,
) -> UniqueNames {
  let tbl : Map[String, Unit] = Map([])
  for id in reserved_identifiers {
    tbl[id] = ()
  }
  for prefix in reserved_prefixes {
    if safe_prefix.has_prefix(prefix) {
      abort(
        "Unique_name.init: safe_prefix \{ocaml_quote(safe_prefix)} is not safe as it conflicts with reserved prefix \{ocaml_quote(prefix)}",
      )
    }
  }
  {
    reserved_identifiers: tbl,
    reserved_prefixes,
    safe_prefix,
    translations: Map([]),
    reverse_translations: Map([]),
  }
}

///|
fn UniqueNames::is_reserved(self : UniqueNames, s : String) -> Bool {
  self.reserved_identifiers.contains(s)
}

///|
fn UniqueNames::has_reserved_prefix(self : UniqueNames, s : String) -> Bool {
  self.reserved_prefixes.iter().any(p => s.has_prefix(p))
}

///|
fn suffix_of_int(n : Int) -> String {
  match n {
    0 => ""
    1 => "_"
    n => n.to_string()
  }
}

///|
fn UniqueNames::register(
  self : UniqueNames,
  src : String,
  preferred_translation? : String,
) -> String {
  let pref_dst = preferred_translation.unwrap_or(src)
  let mut counter = 0
  let dst = for ;; {
    let suffix = suffix_of_int(counter)
    counter += 1
    let dst = pref_dst + suffix
    let dst = if self.has_reserved_prefix(dst) {
      self.safe_prefix + dst
    } else {
      dst
    }
    if !(self.is_reserved(dst) || self.reverse_translations.contains(dst)) {
      break dst
    }
  }
  self.translations[src] = dst
  self.reverse_translations[dst] = src
  dst
}

///|
/// Look up the translation of a registered identifier.
pub fn UniqueNames::translate_only(self : UniqueNames, src : String) -> String? {
  self.translations.get(src)
}

///|
/// Translate an identifier, registering it if needed.
pub fn UniqueNames::translate(
  self : UniqueNames,
  src : String,
  preferred_translation? : String,
) -> String {
  match self.translate_only(src) {
    Some(dst) => dst
    None => self.register(src, preferred_translation?)
  }
}

///|
/// Look up the source identifier of a translation.
pub fn UniqueNames::reverse_translate(
  self : UniqueNames,
  dst : String,
) -> String? {
  self.reverse_translations.get(dst)
}

///|
/// Create a new unique source identifier based on `src` and register it.
pub fn UniqueNames::create(self : UniqueNames, src : String) -> String {
  let mut counter = 0
  let src = for ;; {
    let s = src + suffix_of_int(counter)
    counter += 1
    if !self.translations.contains(s) {
      break s
    }
  }
  ignore(self.register(src))
  src
}

///|
/// All the translations, sorted by source identifier.
pub fn UniqueNames::all(self : UniqueNames) -> Array[(String, String)] {
  let l = self.translations.iter().collect()
  l.sort_by((a, b) => compare_strings(a.0, b.0))
  l
}