//
// 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 web

**
** Renders DocType documents
**
** Overview
** ========
**
**   <h1>
**    <span>{type.flags}</span> {type.qname}
**   </h1>
**   <pre>...</pre>                 // inhertiance
**   <p class='facets'>...</p>      // facet list (if available)
**   <p class='src'><a>...</a></p>  // source link (if available)
**   ...                            // type fandoc
**   <ul>...</ul>                   // emum list (if available)
**
** Slots
** =====
**
**   <dl>
**    <dt id='{slot.name}'>{slot.name}</dt>
**    <dd>
**     <p class='sig'><code>...</code></p>  // slot signature
**     <p class='src'><a>...</a></p>        // source link (if available)
**     ...                                  // slot fandoc
**    </dd>
**   </dl>
**
** Table of Contents
** ==================
**
**   <h3>Source</h3>
**   <ul><li><a>...</a></li></ul>     // if source link
**   <ul><li>Not available</li></ul>  // if no source link
**
**   <h3>Slots</h3>
**   <ul>
**    <li><a href='#{slot.name}'>{slot.name}</a></li>
**   </ul>
**
class DocTypeRenderer : DocRenderer
{

//////////////////////////////////////////////////////////////////////////
// TypeRenderer
//////////////////////////////////////////////////////////////////////////

  ** Constructor with env, out params.
  new make(DocEnv env, WebOutStream out, DocType doc)
    : super(env, out, doc)
  {
    this.type = doc
  }

  ** Type to renderer
  const DocType type

  override Void writeContent()
  {
    out.div("class='mainSidebar'")
      out.div("class='main type'")
        writeTypeOverview
        writeSlots
      out.divEnd
      out.div("class='sidebar'")
        writeToc
      out.divEnd
    out.divEnd
  }

//////////////////////////////////////////////////////////////////////////
// Overview
//////////////////////////////////////////////////////////////////////////

  **
  ** Render the HTML for the type overview (base, mixins, type doc)
  **
  virtual Void writeTypeOverview()
  {
    // type name
    out.h1
      .span.w(DocFlags.toTypeDis(type.flags)).spanEnd
      .w(" $type.qname")
      .h1End

    // inheritance
    writeTypeInheritance

    // facets
    if (type.facets.size > 0)
    {
      out.p("class='facets'")
      type.facets.each |f| { writeFacet(f); out.br }
      out.pEnd
    }

    // if source if available
    writeSrcLink(type.doc.loc)

    // fandoc
    writeFandoc(type.doc)

    // enum vals
    if (DocFlags.isEnum(type.flags))
    {
      out.ul
      type.declared.each |s|
      {
        if (DocFlags.isEnum(s.flags))
          out.li.a(`#$s.name`).esc(s.name).aEnd.liEnd
      }
      out.ulEnd
    }
  }

  ** Render type inheritance.
  virtual Void writeTypeInheritance()
  {
    out.pre
    indent := 0
    type.base.eachr |ref|
    {
      if (indent > 0) out.w("\n${Str.spaces(indent*2)}")
      writeTypeRef(ref, true)
      indent++
    }
    if (type.base.size > 0) out.w("\n${Str.spaces(indent*2)}")
    out.w("$type.qname")
    type.mixins.each |ref,i|
    {
      out.w(i==0 ? " : " : ", ")
      writeTypeRef(ref, true)
    }
    out.preEnd
  }

//////////////////////////////////////////////////////////////////////////
// Slots
//////////////////////////////////////////////////////////////////////////

  ** Render the HTML for all the slot definitions
  virtual Void writeSlots()
  {
    out.dl
    type.slots.each |slot| { writeSlot(slot) }
    out.dlEnd
  }

  ** Render the HTML for all the given slot
  virtual Void writeSlot(DocSlot slot)
  {
    out.dt("id='$slot.name'").w("$slot.name").dtEnd
    out.dd
    writeSlotSig(slot)
    writeSrcLink(slot.doc.loc)
    writeFandoc(slot.doc)
    out.ddEnd
  }

  ** Render HTML for slot signature.
  virtual Void writeSlotSig(DocSlot slot)
  {
    out.p("class='sig'").code
    slot.facets.each |f| { writeFacet(f); out.br }
    writeSlotSigText(slot)
    out.codeEnd.pEnd
  }

  ** Render slot signature inside the outer p element.
  ** This does *not* include facets, but does include signature links.
  @NoDoc Void writeSlotSigText(DocSlot slot)
  {
    if (slot is DocField)
    {
      // field sig
      field := (DocField)slot
      out.w(DocFlags.toSlotDis(field.flags)).w(" ")
      writeTypeRef(field.type)
      out.w(" ").w(field.name)
      if (field.init != null) out.w(" := ").w(field.init.toXml)

      // field setter if different protection scope
      if (field.setterFlags != null)
        out.w(" { ").w(DocFlags.toSlotDis(field.setterFlags)).w(" set }")
    }
    else
    {
      //  method sig
      method := (DocMethod)slot
      if (DocFlags.isCtor(method.flags))
      {
        if (DocFlags.isStatic(method.flags)) out.w("static ")
        out.w("new")
      }
      else
      {
        out.w(DocFlags.toSlotDis(method.flags)).w(" ")
        writeTypeRef(method.returns)
      }
      out.w(" $method.name(")
      method.params.each |param, i|
      {
        if (i > 0) out.w(", ")
        writeTypeRef(param.type)
        out.w(" $param.name")
        if (param.def != null) out.w(" := $param.def.toXml")
      }
      out.w(")")
    }
  }

//////////////////////////////////////////////////////////////////////////
// Toc
//////////////////////////////////////////////////////////////////////////

  ** Render the table of contents for this type.
  virtual Void writeToc()
  {
    // source link
    out.h3.w("Source").h3End
    out.ul.li
    srcLink := toSrcLink(type.doc.loc, "View Source")
    if (srcLink == null)
      out.w("Not available")
    else
      writeLink(srcLink)
    out.liEnd.ulEnd

    // slot list
    out.h3.w("Slots").h3End
    out.ul
    type.slots.each |slot|
    {
      out.li.a(`#$slot.name`).w(slot.name).aEnd.liEnd
    }
    out.ulEnd
  }

//////////////////////////////////////////////////////////////////////////
// Util
//////////////////////////////////////////////////////////////////////////

  ** Write the given type ref as a hyperlink
  virtual Void writeTypeRef(DocTypeRef ref, Bool full := false)
  {
    if (ref.isParameterized)
    {
      if (ref.qname == "sys::List")
      {
        writeTypeRef(ref.v)
        out.w("[]")
      }
      else if (ref.qname == "sys::Map")
      {
        if (ref.isNullable) out.w("[")
        writeTypeRef(ref.k)
        out.w(":")
        writeTypeRef(ref.v)
        if (ref.isNullable) out.w("]")
      }
      else if (ref.qname == "sys::Func")
      {
        isVoid := ref.funcReturn.qname == "sys::Void"
        out.w("|")
        ref.funcParams.each |p, i|
        {
          if (i > 0) out.w(",")
          writeTypeRef(p)
        }
        if (!isVoid || ref.funcParams.isEmpty)
        {
          out.w("->")
          writeTypeRef(ref.funcReturn)
        }
        out.w("|")
      }
      else throw Err("Unsupported parameterized type: $ref")
      if (ref.isNullable) out.w("?")
    }
    else if (ref.isGenericVar)
    {
      out.w(full ? ref.qname : ref.name)
         .w(ref.isNullable ? "?" : "")
    }
    else
    {
      // make link by hand to avoid having to resolve
      // every type to a full fledged Doc instance
      uri := StrBuf()
      if (ref.pod != type.pod.name) uri.add("../").add(ref.pod).add("/")
      uri.add(ref.name)
      uriExt := env.linkUriExt
      if (uriExt != null) uri.add(uriExt)

      out.a(uri.toStr.toUri)
         .w(full ? ref.qname : ref.name)
         .w(ref.isNullable ? "?" : "")
         .aEnd
    }
  }

  ** Write the given facet.
  virtual Void writeFacet(DocFacet f)
  {
    out.code("class='sig'")
    writeFacetText(f)
    out.codeEnd
  }

  ** Write the facet content inside the outer code element
  ** which includes links to the facet type and body values.
  @NoDoc Void writeFacetText(DocFacet f)
  {
    out.w("@")
    writeTypeRef(f.type)
    if (f.fields.size > 0)
    {
      s := f.fields.join("; ") |v,n| { "$n.toXml=$v.toXml" }
      out.w(" { $s }")
    }
  }

  ** Map filename/line number to a source file link
  DocLink? toSrcLink(DocLoc loc, Str dis)
  {
    src := type.pod.src(loc.file, false)
    if (src == null) return null
    frag := loc.line > 20 ? "line${loc.line}" : null
    return DocLink(doc, src, dis, frag)
  }

  ** Write source code link as <p> if source is available.
  virtual Void writeSrcLink(DocLoc loc, Str dis := "Source")
  {
    link := toSrcLink(loc, dis)
    if (link == null) return
    out.p("class='src'")
    writeLink(link)
    out.pEnd
  }

}