//This file is mainly to implement parsing DTD (Document Type Decl).

///|
fn pnmtokens() -> Parser[Char, String] {
  @combinator.pchar_such_that(is_name_char)
  .and_then(
    @combinator.pchar_such_that(is_name_char)
    .repeat()
    .map(arr => arr.fold(init="", (str, c) => str + c.to_string())),
  )
  .map(fn(tuple) {
    let (c, str) = tuple
    c.to_string() + str
  })
}

///|
fn ppubidLiteral() -> Parser[Char, String] {
  fn is_pubid_char(c : Char) -> Bool {
    match c {
      '\u0020'
      | '\u000D'
      | 'a'..='z'
      | 'A'..='Z'
      | '0'..='9'
      | '-'
      | '\''
      | '('
      | ')'
      | '+'
      | ','
      | '.'
      | '/'
      | ':'
      | '='
      | '?'
      | ';'
      | '!'
      | '*'
      | '#'
      | '@'
      | '$'
      | '_'
      | '%' => true
      _ => false
    }
  }

  let single_quote = @combinator.pchar('\'')
    .and_then(
      @combinator.pchar_such_that(c => is_pubid_char(c) && c != '\'')
      .repeat()
      .map(arr => arr.fold(init="", (str, c) => str + c.to_string())),
    )
    .and_then(@combinator.pchar('\''))
    .omit_second()
    .map(fn(tuple) {
      let (quote, str) = tuple
      quote.to_string() + str + quote.to_string()
    })
  let double_quote = @combinator.pchar('"')
    .and_then(
      @combinator.pchar_such_that(c => is_pubid_char(c))
      .repeat()
      .map(arr => arr.fold(init="", (str, c) => str + c.to_string())),
    )
    .and_then(@combinator.pchar('"'))
    .omit_second()
    .map(fn(tuple) {
      let (quote, str) = tuple
      quote.to_string() + str + quote.to_string()
    })
  single_quote.or_else(double_quote)
}

///|
fn psystemLiteral() -> Parser[Char, String] {
  fn make_parser(quote : Char) {
    @combinator.pchar(quote)
    .and_then(@combinator.pchar_such_that(fn(c) { c != quote }).repeat())
    .and_then(@combinator.pchar(quote))
    .omit_second()
    .map(fn(tuple) {
      let (quote, arr) = tuple
      arr.fold(init=quote.to_string(), (str, c) => str + c.to_string()) +
      quote.to_string()
    })
  }

  make_parser('\'').or_else(make_parser('"'))
}

///|
fn pPEReference() -> Parser[Char, String] {
  @combinator.pchar('%')
  .and_then(pname())
  .and_then(@combinator.pchar(';'))
  .map(fn(tuple) {
    let ((_and, name), _semicolon) = tuple
    "&" + name + ";"
  })
}

///|
fn pentityValue() -> Parser[Char, String] {
  fn make_parser(quote : Char) {
    @combinator.pchar(quote)
    .and_then(
      @combinator.pchar_such_that(fn(c) { c != quote && c != '%' && c != '&' })
      .map(fn(c) { c.to_string() })
      .or_else(pPEReference())
      .or_else(preference())
      .repeat(),
    )
    .and_then(@combinator.pchar(quote))
    .map(fn(tuple) {
      let ((_, arr), _) = tuple
      arr.fold(init=quote.to_string(), (str, c) => str + c) + quote.to_string()
    })
  }

  let parser = make_parser('\'').or_else(make_parser('"'))
  parser
}

///|
fn pexternalID() -> Parser[Char, ExternalID] {
  let system = @combinator.pstring("SYSTEM")
    .and_then(pwhite_space())
    .and_then(psystemLiteral())
    .omit_first()
    .map(fn(str) { System(str) })
  let public = @combinator.pstring("PUBLIC")
    .and_then(pwhite_space())
    .and_then(ppubidLiteral())
    .omit_first()
    .and_then(pwhite_space())
    .omit_second()
    .and_then(psystemLiteral())
    .map(fn(tuple) {
      let (pubid, system) = tuple
      Public(pubid, system)
    })
  public.or_else(system)
}

///|
fn ppublicID() -> Parser[Char, String] {
  @combinator.pstring("PUBLIC")
  .and_then(pwhite_space())
  .and_then(ppubidLiteral())
  .omit_first()
}

///|
fn pentityDecl() -> Parser[Char, EntityDecl] {
  let pNDataDecl = pwhite_space()
    .and_then(@combinator.pstring("NDATA"))
    .and_then(pwhite_space())
    .and_then(pname())
    .omit_first()
  let pGEDecl = @combinator.pstring(""))
    .omit_second()
    .map(fn(tuple) {
      let (name, value) = tuple
      GEDecl(name, value)
    })
  let pPEDecl = @combinator.pstring(""))
    .omit_second()
    .map(fn(tuple) {
      let (name, value) = tuple
      PEDecl(name, value)
    })
  pGEDecl.or_else(pPEDecl)
}

///|
test "EnitytDecl" {
  //   
  // 
  // 
  // 
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pentityDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pentityDecl()
  let _result = parser.run(seq).unwrap().0
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pentityDecl()
  let _result = parser.run(seq).unwrap().0
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pentityDecl()
  let _result = parser.run(seq).unwrap().0
}

///|
fn pelementDecl() -> Parser[Char, ElementDecl] {
  let cp_ref : Ref[Parser[Char, ContentParticle]] = Ref::new(
    @combinator.pfail(),
  )
  let choice = @combinator.pchar('(')
    .and_then(pwhite_space().optional())
    .and_then(Parser::from_ref(cp_ref))
    .omit_first()
    .and_then(
      pwhite_space()
      .optional()
      .and_then(@combinator.pchar('|'))
      .and_then(pwhite_space().optional())
      .and_then(Parser::from_ref(cp_ref))
      .omit_first()
      .repeat(),
    )
    .and_then(pwhite_space().optional())
    .omit_second()
    .and_then(@combinator.pchar(')'))
    .omit_second()
    .map(fn(tuple) {
      let (cp, arr) = tuple
      arr.insert(0, cp)
      arr
    })
  let seq = @combinator.pchar('(')
    .and_then(pwhite_space().optional())
    .and_then(Parser::from_ref(cp_ref))
    .omit_first()
    .and_then(
      pwhite_space()
      .optional()
      .and_then(@combinator.pchar(','))
      .and_then(pwhite_space().optional())
      .and_then(Parser::from_ref(cp_ref))
      .omit_first()
      .repeat(),
    )
    .and_then(pwhite_space().optional())
    .omit_second()
    .and_then(@combinator.pchar(')'))
    .omit_second()
    .map(fn(tuple) {
      let (cp, arr) = tuple
      arr.insert(0, cp)
      arr
    })
  let cp = pname()
    .map(fn(s) { Name(s) })
    .or_else(choice.map(fn(arr) { Choice(arr) }))
    .or_else(seq.map(fn(arr) { Seq(arr) }))
    .and_then(
      @combinator.pchar_such_that(ch => ch == '*' || ch == '+' || ch == '?').optional(),
    )
    .map(fn(tuple) {
      let (cp, ch) = tuple
      let pair = match ch {
        Some('*') => (cp, Some(ZeroOrMore))
        Some('+') => (cp, Some(OneOrMore))
        Some('?') => (cp, Some(Optional))
        None => (cp, None)
        _ => abort("unreachable")
      }
      ContentParticle(pair.0, pair.1)
    })
  cp_ref.val = cp
  let children : Parser[Char, ChildrenContentSpec] = choice
    .map(fn(arr) { Choice(arr) })
    .or_else(seq.map(fn(arr) { Seq(arr) }))
    .and_then(
      @combinator.pchar_such_that(ch => ch == '*' || ch == '+' || ch == '?').optional(),
    )
    .map(fn(tuple) {
      let (cp, ch) = tuple
      let pair = match ch {
        Some('*') => (cp, Some(ZeroOrMore))
        Some('+') => (cp, Some(OneOrMore))
        Some('?') => (cp, Some(Optional))
        None => (cp, None)
        _ => abort("unreachable")
      }
      ContentParticle(pair.0, pair.1)
    })
  let mixed = @combinator.pstring("(")
    .and_then(pwhite_space().optional())
    .and_then(@combinator.pstring("#PCDATA"))
    .and_then(pwhite_space().optional())
    .and_then(@combinator.pstring(")"))
    .map(fn(_tuple) { Mixed([]) })
    .or_else(
      @combinator.pstring("(")
      .and_then(pwhite_space().optional())
      .and_then(@combinator.pstring("#PCDATA"))
      .and_then(
        pwhite_space()
        .optional()
        .and_then(@combinator.pchar('|'))
        .and_then(pwhite_space().optional())
        .and_then(pname())
        .omit_first()
        .repeat(),
      )
      .omit_first()
      .and_then(pwhite_space().optional())
      .omit_second()
      .and_then(@combinator.pstring(")*"))
      .omit_second()
      .map(fn(arr) { Mixed(arr) }),
    )
  let contentspec = @combinator.pstring("EMPTY")
    .map(fn(_s) { EMPTY })
    .or_else(@combinator.pstring("ANY").map(fn(_s) { ANY }))
    .or_else(mixed)
    .or_else(children.map(fn(child) { Children(child) }))
  let parser = @combinator.pstring(""))
    .omit_second()
    .map(fn(tuple) {
      let (name, content_spec) = tuple
      // println(tuple)
      { name, content_spec }
    })
  return parser
}

///|
test "ElementDecl" {
  // 
  // 
  //  don't support 
  // 
  // 
  // 
  // 
  // 
  // 
  // 
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)
  inspect(result.name, content="br")
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)

  // let str = #|
  // let seq = Seq::from_string(str)
  // let result = parser.run(seq).unwrap().0
  // println(result)

  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="spec")
  inspect(result, content=str)
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="div1")
  inspect(result, content=str)

  // let str = #|
  // let seq = Seq::from_string(str)
  // let parser = pelementDecl()
  // let result = parser.run(seq).unwrap().0
  // println(result)

  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="p")
  inspect(result, content=str)

  // let str = #|
  // let seq = Seq::from_string(str)
  // let parser = pelementDecl()
  // let result = parser.run(seq).unwrap().0
  // println(result)

  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pelementDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="b")
  inspect(result, content=str)

  // let str = #|
  // let seq = Seq::from_string(str)
  // let parser = pelementDecl()
  // let result = parser.run(seq).unwrap().0
  // println(result)
}

///|
fn pattListDecl() -> Parser[Char, AttlistDecl] {
  fn pattType() -> Parser[Char, AttType] {
    let notationType = @combinator.pstring("NOTATION")
      .and_then(pwhite_space())
      .and_then(@combinator.pchar('('))
      .and_then(pwhite_space().optional())
      .and_then(pname())
      .omit_first()
      .and_then(
        pwhite_space()
        .optional()
        .and_then(@combinator.pchar('|'))
        .and_then(pwhite_space().optional())
        .and_then(pname())
        .omit_first()
        .repeat(),
      )
      .and_then(pwhite_space().optional())
      .omit_second()
      .and_then(@combinator.pchar(')'))
      .omit_second()
      .map(fn(tuple) {
        let (str, arr) = tuple
        arr.insert(0, str)
        NotationType(arr)
      })
    let enumeration = @combinator.pchar('(')
      .and_then(pwhite_space().optional())
      .and_then(pnmtokens())
      .omit_first()
      .and_then(
        pwhite_space()
        .optional()
        .and_then(@combinator.pchar('|'))
        .and_then(pwhite_space().optional())
        .and_then(pnmtokens())
        .omit_first()
        .repeat(),
      )
      .and_then(pwhite_space().optional())
      .omit_second()
      .and_then(@combinator.pchar(')'))
      .omit_second()
      .map(fn(tuple) {
        let (str, arr) = tuple
        arr.insert(0, str)
        Enumeration(arr)
      })
    let stringType = @combinator.pstring("CDATA").map(fn(str) {
      StringType(str)
    })
    let tokenizedType = @combinator.pstring("IDREFS")
      .or_else(@combinator.pstring("IDREF"))
      .or_else(@combinator.pstring("ID"))
      .or_else(@combinator.pstring("ENTITY"))
      .or_else(@combinator.pstring("ENTITIES"))
      .or_else(@combinator.pstring("NMTOKENS"))
      .or_else(@combinator.pstring("NMTOKEN"))
      .map(fn(str) { TokenizedType(str) })
    let attType = stringType
      .or_else(tokenizedType)
      .or_else(notationType)
      .or_else(enumeration)
    attType
  }

  fn pdefaultDecl() -> Parser[Char, DefaultDecl] {
    let required = @combinator.pstring("#REQUIRED").map(fn(_str) { Required })
    let implied = @combinator.pstring("#IMPLIED").map(fn(_str) { Implied })
    let fixed = @combinator.pstring("#FIXED")
      .and_then(pwhite_space())
      .and_then(pattValue())
      .map(fn(tuple) {
        let (_, str) = tuple
        Fixed(str)
      })
    let value = pattValue().map(fn(str) { Value(str) })
    required.or_else(implied).or_else(fixed).or_else(value)
  }

  let pattDef = pwhite_space()
    .and_then(pname())
    .omit_first()
    .and_then(pwhite_space())
    .omit_second()
    .and_then(pattType())
    .and_then(pwhite_space())
    .omit_second()
    .and_then(pdefaultDecl())
    .map(fn(tuple) {
      let ((name, att_type), default_decl) = tuple
      { name, att_type, default_decl }
    })
  let parser = @combinator.pstring(""))
    .omit_second()
    .map(fn(tuple) {
      let (name, att_defs) = tuple
      { name, att_defs }
    })
  parser
}

///|
test "attListDecl" {
  // 
  // 
  // 

  let parser = pattListDecl()
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap()
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="termdef")
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="list")
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="form")
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="poem")
  let str =
    #|
  let seq = Seq::from_string(str)
  let result = parser.run(seq).unwrap().0
  inspect(result.name, content="pre")
}

///|
fn pnotationDecl() -> Parser[Char, NotationDecl] {
  @combinator.pstring(""))
  .omit_second()
  .map(fn(tuple) {
    let (name, id) = tuple
    { name, id }
  })
}

///|
test "NotationDecl" {
  // 
  // 
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pnotationDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)
  let str =
    #|
  let seq = Seq::from_string(str)
  let parser = pnotationDecl()
  let result = parser.run(seq).unwrap().0
  inspect(result, content=str)
}

///|
fn pmarkupDecl() -> Parser[Char, MarkUpDecl] {
  pattListDecl()
  .map(fn(attlist) { AttListDecl(attlist) })
  .or_else(pelementDecl().map(fn(element) { ElementDecl(element) }))
  .or_else(pentityDecl().map(fn(entity) { EntityDecl(entity) }))
  .or_else(pnotationDecl().map(fn(notation) { NotationDecl(notation) }))
  .or_else(ppi().map(fn(pi) { MarkUpDecl::PI(pi) }))
  .or_else(pcomment().map(fn(comment) { MarkUpDecl::Comment(comment) }))
}

///|
fn pinternalSubset() -> Parser[Char, Array[DTDStatement]] {
  let declSep = pPEReference().or_else(pwhite_space()).map(fn(s) { Sep(s) })
  let parser = pmarkupDecl()
    .map(fn(decl) { Decl(decl) })
    .or_else(declSep)
    .repeat()
  parser
}

///|
fn pmisc() -> Parser[Char, Misc] {
  pcomment()
  .map(fn(comment) { Misc::Comment(comment) })
  .or_else(ppi().map(fn(pi) { Misc::PI(pi) }))
  .or_else(pwhite_space().map(fn(s) { Misc::WhiteSpace(s) }))
}