///|
/// An exact XML Schema numeric token. It is never converted through a host float.
pub struct ExactNumber { token : String } derive(Eq, Debug)

///|
pub extend ExactNumber with Eq::{equal, not_equal}

///|
pub extend ExactNumber with @debug.Debug::{to_repr}

///|
fn digits(s : String, start : Int) -> Int {
  let mut i = start
  while unit(s,i) >= 48 && unit(s,i) <= 57 { i += 1 }
  i
}

///|
pub fn ExactNumber::parse(token : String) -> ExactNumber? {
  if token == "INF" || token == "-INF" || token == "NaN" { return Some({ token, }) }
  let mut i = if unit(token,0) == 43 || unit(token,0) == 45 { 1 } else { 0 }
  let begin = i
  i = digits(token,i)
  let whole = i-begin
  let mut fraction = 0
  if unit(token,i) == 46 { i += 1; let begin=i; i=digits(token,i); fraction=i-begin }
  if whole + fraction == 0 { return None }
  if unit(token,i) == 69 || unit(token,i) == 101 {
    i += 1
    if unit(token,i) == 43 || unit(token,i) == 45 { i += 1 }
    let begin=i; i=digits(token,i)
    if i == begin { return None }
  }
  if i == token.length() { Some({ token, }) } else { None }
}

///|
pub fn ExactNumber::is_finite(self : ExactNumber) -> Bool { self.token != "INF" && self.token != "-INF" && self.token != "NaN" }

///|
pub struct ExactInteger { token : String } derive(Eq, Debug)

///|
pub extend ExactInteger with Eq::{equal, not_equal}

///|
pub extend ExactInteger with @debug.Debug::{to_repr}

///|
pub fn ExactInteger::parse(token : String) -> ExactInteger? {
  let begin = if unit(token,0) == 43 || unit(token,0) == 45 { 1 } else { 0 }
  if begin < token.length() && digits(token,begin) == token.length() { Some({ token, }) } else { None }
}

///|
pub(all) enum ReferenceKind { Id; QName } derive(Eq, Debug)

///|
pub extend ReferenceKind with Eq::{equal, not_equal}

///|
pub extend ReferenceKind with @debug.Debug::{to_repr}

///|
/// Namespace identity and original spelling are separate. A missing name marks
/// an unresolved namespace binding, not a missing external target document.
pub struct Reference { kind : ReferenceKind; spelling : String; name : ExpandedName? } derive(Debug)

///|
pub extend Reference with @debug.Debug::{to_repr}

///|
pub fn Reference::new(name : ExpandedName, kind? : ReferenceKind = QName) -> Reference { { kind, spelling: name.local_name, name: Some(name) } }

///|
fn xml_trim(value : String) -> String {
  let mut start = 0; let mut end = value.length()
  while start < end && space(unit(value,start)) { start += 1 }
  while end > start && space(unit(value,end-1)) { end -= 1 }
  value[start:end].to_owned()
}

///|
pub fn Reference::parse(spelling : String, bindings? : ArrayView[(String, String)] = [], kind? : ReferenceKind = QName) -> Reference {
  reference_lookup(spelling,kind,prefix => {
    let mut found : String? = None
    for (p,value) in bindings { if p == prefix { found = Some(value) } }
    found
  })
}

///|
fn reference_lookup(spelling : String, kind : ReferenceKind, lookup : (String) -> String?) -> Reference {
  let token = xml_trim(spelling)
  let mut name : ExpandedName? = None
  if kind == Id {
    if ncname(token) { name = Some(ExpandedName::new("",token)) }
  } else if qname(token) is Some((prefix,local_name)) {
    let uri = match lookup(prefix) {
      Some(uri) => Some(uri)
      None => if prefix == "" { Some("") } else if prefix == "xml" { Some("http://www.w3.org/XML/1998/namespace") } else { None }
    }
    name = uri.map(namespace_uri => ExpandedName::{ namespace_uri, local_name })
  }
  { kind, spelling, name }
}

///|
pub impl Eq for Reference with equal(a,b) {
  if a.kind != b.kind { return false }
  match (a.name,b.name) { (Some(x),Some(y)) => x == y; (None,None) => a.spelling == b.spelling; _ => false }
}

///|
pub extend Reference with Eq::{equal, not_equal}

///|
/// Invalid scalar spellings remain explicit values for inspection. Writers
/// return a structured failure instead of serializing them as valid fields.
pub(all) enum Scalar {
  Text(String)
  Identifier(String)
  Uri(String)
  Boolean(Bool)
  Integer(ExactInteger)
  Number(ExactNumber)
  Reference(Reference)
  Enumeration(BpmnEnum)
  Invalid(raw~ : String, expected~ : ExpandedName)
} derive(Eq, Debug)

///|
pub extend Scalar with Eq::{equal, not_equal}

///|
pub extend Scalar with @debug.Debug::{to_repr}

///|
pub fn Scalar::token(self : Scalar) -> String {
  match self {
    Text(s) | Identifier(s) | Uri(s) => s
    Boolean(b) => if b { "true" } else { "false" }
    Integer(n) => n.token; Number(n) => n.token; Reference(r) => r.spelling
    Enumeration(e) => e.token(); Invalid(raw~, ..) => raw
  }
}

///|
/// Absence is separate from a present false, empty string, zero or reference.
pub(all) enum Field[T] { Absent; Present(T); InvalidValue(Scalar) } derive(Eq, Debug)

///|
pub extend Field with Eq::{equal, not_equal}

///|
pub extend Field with @debug.Debug::{to_repr}

///|
pub fn[T] Field::value(self : Field[T]) -> T? { match self { Present(v) => Some(v); _ => None } }

///|
pub struct Attribute {
  name : ExpandedName?
  raw_name : String
  value : Scalar
  span : Span?
  priv origin : SourceOrigin?
} derive(Eq, Debug)

///|
pub extend Attribute with Eq::{equal, not_equal}

///|
pub extend Attribute with @debug.Debug::{to_repr}

///|
pub fn Attribute::new(name : ExpandedName, value : Scalar) -> Attribute {
  let prefix = namespace_prefix(name.namespace_uri)
  { name: Some(name), raw_name: if prefix == "" { name.local_name } else { prefix+":"+name.local_name }, value, span: None, origin: None }
}

///|
fn collapse_space(token : String) -> String {
  let out=StringBuilder(); let mut pending=false; let mut any=false
  for c in token {
    if space(c.to_int()) { pending=any } else {
      if pending { out.write_string(" ") }; out.write_char(c); any=true; pending=false
    }
  }
  out.to_string()
}

///|
fn int32_token(token : String) -> Bool {
  let negative=unit(token,0) == 45
  let mut start=if negative || unit(token,0) == 43 { 1 } else { 0 }
  while start < token.length()-1 && unit(token,start) == 48 { start += 1 }
  let size=token.length()-start
  let bound = if negative { "2147483648" } else { "2147483647" }
  size < 10 || (size == 10 && token[start:].to_owned() <= bound)
}

///|
fn primitive_scalar(expected : ExpandedName, token : String) -> Scalar {
  let invalid = () => Scalar::Invalid(raw=token, expected~)
  match expected.local_name {
    "boolean" => match xml_trim(token) { "true" | "1" => Boolean(true); "false" | "0" => Boolean(false); _ => invalid() }
    "QName" => Reference(Reference::parse(token))
    "IDREF" => Reference(Reference::parse(token,kind=Id))
    "ID" => if ncname(xml_trim(token)) { Identifier(xml_trim(token)) } else { invalid() }
    "integer" | "int" | "long" | "short" | "byte" | "unsignedInt" | "nonNegativeInteger" | "positiveInteger" => {
      let value = xml_trim(token)
      match ExactInteger::parse(value) { Some(n) => if expected.local_name == "int" && !int32_token(value) { invalid() } else { Integer(n) }; None => invalid() }
    }
    "double" | "float" | "decimal" => match ExactNumber::parse(xml_trim(token)) {
      Some(n) => if expected.local_name == "decimal" && (!n.is_finite() || n.token.contains("e") || n.token.contains("E")) { invalid() } else { Number(n) }
      None => invalid()
    }
    "anyURI" => Uri(collapse_space(token))
    "token" => Text(collapse_space(token))
    _ => Text(token)
  }
}

///|
fn parse_scalar(expected : ExpandedName, token : String, bindings : ArrayView[(String,String)]) -> Scalar {
  parse_scalar_lookup(expected,token,prefix => {
    let mut found : String? = None
    for (p,value) in bindings { if p == prefix { found = Some(value) } }
    found
  })
}

///|
fn parse_scalar_lookup(expected : ExpandedName, token : String, lookup : (String) -> String?) -> Scalar {
  let mut current = expected
  for _ in 0..<(schema_types.length()+1) {
    if current.namespace_uri == "http://www.w3.org/2001/XMLSchema" {
      let value = primitive_scalar(current,token)
      return match value { Reference(r) => Reference(reference_lookup(token,r.kind,lookup)); _ => value }
    }
    guard schema_type(current) is Some(t) else { return Text(token) }
    let enum_token = if !t.union_members.is_empty() || t.base == Some(ExpandedName::new("http://www.w3.org/2001/XMLSchema","token")) { collapse_space(token) } else { token }
    if parse_schema_enum(t.id,enum_token) is Some(value) { return Enumeration(value) }
    if !t.enum_values.is_empty() || !t.union_members.is_empty() { return Invalid(raw=token, expected~) }
    guard t.base is Some(base) else { return Text(token) }
    current = base
  }
  Invalid(raw=token, expected~)
}