///|
using @combinator {type Parser}

///|
using @combinator {type Seq}

///|
pub(all) struct XMLDocument {
  version : String
  encoding : String
  standalone : Bool
  dtd : DocTypeDecl?
  root : XMLElement
}

///|
pub(all) struct XMLElement {
  name : String
  empty_element : Bool // 是否是单标签
  attributes : Map[String, String]
  children : Array[XMLChildren]
}

///|
pub(all) enum XMLChildren {
  Element(XMLElement)
  Reference(String)
  CDATA(String)
  PI(String)
  Comment(String)
  Text(String)
  WhiteSpace(String)
}

///|
pub(all) struct DocTypeDecl {
  name : String
  externalID : ExternalID?
  intSubset : Array[DTDStatement]?
}

///|
pub impl Show for DocTypeDecl with output(self, logger) {
  let external_id_str = match self.externalID {
    None => ""
    Some(id) => " " + id.to_string()
  }
  let subset_str = match self.intSubset {
    None => ""
    Some(arr) =>
      " [" + arr.map(fn(statement) { statement.to_string() }).join("") + "]"
  }
  logger.write_string("")
}

///|
pub impl Debug for DocTypeDecl with to_repr(self) {
  Repr::literal(self.to_string())
}

///|
pub(all) enum DTDStatement {
  Decl(MarkUpDecl)
  Sep(String)
}

///|
pub impl Show for DTDStatement with output(self, logger) {
  match self {
    Decl(decl) => logger.write_string(decl.to_string())
    Sep(s) => logger.write_string(s)
  }
}

///|
pub(all) enum MarkUpDecl {
  ElementDecl(ElementDecl)
  AttListDecl(AttlistDecl)
  EntityDecl(EntityDecl)
  NotationDecl(NotationDecl)
  PI(String)
  Comment(String)
}

///|
pub impl Show for MarkUpDecl with output(self, logger) {
  match self {
    ElementDecl(e) => logger.write_string(e.to_string())
    AttListDecl(a) => logger.write_string(a.to_string())
    EntityDecl(e) => logger.write_string(e.to_string())
    NotationDecl(n) => logger.write_string(n.to_string())
    PI(s) => logger.write_string("")
    Comment(s) => logger.write_string("")
  }
}

///|
pub(all) struct ElementDecl {
  name : String
  content_spec : ContentSpec
}

///|
pub impl Show for ElementDecl with output(self, logger) {
  logger.write_string("")
}

///|
pub(all) enum ContentSpec {
  EMPTY
  ANY
  Mixed(Array[String])
  Children(ChildrenContentSpec)
}

///|
pub impl Show for ContentSpec with output(self, logger) {
  match self {
    EMPTY => logger.write_string("EMPTY")
    ANY => logger.write_string("ANY")
    Mixed(arr) => {
      let str = arr.join("|")
      if arr.length() > 0 {
        logger.write_string("(#PCDATA|\{str})*")
      } else {
        logger.write_string("(#PCDATA)")
      }
    }
    Children(c) => logger.write_string(c.to_string())
  }
}

///|
pub(all) struct ContentParticle(SingleContentParticle, ChildrenContentSpecOp?)

///|
type ChildrenContentSpec = ContentParticle

///|
pub impl Show for ContentParticle with output(self, logger) {
  match (self.0, self.1) {
    (p, None) => logger.write_string(p.to_string())
    (p, Some(op)) => logger.write_string("\{p}\{op}")
  }
}

///|
pub(all) enum ChildrenContentSpecOp {
  Optional
  ZeroOrMore
  OneOrMore
}

///|
pub impl Show for ChildrenContentSpecOp with output(self, logger) {
  match self {
    Optional => logger.write_string("?")
    ZeroOrMore => logger.write_string("*")
    OneOrMore => logger.write_string("+")
  }
}

///|
pub(all) enum SingleContentParticle {
  Name(String)
  Seq(Array[ContentParticle])
  Choice(Array[ContentParticle])
}

///|
pub impl Show for SingleContentParticle with output(self, logger) {
  match self {
    Name(s) => logger.write_string(s)
    Seq(arr) => {
      let str = arr.map(fn(p) { p.to_string() }).join(", ")
      logger.write_string("(\{str})")
    }
    Choice(arr) => {
      let str = arr.map(fn(p) { p.to_string() }).join(" | ")
      logger.write_string("(\{str})")
    }
  }
}

///|
pub(all) struct AttlistDecl {
  name : String
  att_defs : Array[AttDef]
}

///|
pub impl Show for AttlistDecl with output(self, logger) {
  let att_str = self.att_defs.map(fn(att) { att.to_string() }).join("")
  logger.write_string("")
}

///|
pub(all) struct AttDef {
  name : String
  att_type : AttType
  default_decl : DefaultDecl
}

///|
pub impl Show for AttDef with output(self, logger) {
  logger.write_string(" \{self.name} \{self.att_type} \{self.default_decl}")
}

///|
pub(all) enum DefaultDecl {
  Required
  Implied
  Fixed(String)
  Value(String)
}

///|
pub impl Show for DefaultDecl with output(self, logger) {
  match self {
    Required => logger.write_string("#REQUIRED")
    Implied => logger.write_string("#IMPLIED")
    Fixed(s) => logger.write_string("#FIXED \{s}")
    Value(s) => logger.write_string(s)
  }
}

///|
pub(all) enum AttType {
  StringType(String)
  TokenizedType(String)
  NotationType(Array[String])
  Enumeration(Array[String])
}

///|
pub impl Show for AttType with output(self, logger) {
  match self {
    StringType(_) => logger.write_string("CDATA")
    TokenizedType(s) => logger.write_string(s)
    NotationType(s) => {
      let str = s.join(" | ")
      logger.write_string("NOTATION (\{str})")
    }
    Enumeration(s) => {
      let str = s.join(" | ")
      logger.write_string("(\{str})")
    }
  }
}

///|
pub(all) enum EntityDecl {
  GEDecl(String, EntityDef) // General Entity Declaration
  PEDecl(String, EntityDef) // Parameter Entity Declaration
}

///|
pub(all) enum EntityDef {
  EntityValue(String)
  GExternalID(ExternalID, String?)
  PExternalID(ExternalID)
}

///|
pub(all) enum ExternalID {
  System(String)
  Public(String, String)
}

///|
pub(all) struct NotationDecl {
  name : String
  id : NotationDeclID
}

///|
pub(all) enum NotationDeclID {
  PublicID(String)
  ExternalID(ExternalID)
}

///|
pub(all) enum Misc {
  Comment(String)
  PI(String)
  WhiteSpace(String)
}

///|
pub(all) enum XMLErrorKind {
  SyntaxError
  ValidationError
  EncodingError
  MalformedReference
  MismatchedTags(String, String) // 开始标签和结束标签
  InternalParserError
}

///|
pub impl Show for XMLErrorKind with output(self, logger) {
  match self {
    SyntaxError => logger.write_string("SyntaxError")
    ValidationError => logger.write_string("ValidationError")
    EncodingError => logger.write_string("EncodingError")
    MalformedReference => logger.write_string("MalformedReference")
    MismatchedTags(start, end) =>
      logger.write_string("MismatchedTags(\{start}, \{end})")
    InternalParserError => logger.write_string("InternalParserError")
  }
}

///|
pub(all) struct XMLParseError {
  kind : XMLErrorKind
  message : String
  location : Location
}

///|
pub impl Show for XMLParseError with output(self, logger) {
  logger.write_string(
    "{ kind: \{self.kind}, message: \{self.message}, location: \{self.location} }",
  )
}

///|
pub(all) struct Location {
  mut index : Int
  mut line : Int
  mut column : Int
  length : Int
}

///|
pub impl Show for Location with output(self, logger) {
  logger.write_string(
    "{ index: \{self.index}, line: \{self.line}, column: \{self.column}, length: \{self.length} }",
  )
}

///|
pub impl Show for XMLDocument with output(self, logger) {
  logger.write_string("XMLDocument: " + self.to_string())
}

///|
pub impl Show for XMLDocument with to_string(self) {
  let dtd_str = match self.dtd {
    Some(dtd) => dtd.to_string()
    None => ""
  }
  "\{dtd_str}\n\{self.root}"
}

///|
pub impl Debug for XMLDocument with to_repr(self) {
  Repr::literal("XMLDocument: " + self.to_string())
}

///|
pub impl Show for XMLElement with output(self, logger) {
  logger.write_string("XMLElement: " + self.to_string())
}

///|
pub impl Show for XMLElement with to_string(self) {
  let attributes = self.attributes
    .iter()
    .map(kv => "\{kv.0}=\"\{kv.1}\"")
    .join(" ")
  if self.empty_element {
    return "<\{self.name}" +
      (if attributes.length() == 0 { "" } else { " " }) +
      "\{attributes}/>"
  }
  let children = self.children.map(c => c.to_string()).join("")
  "<\{self.name} \{attributes}>\{children}"
}

///|
pub impl Debug for XMLElement with to_repr(self) {
  Repr::literal("XMLElement: " + self.to_string())
}

///|
pub impl Show for XMLChildren with output(self, logger) {
  logger.write_string("XMLChildren: " + self.to_string())
}

///|
pub impl Show for XMLChildren with to_string(self) {
  match self {
    Element(e) => e.to_string()
    Reference(s) => "\{s}"
    CDATA(s) => ""
    PI(s) => ""
    Comment(s) => ""
    Text(s) => s
    WhiteSpace(s) => s
  }
}

///|
pub impl Debug for XMLChildren with to_repr(self) {
  Repr::literal("XMLChildren: " + self.to_string())
}

///|
pub impl Show for EntityDecl with output(self, logger) {
  match self {
    GEDecl(name, def) => logger.write_string("")
    PEDecl(name, def) => logger.write_string("")
  }
}

///|
pub impl Show for EntityDef with output(self, logger) {
  match self {
    EntityValue(value) => logger.write_string(value)
    PExternalID(id) => logger.write_string(id.to_string())
    GExternalID(id, None) => logger.write_string(id.to_string())
    GExternalID(id, Some(n)) =>
      logger.write_string(id.to_string() + " NDATA \{n}")
  }
}

///|
pub impl Show for ExternalID with output(self, logger) {
  match self {
    System(s) => logger.write_string("SYSTEM \{s}")
    Public(p, s) => logger.write_string("PUBLIC \{p} \{s}")
  }
}

///|
pub impl Show for NotationDeclID with output(self, logger) {
  match self {
    PublicID(s) => logger.write_string("PUBLIC \{s}")
    ExternalID(id) => logger.write_string(id.to_string())
  }
}

///|
pub impl Show for NotationDecl with output(self, logger) {
  logger.write_string("")
}

///|
pub fn XMLParseError::is_fatal(self : XMLParseError) -> Bool {
  match self.kind {
    SyntaxError => true
    ValidationError => true
    EncodingError => true
    MalformedReference => true
    MismatchedTags(_, _) => true
    InternalParserError => true
  }
}