///|
/// Edn data type, used in Calcit
pub(all) enum Edn {
  Nil
  Bool(Bool)
  Number(Double)
  Symbol(String)
  /// a tag, like `:keyword` in Clojure
  Tag(EdnTag)
  Str(String)
  /// quoted Cirru code data
  Quote(Cirru)
  Tuple(EdnTupleView)
  List(EdnListView)
  Set(EdnSetView)
  Map(EdnMapView)
  /// a record, like `#my/record` in Clojure
  Record(EdnRecordView)
  Buffer(EdnBufferView)
  /// reference to Rust data, not interpretable in Calcit
  // AnyRef(EdnAnyRef)
  /// a reference to a Calcit atom, like `@my/atom` in Clojure
  Atom(Edn)
} derive(Eq, Hash, Default)

///|
/// for debugging. use `format!` if you want to generate in Cirru format
pub impl Show for Edn with output(self, logger) {
  let s = match self {
    Nil => "nil".to_string()
    Tag(t) => t.to_string()
    Bool(b) => if b { "true" } else { "false" }
    Number(n) => n.to_string()
    Symbol(s) => s
    Str(s) =>
      if is_simple_token(s) {
        s
      } else {
        let mut ret = "|"
        for c in s {
          if is_simple_char(c) {
            ret = ret + c.to_string()
          } else {
            ret = ret + c.to_string().escape()
          }
        }
        ret
      }
    Quote(e) => e.to_string()
    Buffer(b) => b.to_string()
    Tuple(t) => t.to_string()
    List(l) => l.to_string()
    Set(s) => s.to_string()
    Map(m) => m.to_string()
    Record(r) => r.to_string()
    Atom(a) => "atom " + a.to_string()
  }
  logger.write_string(s)
}

// TODO Hash

///|
pub impl Compare for Edn with compare(self : Edn, right : Edn) -> Int {
  let ret = match (self, right) {
    (Nil, Nil) => 0
    (Nil, _) => -1
    (_, Nil) => 1
    (Bool(a), Bool(b)) => a.compare(b)
    (Bool(_), _) => -1
    (_, Bool(_)) => 1
    (Number(a), Number(b)) => a.compare(b)
    (Number(_), _) => -1
    (_, Number(_)) => 1
    (Symbol(a), Symbol(b)) => a.compare(b)
    (Symbol(_), _) => -1
    (_, Symbol(_)) => 1
    (Tag(a), Tag(b)) => a.compare(b)
    (Tag(_), _) => -1
    (_, Tag(_)) => 1
    (Str(a), Str(b)) => a.compare(b)
    (Str(_), _) => -1
    (_, Str(_)) => 1
    (Quote(a), Quote(b)) => a.compare(b)
    (Quote(_), _) => -1
    (_, Quote(_)) => 1
    (Tuple(a), Tuple(b)) => a.compare(b)
    (Tuple(_), _) => -1
    (_, Tuple(_)) => 1
    (List(a), List(b)) => a.compare(b)
    (List(_), _) => -1
    (_, List(_)) => 1
    (Buffer(a), Buffer(b)) => a.compare(b)
    (Buffer(_), _) => -1
    (_, Buffer(_)) => 1
    (Set(a), Set(b)) => a.compare(b)
    (Set(_), _) => -1
    (_, Set(_)) => 1
    (Map(a), Map(b)) => a.compare(b)
    (Map(_), _) => -1
    (_, Map(_)) => 1
    (Atom(a), Atom(b)) => a.compare(b)
    (Atom(_), _) => -1
    (_, Atom(_)) => 1
    (Record(a), Record(b)) => a.compare(b)
    // (Record(_), _) => -1
    // (_, Record(_)) => 1
  }
  ret
}

///|
#deprecated("use Edn::Str() instead")
pub fn Edn::str(s : String) -> Edn {
  Edn::Str(s)
}

///|
#deprecated("use Edn::Tag() instead")
pub fn Edn::tag(s : String) -> Edn {
  Edn::Tag(EdnTag::new(s))
}

///|
#deprecated("use Edn::Symbol() instead")
pub fn Edn::symbol(s : String) -> Edn {
  Edn::Symbol(s)
}

///|
pub fn Edn::tuple(tag : Edn, extra : Array[Edn]) -> Edn {
  Edn::Tuple(EdnTupleView::new(tag, extra))
}

// TODO any-ref

///|
pub fn Edn::is_literal(self : Edn) -> Bool {
  match self {
    Nil => true
    Bool(_) => true
    Number(_) => true
    Symbol(_) => true
    Str(_) => true
    Tag(_) => true
    _ => false
  }
}

///|
fn is_simple_char(c : Char) -> Bool {
  match c {
    '-' | '?' | '.' | '$' | ',' => true
    _ =>
      if c.to_int() >= 0x30 && c.to_int() <= 0x39 { // 0-9
        true
      } else if c.to_int() >= 0x41 && c.to_int() <= 0x5A { // A-Z
        true
      } else if c.to_int() >= 0x61 && c.to_int() <= 0x7A { // a-z
        true
      } else {
        false
      }
  }
}

///|
fn is_simple_token(tok : String) -> Bool {
  for s in tok {
    if not(is_simple_char(s)) {
      return false
    }
  }
  true
}