package net.liftweb.textile

/*
 * Copyright 2006-2008 WorldWide Conferencing, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import _root_.scala.util.parsing.combinator.{Parsers, ImplicitConversions}
import _root_.scala.xml.{Elem => XmlElem, MetaData, NodeSeq, Null, Text, TopScope, Unparsed, UnprefixedAttribute, Group, Node}
import _root_.scala.collection.mutable.HashMap

/**
 * The textile parser
 *
 * ported to the scala.util.parsing.combinator style of parsing by Adriaan Moors
 */
object TextileParser {
  type RewriteFunc = WikiURLInfo => (String, NodeSeq, Option[String])

  case class WikiURLInfo(word: String, category: Option[String]) {
    override def toString = category match {
      case Some(cat) => cat+"/"+word
      case _ => word
    }
  }


  def parse(toParse: String, urlRewrite: Option[RewriteFunc]): Option[Lst] =
  parse(toParse, urlRewrite, false)

  private def fixBadWindows(in: String): String = {
    val len = in.length
    val minOne = len - 1
    val ret = new StringBuilder(len)
    var pos = 0
    while (pos < len) {
      in.charAt(pos) match {
        case '\r' if pos < minOne && in.charAt(pos + 1) == '\n' => // ignore CR/LF
        case '\r' => ret.append('\n')
        case c => ret.append(c)
      }
      pos = pos + 1
    }
    ret.toString
  }

  /**
   * Take a string and return the parsed value.
   * Lst is a list of Textile parsed elements.  You can do a .toHtml on the Lst
   * to get the XHTML result to send to the browser.
   * int will be the number of characters parsed.
   */
  def parse(_toParse: String, urlRewrite: Option[RewriteFunc], disableLinks: Boolean): Option[Lst] = {
    val toParse = fixBadWindows(_toParse) match {
      case null => "null\n\n"
      case s if !s.endsWith("\n\n") => s + "\n\n"
      case s => s
    }

    def findRefs(in : List[Textile]) : List[Pair[String,String]] = in match {
      case (s : ARef) :: rest => {Pair(s.name, s.href) :: findRefs(rest)}
      case (s : ATextile) :: rest => {findRefs(s.theElems) ::: findRefs(rest)}
      case x :: rest => {findRefs(rest)}
      case _ => Nil
    }


    def fixRefs(in : List[Textile], refs: HashMap[String, String]): Unit = in match {
      case Nil =>

      case (s: RefAnchor) :: rest =>
        refs.get(s.ref) match {case Some(tr) => s.href = tr case None => s.href = "javascript://"}
        fixRefs(rest, refs)

      case (s: ATextile) :: rest =>
        fixRefs(s.theElems, refs)
        fixRefs(rest, refs)

      case _ :: rest => fixRefs(rest, refs)

      case huh =>
    }

    val parser = new TextileParsers(urlRewrite, disableLinks)
    val lst = parser.document(new _root_.scala.util.parsing.input.CharArrayReader(toParse.toCharArray()))

    lst map { it =>
      val tr = findRefs(List(it))
      val refs = new HashMap[String,String];
      tr.foreach{b => refs(b._1) = b._2}

      fixRefs(List(it), refs)

      Some(it)
    } getOrElse None
  }

  def toHtml(toParse: String, wikiUrlFunc: Option[RewriteFunc], disableLinks: Boolean): NodeSeq = {
    parse(toParse, wikiUrlFunc, disableLinks).map(_.toHtml) getOrElse Text("")
  }

  def toHtml(toParse: String, disableLinks: Boolean): NodeSeq =
  toHtml(toParse, None, disableLinks)

  def toHtml(toParse: String, wikiUrlFunc: Option[RewriteFunc]): NodeSeq =
  toHtml(toParse, wikiUrlFunc, false)

  def toHtml(toParse: String): NodeSeq =
  toHtml(toParse, None, false)

  /**
   * Useful helper function for stripping out the surrounding <p> tags.
   */
  def paraFixer(in: NodeSeq): NodeSeq = in match {
    case g: Group => paraFixer(g.nodes)
    case e: XmlElem if e.label == "p" => e.child
    case e: XmlElem => e
    case ns: Seq[Node] if ns.length == 2 &&
      ns(1).text.trim.length == 0 => paraFixer(ns(0))
    case x => x
  }

  /**
   * the thing that actually does the textile parsing
   */
  class TextileParsers(wikiUrlFunc: Option[RewriteFunc], disableLinks: Boolean) extends Parsers with ImplicitConversions {
    type Elem = Char

    type UnitParser=Parser[Unit]

    implicit def discard[T](p: Parser[T]): Parser[Unit] = p ^^ {x => ()}

    lazy val document : Parser[Lst] = rep(paragraph) ^^ Lst
    // final val  Ch = '\032'
    private def chrExcept(cs: Char*): Parser[Char] = elem("", {c => ('\032' :: cs.toList) forall (_ != c)}) //{x =>  !cs.contains(x)})
    private def mkString(cs: List[Any]) = cs.mkString("")

    implicit def str2chars(s: String): List[Char] = stringWrapper(s).toList

    lazy val num = rep1(elem("digit", Character.isDigit)) ^^ mkString

    def enclosed(delim: Char, what: String, pred: Char => Boolean) = delim ~> str(what, {c => c != delim && pred(c)}) <~ delim
    def str(what: String, pred: Char => Boolean) = rep(elem(what, pred)) ^^ mkString
    def str1(what: String, pred: Char => Boolean) = rep1(elem(what, pred)) ^^ mkString
    def chrsExcept(cs: Char*): Parser[String] = rep1(chrExcept(cs : _*)) ^^ mkString


    lazy val beginl = Parser[Unit]{ in =>
      if(in.pos.column==1) Success((), in) else Failure("at column "+in.pos.column+", not beginning of line", in)
    }

    lazy val beginlS = beginl ~ rep(' ')

    /**
     * is it a blank line?  Start of line, some spaces, and an end of line
     */
    lazy val blankLine: Parser[Textile] = beginlS ~ '\n' ^^^ BlankLine


    /**
     * Line elements make up paragraphs and block elements
     */
    lazy val lineElem : Parser[Textile] = {
      not(discard(blankLine)) ~> (endOfLine | image | footnote_def |
                                  anchor | dimension | elipsis  |
                                  copyright | trademark | registered |
                                  emDash |
                                  enDash | italic | emph | bold  |
                                  cite |  span | code | delete | insert |
                                  sup | sub | strong | html |
                                  single_quote | quote | acronym | charBlock)
    }

    /**
     * If we've got an italic (__text__), the parser doesn't do well with a single underscore, so
     * we exclude looking for _emph_
     */
    lazy val lineElem_notEmph : Parser[Textile] = {
      not(discard(blankLine)) ~> (endOfLine | image | footnote_def | anchor |
                                  dimension | elipsis |
                                  copyright | trademark | registered | emDash | enDash | italic |
                                  bold  |
                                  cite |  span| code | delete | insert| sup | sub | strong  |
                                  html| single_quote | quote | acronym | charBlock)
    }

    /**
     * Don't look for *strong* if we're currently in a **bold** element
     */
    lazy val lineElem_notStrong : Parser[Textile] = {
      not(discard(blankLine)) ~> (endOfLine | image | footnote_def | anchor |
                                  dimension | elipsis |
                                  copyright | trademark | registered | emDash | enDash | italic |
                                  emph |
                                  cite |  span | code | delete | insert  | sup |
                                  sub | bold  | html| single_quote | quote | acronym  | charBlock)
    }


    /**
     * Look for an acronym, but don't mistake registered, copyright, and trademarks
     */
    lazy val acronym : Parser[Acronym] =
    ((chrsExcept(' ', '(', '\n') <~
      not(discard(copyright | trademark | registered))) ~
     ('(' ~> chrsExcept(')', '\n') <~ ')') ) ^^ flatten2(Acronym)

    /**
     * is it an !image!
     */
    lazy val image : Parser[Textile] =
    ('!' ~> opt('<')) ~ opt('>') ~
    (rep1(not(accept('!')) ~> elem("", validUrlChar)) ^^ mkString) ~
    (opt('(' ~> chrsExcept(')', '\n') <~ ')') <~ '!') ~
    opt(':' ~> url ^? {case a: Anchor => a}) ^^
    { case fl ~ fr ~ img_url ~ alt ~ link =>
        Image(img_url, alt getOrElse "", link.map(_.href) getOrElse null,
              if (!fl.isEmpty) List(AnyAttribute("style", "float:left"))
              else if (!fr.isEmpty) List(AnyAttribute("style", "float:right"))
              else Nil)}


    /**
     * [footnote]
     */
    lazy val footnote_def: Parser[Textile] = '[' ~> num <~ ']' ^^ FootnoteDef

    /**
     * various things that make up an anchor (a tag)
     */
    lazy val anchor =
    url | quote_url | quote_ref | a_ref | escCamelUrl | camelUrl


    lazy val upper: Parser[Char] =
    elem("Uppercase character", Character.isUpperCase)

    lazy val lower: Parser[Char] =
    elem("Lowercase character", Character.isLowerCase)

    lazy val lowerOrNumber: Parser[Char] =
    elem("Lowercase character or digit", c => Character.isLowerCase(c) ||
         Character.isDigit(c))

    /**
     * Don't use the CamelCase thing for a wikiword if it's prefixed by
     * a backslash
     */
    lazy val escCamelUrl: Parser[CharBlock] =
    ('\\'  ~ upper ~ lower ~ rep(lowerOrNumber) ~
     upper ~ lower ~ rep(lowerOrNumber) ~
     rep(upper ~ lower ~ rep(lowerOrNumber) ^^
         {case c1 ~ c2 ~ s => mkString(List(c1, c2))+ mkString(s)})) ^^
    {case c1 ~ c2 ~ c3 ~ s4 ~ c5 ~ c6 ~ s7 ~ s8
        => CharBlock(mkString(List(c1, c2, c3)) +
                     mkString(s4) +
                     mkString(List(c5, c6)) + mkString(s7) + s8.mkString(""))}

    /**
     * is the work camelized?
     */
    lazy val camelizedWord: Parser[CharBlock] =
    upper ~ lower ~ rep(lowerOrNumber) ^^
    { case u ~ l ~ cs => CharBlock(mkString(u :: (l :: cs))) }

    lazy val lowerWord: Parser[String] =
    lower ~ rep1(lowerOrNumber) ^^ {case x ~ xs => (x :: xs).mkString("")}


    /**
     * a WikiWord
     */
    lazy val camelUrl: Parser[Textile] =
    noCatCamelUrl | catCamelUrl | tickUrl | catTickUrl

    lazy val catTickUrl: Parser[WikiAnchor] =
    lowerWord ~ ':' ~ tickUrl ^^
    {case cat ~ colon ~ rest => WikiAnchor(rest.word, Some(cat), wikiUrlFunc)}

    lazy val tickUrl: Parser[WikiAnchor] = '`' ~ rep1(tickUrlChar) ~ '`' ^^
    {case tick ~ c ~ tick2 => WikiAnchor(c.mkString(""), None, wikiUrlFunc)}

    def isTickUrlChar(c: Char): Boolean = c.isLetter || c.isDigit ||
    c == ' ' || c == '@'

    lazy val tickUrlChar: Parser[Char] = elem("tick url char", isTickUrlChar)

    lazy val noCatCamelUrl: Parser[Textile] =
    camelizedWord ~ rep1(camelizedWord) ^^
    { case c ~ cs => val ss = mkString((c :: cs).map(_.s));
        WikiAnchor(ss, None, wikiUrlFunc)}

    lazy val catCamelUrl: Parser[Textile] =
    lowerWord ~ ':' ~ camelizedWord ~ rep1(camelizedWord) ^^ {
      case cat ~ colon ~ c ~ cs =>
        val ss = mkString((c :: cs).map(_.s));
        WikiAnchor(ss, Some(cat), wikiUrlFunc)}


    lazy val urlStr = (rep1(elem("", validUrlChar))^^ mkString)

    /**
     * "reference":reference
     */
    lazy val quote_ref: Parser[Textile] =
    ('"' ~> chrsExcept('"', '\n') <~ '"') ~ (':' ~> urlStr) ^^
    {case fs ~ rc => RefAnchor(Nil, rc, fs, Nil)}


    lazy val httpStr =
    (accept("https://") ^^ mkString | accept("http://") ^^
     mkString) ~ urlStr ^^ { case protocol ~ rc => protocol + rc }

    /**
     * "google":http://google.com
     */
    lazy val quote_url: Parser[Textile] = ('"' ~> chrsExcept('"', '\n')) ~ ('"' ~> ':' ~> httpStr) ^^
    { case fs ~ url => Anchor(Nil, url, fs, Nil, disableLinks) }

    /**
     * [reference]:http://reference.com
     */

    lazy val a_ref: Parser[Textile] = ('[' ~> urlStr <~ ']') ~ url ^^ {case  fr ~ (url: Anchor) => ARef(fr, url.href) }

    /**
     * http://google.com
     */
    lazy val url: Parser[Textile] = httpStr ^^ { u => Anchor(Nil, u, u, Nil, disableLinks) }

    /**
     * a valid character in a URL
     */
    def validUrlChar(c : Char) : Boolean = {
      Character.isLetterOrDigit(c) || c == '/' || c == '%' || c == '&' || c == '?' || c == '#' ||
      c == '$' || c == '.' ||
      c == '-' || c == ':' || c == '_'
    }


    /**
     * an EOL character
     */
    lazy val endOfLine: Parser[Textile] = (('\r' ~ '\n') | '\n') ^^^ EOL

    // replaced by checking the position of the current input (not that beginl does not consume any input,
    // so repeatedly calling beginningOfLine is not a good idea -- that will either loop forever or fail immediately)
    //def beginningOfLine : Parser[Textile] = beginl ^^ BOL


    /**
     * if we're in a <pre> block, an end of line is just an end of line.  We
     * pass the '\n' on though.
     */
    lazy val preEndOfLine: Parser[Textile] = (('\r' ~ '\n') | '\n') ^^^ CharBlock("\n")


    /**
     * a <pre> block.  Just send text of through, unmolested.
     */
    lazy val preBlock: Parser[Textile] =
    beginlS ~> accept("<pre") ~> rep(' ') ~> '>' ~>
    rep(not(discard(accept("</pre"))) ~> (preEndOfLine | charBlock )) <~
    accept("</pre") <~ rep(' ') <~ '>' <~ rep(' ') <~ '\n' ^^ {
      case elms => Pre(reduceCharBlocks(elms), Nil)
    }

    /**
     * (c)
     */
    lazy val copyright: Parser[Textile] =
    '(' ~ (accept('c') | 'C') ~ ')' ^^^ Copyright  // accept necesary because of clash with | on char's


    def validTagChar(c: Char) = Character.isDigit(c) || Character.isLetter(c) || c == '_'
    def validStyleChar(c: Char) = Character.isDigit(c) || Character.isLetter(c) || c == '.' || c == ' ' || c == ';' || c == '#'
    def validClassChar(c: Char) = Character.isDigit(c) || Character.isLetter(c) || c == '.'

    lazy val validTag =
    rep1(elem("valid tag character", validTagChar)) ^^
    mkString ^? {case s if isValidTag(s) => s}

    /**
     * An HTML block is made up of single HTML tag (e.g., <br />) and
     * tags that contain other stuff
     */
    lazy val html = single_html | multi_html

    def closingTag(tag: String) =
    accept("</") ~ accept(tag) ~ rep(' ') ~ '>'

    /**
     * an HTML tag that contains other stuff
     */
    lazy val multi_html: Parser[Textile] = ('<' ~> validTag) >> { tag =>
      rep(tag_attr) ~
      ('>' ~> rep(not(discard(closingTag(tag))) ~> (lineElem | paragraph)) <~ closingTag(tag)) ^^ {
        case attrs ~ body => HTML(tag, reduceCharBlocks(body), attrs)}}

    /**
     * A stand-alone tag
     */
    lazy val single_html: Parser[Textile] =
    ('<' ~> validTag) ~ (rep(tag_attr) <~ accept("/>")) ^^
    { case tag ~ attrs => HTML(tag, Nil, attrs) }

    lazy val tag_attr = single_quote_attr | double_quote_attr

    def attr_name(c: Char) = Character.isLetterOrDigit(c) || c == '_' || c == '-'

    def attr_value(c: Char) = c >= ' '

    /**
     * an attribute with single quotes
     */
    lazy val single_quote_attr =
    (rep(' ') ~> str1("attribute name", attr_name)) ~
    ('=' ~> enclosed('\'', "attribute value", attr_value(_))) ^^
    flatten2(AnyAttribute)

    /**
     * an attribute with double quotes
     */
    lazy val double_quote_attr: Parser[Attribute] =
    (rep(' ') ~> str1("attribute name", attr_name) <~ '=') ~
    enclosed('"', "attribute value", attr_value(_)) ^^ flatten2(AnyAttribute)

    /**
     * is it a valid HTML tag?  This list should be expanded
     */
    def isValidTag(in : String) = in == "b" || in == "em" || in == "code" || in == "div" || in == "span"


    /**
     * A "dimension" pretty printer
     */
    lazy val dimension: Parser[Textile] = accept(" x ") ^^^ Dimension

    lazy val registered: Parser[Textile] =
    '(' ~ (accept('r') | 'R') ~ ')' ^^^ Register

    lazy val trademark: Parser[Textile] =
    '(' ~ (accept('t') | 'T') ~ (accept('m') | 'M') ~ ')' ^^^ Trademark

    lazy val elipsis: Parser[Textile] = accept("...") ^^^ Elipsis

    lazy val emDash: Parser[Textile] = accept(" -- ") ^^^ EmDash

    lazy val enDash: Parser[Textile] = accept(" - ") ^^^ EnDash

    lazy val single_quote: Parser[Textile] = '\'' ^^^ SingleQuote


    def bullet(depth : Int, numbered : Boolean): Parser[Textile] = {
      val oneBullet = if (numbered) accept('#') else accept('*')

      def bullet_line(depth : Int, numbered : Boolean): Parser[Textile] =
      beginlS ~> repN(depth+1, oneBullet) ~> rep(not(discard('\n')) ~>
                                                 lineElem) <~ '\n' ^^
      {case elms => BulletLine(reduceCharBlocks(elms), Nil)}

      bullet_line(depth, numbered) ~
      (rep1(bullet(depth + 1, numbered) | bullet_line(depth, numbered))) ^^
      {case fbl ~ abl => Bullet(fbl :: abl, numbered)}
    }

    def formattedLineElem[Q <% Parser[Any]](m: Q):
    Parser[List[Textile] ~ List[Attribute]] =
    formattedLineElem(m, rep(attribute))

    def formattedLineElem[Q <% Parser[Any]](m: Q, p: Parser[List[Attribute]]):
    Parser[List[Textile] ~ List[Attribute]] =
    (m ~> p) ~ (rep1(not(discard(m) | endOfLine) ~> lineElem) <~ m) ^^
    {case attrs ~ ln => new ~(reduceCharBlocks(ln), attrs) }

    lazy val spaceOrStart: UnitParser = beginl | accept(' ')
    lazy val spaceOrEnd: UnitParser = accept(' ') | discard(endOfLine)

    // TODO: generalize formattedLineElem some more
    lazy val bold: Parser[Textile] =
    (accept("**") ~> rep(attribute)) ~
    (rep1(not(discard(accept("**") |
                      endOfLine)) ~>
          lineElem_notStrong) <~ accept("**")) ^^ {
      case attrs ~ ln => Bold(reduceCharBlocks(ln), attrs) }

    lazy val strong: Parser[Textile] = formattedLineElem('*') ^^ flatten2(Strong)

    lazy val cite: Parser[Textile] = formattedLineElem(accept("??")) ^^ flatten2(Cite)

    lazy val code: Parser[Textile] = formattedLineElem('@') ^^ flatten2(Code)

    lazy val styleElem: Parser[StyleElem] =
    str1("style", validStyleChar) ~ (':' ~> str1("style", validStyleChar)) ^^
    flatten2(StyleElem)

    lazy val attribute = style | class_id | the_class | the_id | the_lang

    lazy val para_attribute = attribute | fill_align | left_align |
    right_align |
    center_align | top_align | bottom_align | em_left | em_right;

    lazy val left_align : Parser[Attribute] = elem('<') ^^ Align

    lazy val right_align : Parser[Attribute] = elem('>') ^^ Align

    lazy val center_align : Parser[Attribute] = elem('=') ^^ Align

    lazy val top_align : Parser[Attribute] = elem('^') ^^ Align

    lazy val bottom_align : Parser[Attribute] = elem('~') ^^ Align

    lazy val fill_align : Parser[Attribute] = accept("<>") ^^^ Align('f')

    lazy val em_left : Parser[Attribute] = rep1(elem('(')) ^^ {xs => Em(xs.length)}

    lazy val em_right : Parser[Attribute] = rep1(elem(')')) ^^ {xs => Em(-xs.length)}

    lazy val class_id : Parser[Attribute] = ('(' ~> str1("class", validClassChar)) ~ ('#' ~> str1("class", validClassChar) <~ ')') ^^ flatten2(ClassAndId)

    lazy val the_id : Parser[Attribute] = '(' ~> '#' ~> str1("class", validClassChar) <~ ')' ^^ {ri => ClassAndId(null, ri)}

    lazy val the_lang : Parser[Attribute] = '[' ~> str1("class", validClassChar) <~ ']' ^^ Lang

    lazy val the_class : Parser[Attribute] = '(' ~> str1("class", validClassChar) <~ ')' ^^ {rc => ClassAndId(rc, null)}


    lazy val style : Parser[Attribute] = '{' ~> repsep(styleElem, ';') <~ '}' ^^ Style

    lazy val span : Parser[Textile] = formattedLineElem('%', opt(style) ^^ {s => s.toList}) ^^ flatten2(Span)

    lazy val delete : Parser[Textile] = formattedLineElem('-') ^^ flatten2(Delete)


    lazy val insert : Parser[Textile] = formattedLineElem('+') ^^ flatten2(Ins)

    lazy val sup : Parser[Textile] = formattedLineElem('^') ^^ flatten2(Super)

    lazy val sub : Parser[Textile] =  formattedLineElem('~') ^^ flatten2(Sub)

    lazy val italic : Parser[Textile] = formattedLineElem(accept("__")) ^^ flatten2(Italic)

    lazy val emph : Parser[Textile] = formattedLineElem('_') ^^ flatten2(Emph)

    lazy val quote : Parser[Textile] = formattedLineElem('"', success(Nil)) ^^ flatten2((x, y) => Quoted(x))

    def reduceCharBlocks(in : List[Textile]) : List[Textile] =
    (in: @unchecked) match {
      case Nil => Nil
        // this is now done (hacked) in flattenAndDropLastEOL
        //        case EOL() :: BOL() :: rest => EOL :: reduceCharBlocks(rest)
        //        case EOL() :: rest => reduceCharBlocks(rest)
      case CharBlock(s1) :: CharBlock(s2) :: rest => reduceCharBlocks(CharBlock(s1 + s2) :: rest)
      case x :: xs => x :: reduceCharBlocks(xs)
    }

    lazy val charBlock : Parser[Textile] = chrExcept('\n') ^^ {c => CharBlock(c.toString)}



    lazy val blankPara : Parser[Textile] = discard(rep1(blankLine)) ^^^ BlankLine

    lazy val not_blank_line : Parser[Textile] = not(discard(blankLine)) ~> lineElem

    lazy val head_line : Parser[Textile] = (beginl ~> 'h' ~> elem("1, 2 or 3", {c => c == '1' | c == '2' | c == '3'})) ~
    rep(para_attribute) ~
    (accept(". ") ~> rep1(not_blank_line) <~ blankLine) ^^ {
      case n ~ attrs ~ ln => Header(n - '0', reduceCharBlocks(ln), attrs) }

    lazy val blockquote : Parser[Textile] = (beginl ~> accept("bq") ~> rep(para_attribute)) ~ (accept(". ") ~> rep1(not_blank_line) <~ blankLine) ^^ {
      case attrs ~ ln => BlockQuote(reduceCharBlocks(ln), attrs)}


    lazy val footnote : Parser[Textile] = (beginl ~> accept("fn") ~> num) ~ rep(para_attribute) ~ (accept(". ") ~> rep1(not_blank_line) <~ blankLine) ^^ {
      case dr ~ attrs ~ ln => Footnote(reduceCharBlocks(ln), attrs, dr)}

    lazy val first_paraAttrs : Parser[TableDef] = beginlS ~> 'p' ~> rep(para_attribute) <~ '.' ^^ TableDef

    lazy val normPara : Parser[Textile] = opt(first_paraAttrs) ~ rep1(not_blank_line) ~ blankLine ^^ {
      case td ~ fp ~ _ => Paragraph(reduceCharBlocks(fp), td.map(_.attrs) getOrElse(Nil))}

    lazy val table_attribute = para_attribute | row_span | col_span



    lazy val row_span : Parser[Attribute] = '/' ~> num ^^ {s => AnyAttribute("rowspan", s )}

    lazy val col_span : Parser[Attribute] = '\\' ~> num ^^ {s => AnyAttribute("colspan", s )}

    lazy val table : Parser[Textile] = opt(table_def) ~ opt(table_header) ~ rep1(table_row) ^^ {
      case td ~ th ~ tr => Table(th.toList ::: tr, td.map(_.attrs) getOrElse Nil)  }

    lazy val table_def : Parser[TableDef] = (beginlS ~> accept("table") ~> rep(para_attribute) <~ '.' <~ rep(' ') <~ '\n') ^^ TableDef

    lazy val row_def : Parser[TableDef] = rep1(table_attribute) <~ '.' ^^ TableDef

    lazy val table_row : Parser[Textile] = (beginlS ~> opt(row_def)) ~ (rep(' ') ~> '|' ~> rep1(table_element(false)) <~ rep(' ') <~ '\n') ^^ {
      case td ~ re => TableRow(re, td.map(_.attrs) getOrElse Nil)}

    lazy val table_header : Parser[Textile] = (beginlS ~> opt(row_def)) ~ (rep(' ') ~> accept("|_.") ~> rep1sep(table_element(true), accept("_.")) <~ rep(' ') <~ '\n') ^^ {
      case td ~ re => TableRow(re, td.map(_.attrs) getOrElse Nil)}

    def table_element(isHeader : Boolean) : Parser[Textile] = opt(row_def) ~ (rep(not(discard(accept('|') | '\n')) ~> lineElem) <~ '|') ^^ {
      case td ~ el => TableElement(reduceCharBlocks(el), isHeader, td.map(_.attrs) getOrElse Nil)}

    lazy val paragraph : Parser[Textile] =
    preBlock | footnote | table | bullet(0, false) | bullet(0, true) | blockquote | head_line | blankPara | normPara
  }



  abstract class Textile {
    def toHtml : NodeSeq
  }

  case class Style(elms : List[StyleElem]) extends Attribute {
    def name(which : Int) : String = "style"
    def value(which : Int) : String = elms.mkString("",";","")
  }

  abstract class Attribute extends Textile {
    def toHtml = null
    def name(which : Int) : String
    def value(which : Int) : String
    def fieldCnt = 1
    def toList : List[Pair[String, String]] = {
      var ret : List[Pair[String,String]] = Nil
      var cnt = fieldCnt
      while (cnt > 0) {
        cnt = cnt -1
        val tn = name(cnt)
        val tv = value(cnt)
        if ((tn ne null) && (tv ne null)) ret = Pair(tn,tv) :: ret
      }
      ret
    }
  }

  case class AnyAttribute(name : String, value : String) extends Attribute {
    def name(which : Int) : String = name
    def value(which : Int) : String = value
  }

  case class ClassAndId(className : String, idName : String) extends Attribute {
    override def fieldCnt = 2
    def name(which : Int) : String = {
      which match {
        case 0 => if ((className ne null) && className.length > 0) "class" else null
        case 1 => if ((idName ne null) && idName.length > 0) "id" else null
      }
    }
    def value(which : Int) : String = {
      which match {
        case 0 => if ((className ne null) && className.length > 0) className else null
        case 1 => if ((idName ne null) && idName.length > 0) idName else null
      }
    }

  }

  case class Lang(lang : String) extends Attribute {
    def name(which : Int) : String = "lang"
    def value(which : Int) : String = lang
  }

  case class Align(align : Char) extends Attribute {
    def name(which : Int) : String = "style"
    def value(which : Int) : String = {
      align match {
        case '<' => "text-align:left";
        case '>' => "text-align:right";
        case '=' => "text-align:center";
        case '^' => "vertical-align:top";
        case '~' => "vertical-align:bottom";
        case 'f' | 'j' => "text-align:justify";

      }
    }
  }

  case class Em(cnt : Int) extends Attribute {
    def name(which : Int) : String = "style"
    def value(which : Int) : String = {
      if (cnt > 0) "padding-left:"+cnt+"em" else "padding-right:"+(-cnt)+"em"
    }
  }



  case class StyleElem(name : String, value : String) extends Textile {
    def toHtml = null
    override def toString = name+":"+value
  }

  abstract class ATextile(val theElems : List[Textile], attrs : List[Attribute]) extends Textile {
    def fromStyle(st : List[Attribute]) : MetaData = {
      def toList(st : List[Attribute]) : List[Pair[String, String]] = {
        st match {
          case Nil => Nil
          case x :: xs => x.toList ::: toList(xs)
        }
      }

      def crunchStyle(st : List[Pair[String, String]]) : List[Pair[String,String]] = {
        val p = st.partition {a => a._1 == "style"}
        if (p._1 == Nil) p._2 else
        (p._1.reduceLeft{(a : Pair[String,String],b : Pair[String,String]) => Pair("style", a._2 + ";"+ b._2)}) :: p._2
      }

      def fromList(st : List[Pair[String,String]]) : MetaData = {
        st match {
          case Nil => Null
          case x :: xs => {new UnprefixedAttribute(x._1, x._2, fromList(xs))}
        }
      }
      fromList(crunchStyle(toList(st)))
    }

    def toHtml :NodeSeq = {
      flattenAndDropLastEOL(theElems)
    }
  }

  case class Line(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = super.toHtml ++ Text("\n")
  }

  case class Lst(elems : List[Textile]) extends ATextile(elems, Nil) {
    /*
     def performOnWikiAnchor(f : (WikiAnchor) => Any) : Unit = {
     def findWikiAnchor(in : List[Textile], f : (WikiAnchor) => Any) : Unit = {
     in match {
     case (s : WikiAnchor) :: rest => {f(s) ; findWikiAnchor(rest, f)}
     case (s : ATextile) :: rest => {findWikiAnchor(s.theElems, f); findWikiAnchor(rest, f)}
     case x :: rest => {findWikiAnchor(rest, f)}
     case _ => Nil
     }
     }
     findWikiAnchor(List(this), f)
     }*/
  }

  // drop the last EOL to prevent needless <br />
  // TODO: find a better solution.. it's not quite clear to me where newlines are meaningful
  private def flattenAndDropLastEOL(elems : List[Textile]) = ((elems match {case Nil => Nil case x => (x.last match { case EOL => elems.init case _ => elems})})).flatMap{e => e.toHtml.toList}

  case class Paragraph(elems : List[Textile], attrs: List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "p", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
    }

  }

  case class Acronym(thing : String, acro : String) extends ATextile(Nil, Nil) {
    override def toHtml : NodeSeq =
    XmlElem(null, "acronym", fromStyle(AnyAttribute("title", acro) :: Nil), TopScope, Text(thing) : _*)
  }

  case class Image(url : String, alt : String, link : String, attrs : List[Attribute] ) extends ATextile(Nil, attrs) {
    override def toHtml : NodeSeq = {
      val img = XmlElem(null, "img", fromStyle(AnyAttribute("src", url) :: AnyAttribute("alt", alt) :: attrs), TopScope, Nil : _*)

      if (link ne null) XmlElem(null, "a", fromStyle(AnyAttribute("href", link) :: attrs), TopScope, img : _*)
      else img
    }
  }

  case object BlankLine extends Textile {
    def toHtml = Text("")
  }

  case class CharBlock(s : String) extends Textile {
    def toHtml = Text(s)
  }

  case class Quoted(elems : List[Textile]) extends ATextile(elems, Nil) {
    override def toHtml: NodeSeq = {
      (<xml:group>“</xml:group> ++
       flattenAndDropLastEOL(elems)) ++
      <xml:group>”</xml:group>
    }
  }

  case object EmDash extends Textile  {
    def toHtml : NodeSeq = <xml:group> — </xml:group>
  }

  /*  case class BOL extends Textile  {
   def toHtml : NodeSeq = Text("")
   }*/

  case object EOL extends Textile  {
    // return the characters because Scala's XML library returns <br></br> in the
    // toString for the element and this causes 2 line breaks in some browsers
    // def toHtml :NodeSeq = XmlElem(null, "br", null, TopScope, Nil : _*) concat Text("\n")
    def toHtml :NodeSeq = <br/>
  }

  case object EnDash extends Textile  {
    def toHtml : NodeSeq = <xml:group> – </xml:group>
  }

  case object SingleQuote extends Textile  {
    def toHtml : NodeSeq = <xml:group>’</xml:group>
  }

  case object Elipsis extends Textile  {
    def toHtml : NodeSeq = <xml:group>…</xml:group>
  }

  case object Dimension extends Textile   {
    def toHtml : NodeSeq = <xml:group>×</xml:group>
  }

  case object Trademark extends Textile  {
    def toHtml : NodeSeq = <xml:group>™</xml:group>
  }
  case object Copyright extends Textile {
    def toHtml : NodeSeq = <xml:group>©</xml:group>
  }
  case object Register extends Textile {
    def toHtml : NodeSeq = <xml:group>®</xml:group>
  }

  case class Header(what : Int, elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq =
    XmlElem(null, "h"+what, fromStyle(attrs), TopScope, super.toHtml : _*) ++
    Text("\n")
  }

  case class BlockQuote(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      val par : NodeSeq = XmlElem(null, "p", null, TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
      XmlElem(null, "blockquote", fromStyle(attrs), TopScope, par : _*)  ++ Text("\n")
    }
  }

  val validAttributes = List(/*"class", */ "title", /*"style", "dir",*/ "lang")

  case class HTML(tag : String, elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    def validAttr(in: Attribute): Boolean = in match {
      case AnyAttribute(name, _) => validAttributes.contains(name)
      case _ => false
    }

    override def toHtml : NodeSeq = {
      XmlElem(null, tag, fromStyle(attrs.filter(validAttr)), TopScope, flattenAndDropLastEOL(elems) : _*)
    }}
  case class FootnoteDef(num : String) extends ATextile(null, Nil) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "sup", Null, TopScope,
              XmlElem(null, "a", fromStyle(List(AnyAttribute("href", "#fn"+num))), TopScope, Text(num) : _*) : _*)
    }
  }

  // ) yield Footnote(reduceCharBlocks(le :: ln), attrs, (d1 :: dr).mkString(""))

  case class Footnote(elems : List[Textile], attrs : List[Attribute], num : String) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "p", fromStyle(AnyAttribute("id", "fn"+num) :: attrs), TopScope,
              (XmlElem(null, "sup", null, TopScope, Text(num) : _*) :: flattenAndDropLastEOL(elems)) : _*) ++ Text("\n")
    }
  }

  case class Emph(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "em", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
    }
  }
  case class Strong(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "strong", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
    }
  }

  case class TableDef(attrs : List[Attribute]) extends Textile {
    def toHtml = null
  }

  case class Table(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "table", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
    }
  }

  case class TableRow(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "tr", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
    }
  }

  case class TableElement(elems : List[Textile], isHeader : Boolean, attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null,
              if (isHeader) "th" else "td", fromStyle(attrs),
              TopScope,
              (if (elems == Nil) <xml:group> </xml:group> else
               flattenAndDropLastEOL(elems)) : _*) ++ Text("\n")
    }
  }

  case class Bullet(elems : List[Textile], numbered : Boolean) extends ATextile(elems, Nil) {
    override def toHtml : NodeSeq = {
      XmlElem(null, if (numbered) "ol" else "ul", fromStyle(Nil), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
    }
  }

  case class BulletLine(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "li", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
    }
  }

  case class Italic(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "i", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
    }
  }
  case class Bold(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = {
      XmlElem(null, "b", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
    }
  }
  case class Cite(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs){
    override def toHtml : NodeSeq = XmlElem(null, "cite", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Code(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "code", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Delete(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "del", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Ins(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "ins", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Super(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "sup", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Sub(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "sub", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }
  case class Pre(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "pre", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*) ++ Text("\n")
  }
  case class Span(elems : List[Textile], attrs : List[Attribute]) extends ATextile(elems, attrs) {
    override def toHtml : NodeSeq = XmlElem(null, "span", fromStyle(attrs), TopScope, flattenAndDropLastEOL(elems) : _*)
  }


  case class Anchor(elems : List[Textile], href : String, alt : String, attrs : List[Attribute], disableLinks: Boolean) extends ATextile(elems, attrs) {
    def allAttrs = AnyAttribute("href", href) :: attrs
    override def toHtml : NodeSeq = if (disableLinks) Text(alt) else
    XmlElem(null, "a", fromStyle(allAttrs), TopScope, Text(alt) : _*)
  }

  case class RefAnchor(elems : List[Textile], ref : String, alt : String, attrs : List[Attribute]) extends ATextile(elems, attrs) {
    private var _href = ""
    def href = _href
    def href_=(i : String) {_href = i}

    def allAttrs = {AnyAttribute("href", _href) :: attrs }
    override def toHtml : NodeSeq = XmlElem(null, "a", fromStyle(allAttrs), TopScope, Text(alt) : _*)
  }

  case class ARef( name : String, href : String) extends ATextile(Nil, Nil) {
    override def toHtml : NodeSeq = Text("")
  }

  case class WikiAnchor(word: String, category: Option[String], wikiFunc: Option[RewriteFunc]) extends ATextile(Nil, Nil) {
    // var rootUrl = ""
    def allAttrs: List[AnyAttribute] = wikiFunc match {
      case Some(func) =>
        func(WikiURLInfo(word, category)) match {
          case (href, _, Some(cls)) => AnyAttribute("href", href) :: AnyAttribute("class", cls) :: Nil
          case (href, _, _) => AnyAttribute("href", href) :: Nil
        }

      case _ => Nil
    }
    // def allAttrs = wikiFunc.map(wf => AnyAttribute("href", wf(href)) :: attrs) getOrElse attrs
    override def toHtml: NodeSeq =
    wikiFunc match {
      case Some(func) =>
        func(WikiURLInfo(word, category)) match {
          case (href, text, Some(cls)) => <a href={href} class={cls}>{text}</a>
          case (href, text, _) => <a href={href}>{text}</a>
        }

      case _ => Text(word)
    }

    // wikiFunc.map(ignore => Elem(null, "a", fromStyle(allAttrs), TopScope, Text(alt) : _*)) getOrElse Text(alt)
  }
  /*
   case class WikiAnchor(elems: List[Textile], href: String, alt: String, attrs: List[Attribute], wikiFunc: Option[RewriteFunc]) extends ATextile(elems, attrs) {
   // var rootUrl = ""
   def allAttrs = wikiFunc.map(wf => AnyAttribute("href", wf(href)) :: attrs) getOrElse attrs
   override def toHtml: NodeSeq = wikiFunc.map(ignore => XmlElem(null, "a", fromStyle(allAttrs), TopScope, Text(alt) : _*)) getOrElse Text(alt)
   }*/

  val example =
  """I am <em>very</em> serious

Observe -- very nice!

Observe - tiny and brief.

"Observe!"

Hello Dude

**Bold * Not Strong**


my bold line **bold**

**strong* Not Bold


*strong*

This is a single paragraph

This is another paragraph

I am <b>very</b> serious.

This
is a paragraph

<pre>
I am <b>very</b> serious.

Oh, yes I am!!
</pre>

I spoke.
And none replied.




Observe...

Observe: 2 x 2.

one(TM), two(R), three(C).

h1. Header 1
second line of header 1

h2. Header 2

h3. Header 3

An old text

bq. A block quotation.

Any old text

This is covered elsewhere[1].

fn1. Down here, in fact.

I _believe_ every word.

And then? She *fell*!

I __know__.
I **really** __know__.

??Cat's Cradle?? by Vonnegut

Convert with @r.to_html@

I'm -sure- not sure.

You are a +pleasant+ child.

a ^2^ + b ^2^ = c ^2^

log ~2~ x

I'm %unaware% of most soft drinks.

I'm %{color:red}unaware%
of most soft drinks.

http://hobix.com/textile/#attributes

I searched "Google":http://google.com.

CamelCase

\\CamelCase

ThreeHumpCamel

Four4FourHumpCamel


I am crazy about "Hobix":hobix
and "it's":hobix "all":hobix I ever
"link to":hobix!

[hobix]http://hobix.com

# A first item
# A second item
# A third

# Fuel could be:
## Coal
## Gasoline
## Electricity
# Humans need only:
## Water
## Protein

* A first item
* A second item
* A third

* Fuel could be:
** Coal
** Gasoline
** Electricity
* Humans need only:
** Water
** Protein

| name | age | sex |
| joan | 24 | f |
| archie | 29 | m |
| bella | 45 | f |

|_. name |_. age |_. sex |
| joan | 24 | f |
| archie | 29 | m |
| bella | 45 | f |

|_. attribute list |
|<. align left |
|>. align right|
|=. center |
|<>. justify this block |
|^. valign top |
|~. bottom |

|\2. spans two cols |
| col 1 | col 2 |

|/3. spans 3 rows | a |
| b |
| c |

|{background:#ddd}. Grey cell|

table{border:1px solid black}.
|This|is|a|row|
|This|is|a|row|

|This|is|a|row|
{background:#ddd}. |This|is|grey|row|

p<. align left

p>. align right

p=. centered

p<>. justified

p(. left ident 1em

p((. left ident 2em

p))). right ident 3em

h2()>. Bingo.

h3()>[no]{color:red}. Bingo

<pre>
<code>
a.gsub!( /</, '' )
</code>
</pre>


<div style='float:right;'>
float right
</div>

<div style='float:right;'>

h3. Sidebar

"Hobix":http://hobix.com/
"Ruby":http://ruby-lang.org/

</div>

The main text of the
page goes here and will
stay to the left of the
sidebar.

!http://hobix.com/sample.jpg!

!http://hobix.com/sa.jpg(Bunny.)!

!http://hobix.com/sample.jpg!:http://hobix.com/

!>http://hobix.com/sample.jpg!

And others sat all round the small
machine and paid it to sing to them.

We use CSS(Cascading Style Sheets).


"""


  def tryit = (for (res <- parse(example, Some(DefaultRewriter("/foo")))) yield {
      // res.performOnWikiAnchor{a => a.rootUrl = "/foo/"}
      res.toHtml
    }) getOrElse ""


  case class DefaultRewriter(base: String) extends RewriteFunc {
    def apply(in: WikiURLInfo) = in match {
      case WikiURLInfo(word, Some(cat)) =>
        (base+"/"+urlEncode(cat)+"/"+urlEncode(word), Text(word), None)
      case WikiURLInfo(word, _) =>
        (base+"/"+urlEncode(word), Text(word), None)
    }

    def urlEncode(in : String) = _root_.java.net.URLEncoder.encode(in, "UTF-8")
  }
}

case class DefaultRewriter(base: String) extends TextileParser.RewriteFunc {
  import TextileParser._

  def apply(in: WikiURLInfo) = in match {
    case WikiURLInfo(word, Some(cat)) =>
      (base+"/"+urlEncode(cat)+"/"+urlEncode(word), Text(word), None)
    case WikiURLInfo(word, _) =>
      (base+"/"+urlEncode(word), Text(word), None)
  }

  def urlEncode(in : String) = _root_.java.net.URLEncoder.encode(in, "UTF-8")
}