///|
/// Metadata of Python's `BBCodeFormatter`.
pub let bbcode_info : FormatterInfo = {
  class_name: "BBCodeFormatter",
  name: "BBCode",
  aliases: ["bbcode", "bb"],
  filenames: [],
  description: "Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there.",
}

///|
/// Metadata of Python's `PangoMarkupFormatter`.
pub let pango_info : FormatterInfo = {
  class_name: "PangoMarkupFormatter",
  name: "Pango Markup",
  aliases: ["pango", "pangomarkup"],
  filenames: [],
  description: "Format tokens as Pango Markup code. It can then be rendered to an SVG.",
}

///|
/// The nearest ancestor of `ttype` present in `styles`.
fn nearest_styled(
  styles : Map[@token.TokenType, (String, String)],
  ttype : @token.TokenType,
) -> @token.TokenType {
  let mut t = ttype
  while !styles.contains(t) {
    match t.parent() {
      Some(p) => t = p
      None => break
    }
  }
  t
}

///|
/// Merges runs of tokens with the same (resolved) style and wraps each run
/// in its start/end markup (the loop shared by the BBCode and Pango
/// formatters).
fn write_merged(
  buf : StringBuilder,
  styles : Map[@token.TokenType, (String, String)],
  tokens : Tokens,
  escape : (String) -> String,
) -> Unit {
  let lastval = StringBuilder()
  let mut lasttype : @token.TokenType? = None
  let flush = (t : @token.TokenType?) => {
    if lastval.to_string() != "" {
      let (start, end) = match t {
        Some(t) => styles.get(t).unwrap_or(("", ""))
        None => ("", "")
      }
      buf.write_string(start)
      buf.write_string(lastval.to_string())
      buf.write_string(end)
    }
  }
  for tok in tokens {
    let ttype = nearest_styled(styles, tok.0)
    if lasttype == Some(ttype) {
      lastval.write_string(escape(tok.1))
    } else {
      flush(lasttype)
      lastval.reset()
      lastval.write_string(escape(tok.1))
      lasttype = Some(ttype)
    }
  }
  flush(lasttype)
}

///|
/// Python's `BBCodeFormatter`. Options: `style`, `codetag`, `monofont`.
pub fn bbcode_formatter(
  options? : @lexer.Options = Map([]),
  style? : @styles.Style,
) -> Formatter raise {
  let base = BaseOptions::new(options, style?)
  let code = @lexer.get_bool_opt(options, "codetag", false)
  let mono = @lexer.get_bool_opt(options, "monofont", false)
  let styles : Map[@token.TokenType, (String, String)] = Map([])
  for entry in base.style.iter() {
    let (ttype, ndef) = entry
    let mut start = ""
    let mut end = ""
    if ndef.color is Some(c) {
      start += "[color=#\{c}]"
      end = "[/color]" + end
    }
    if ndef.bold {
      start += "[b]"
      end = "[/b]" + end
    }
    if ndef.italic {
      start += "[i]"
      end = "[/i]" + end
    }
    if ndef.underline {
      start += "[u]"
      end = "[/u]" + end
    }
    styles[ttype] = (start, end)
  }
  Formatter::new(bbcode_info, base, tokens => {
    let buf = StringBuilder()
    if code {
      buf.write_string("[code]")
    }
    if mono {
      buf.write_string("[font=monospace]")
    }
    write_merged(buf, styles, tokens, s => s)
    if mono {
      buf.write_string("[/font]")
    }
    if code {
      buf.write_string("[/code]")
    }
    if code || mono {
      buf.write_string("\n")
    }
    buf.to_string()
  })
}

///|
/// Python's `pangomarkup.escape_special_chars`.
fn pango_escape(text : String) -> String {
  text.replace_all(old="&", new="&").replace_all(old="<", new="<")
}

///|
/// Python's `PangoMarkupFormatter`. Options: `style`.
pub fn pango_markup_formatter(
  options? : @lexer.Options = Map([]),
  style? : @styles.Style,
) -> Formatter raise {
  let base = BaseOptions::new(options, style?)
  let styles : Map[@token.TokenType, (String, String)] = Map([])
  for entry in base.style.iter() {
    let (ttype, ndef) = entry
    let mut start = ""
    let mut end = ""
    if ndef.color is Some(c) {
      start += ""
      end = "" + end
    }
    if ndef.bold {
      start += ""
      end = "" + end
    }
    if ndef.italic {
      start += ""
      end = "" + end
    }
    if ndef.underline {
      start += ""
      end = "" + end
    }
    styles[ttype] = (start, end)
  }
  Formatter::new(pango_info, base, tokens => {
    let buf = StringBuilder()
    buf.write_string("")
    write_merged(buf, styles, tokens, pango_escape)
    buf.write_string("")
    buf.to_string()
  })
}