//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   13 Aug 11  Brian Frank  Creation
//

using fandoc
using fandoc::Doc as FandocDoc
using web

**
** DocRenderer is base class for rendering a Doc.
** See `writeDoc` for rendering pipeline.
**
abstract class DocRenderer
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  ** All subclasses must implement ctor with env, out, doc params.
  new make(DocEnv env, WebOutStream out, Doc doc)
  {
    this.envRef = env
    this.outRef = out
    this.docRef = doc
  }

//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////

  ** Environment with access to model, theme, linking, etc
  virtual DocEnv env() { envRef }
  private DocEnv envRef

  ** HTML output stream
  virtual WebOutStream out() { outRef }
  private WebOutStream outRef

  ** Document to be renderered
  virtual Doc doc() { docRef }
  private Doc docRef

  ** Theme to use for rendering chrome and navigation.
  ** This field is initialized from `DocEnv.theme`.
  virtual DocTheme theme() { env.theme }

//////////////////////////////////////////////////////////////////////////
// Hooks
//////////////////////////////////////////////////////////////////////////

  **
  ** Render the `doc`.  This method delegates to:
  **  1. `DocTheme.writeStart`
  **  2. `DocTheme.writeBreadcrumb`
  **  3. `writeContent`
  **  3. `DocTheme.writeEnd`
  **
  virtual Void writeDoc()
  {
    theme.writeStart(this)
    theme.writeBreadcrumb(this)
    writeContent
    theme.writeEnd(this)
  }

  **
  ** Subclass hook to render document specific content.
  ** See `writeDoc` for rendering pipeline.
  **
  abstract Void writeContent()

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  **
  ** Write an '<a>' element for the given link from this renderer
  ** document to another document.  See `DocEnv.linkUri`.
  **
  virtual Void writeLink(DocLink link)
  {
    out.a(env.linkUri(link)).esc(link.dis).aEnd
  }

  **
  ** Convenience for 'writeLink(linkTo(target, dis, frag))'
  **
  virtual Void writeLinkTo(Doc target, Str? dis := null, Str? frag := null)
  {
    if (dis == null) dis = target is DocChapter ? target.title : target.docName
    writeLink(linkTo(target, dis, frag))
  }

  **
  ** Create a DocLink from this renderer doc to the target document.
  **
  DocLink linkTo(Doc target, Str? dis := null, Str? frag := null)
  {
    if (dis == null) dis = target is DocChapter ? target.title : target.docName
    return DocLink(this.doc, target, dis, frag)
  }

  **
  ** Write the given fandoc string as HTML.  This method
  ** delegates to `DocEnv.link` and `DocEnv.linkUri` to
  ** resolve links from the current document.
  **
  virtual Void writeFandoc(DocFandoc doc)
  {
    // parse fandoc
    docLoc := doc.loc
    parser := FandocParser()
    parser.silent = true
    root := parser.parse(docLoc.file, doc.text.in)

    // if no errors, then write as HTML
    if (parser.errs.isEmpty)
    {
      writer := env.initFandocHtmlWriter(out)
      writer.onLink  = |Link elem| { onFandocLink(elem, toFandocElemLoc(docLoc, elem.line)) }
      writer.onImage = |Image elem| { onFandocImage(elem, toFandocElemLoc(docLoc, elem.line)) }
      root.children.each |child| { child.write(writer) }
    }

    // otherwise report errors and print as <pre>
    else
    {
      // report each error
      parser.errs.each |err|
      {
        env.err(err.msg, toFandocElemLoc(docLoc, err.line))
      }

      // print as <pre>
      out.pre.w(doc.text).preEnd
    }
  }

  ** Map document location and element to the element location
  private DocLoc toFandocElemLoc(DocLoc docLoc, Int line)
  {
    DocLoc(docLoc.file, docLoc.line + line - 1)
  }

  ** Fandoc handling for link nodes
  @NoDoc
  virtual Void onFandocLink(Link elem, DocLoc loc)
  {
    // don't process absolute links
    orig := elem.uri
    if (orig.startsWith("http:/") ||
        orig.startsWith("https:/") ||
        orig.startsWith("ftp:/")) return

    try
    {
      // route to DocEnv.link
      link := resolveFandocLink(elem, true)

      // get environment URI for the DocLink
      elem.uri = env.linkUri(link).encode
      elem.isCode = link.target.isCode

      // extra checking
      env.linkCheck(link, loc)

      // if link text was original URI, then update with DocLink.dis
      if (elem.children.first is DocText && elem.children.first.toStr == orig)
      {
        elem.removeAll.add(DocText(link.dis))
      }
    }
    catch (Err e)
    {
      if (elem.uri.startsWith("examples::"))
        elem.uri = "https://fantom.org/doc/" + elem.uri.replace("::", "/")
      else
        onFandocErr(e, loc)
    }
  }

  ** Fandoc handling for inage nodes
  @NoDoc
  virtual Void onFandocImage(Image elem, DocLoc loc)
  {
  }

  **
  ** Hook used to map a fandoc link to a doc link
  **
  virtual DocLink? resolveFandocLink(Link elem, Bool checked := true)
  {
    env.link(this.doc, elem.uri, true)
  }

  ** Handle a fandoc linking error
  @NoDoc
  virtual Void onFandocErr(Err e, DocLoc loc)
  {
    env.err(e.toStr, loc)
  }
}