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

**
** ApiDocWriter is used to write out an AST definition in
** the Fantom API doc formatted used by compilerDoc.
** See 'compilerDoc::ApiDocParser' for formal definition.
**
class ApiDocWriter
{
  new make(OutStream out) { this.out = out }

  Bool close() { out.close }

//////////////////////////////////////////////////////////////////////////
// Type
//////////////////////////////////////////////////////////////////////////

  This writeType(TypeDef t)
  {
    // name
    w(typePrefix).w(t.name).w("\n")

    // attributes
    writeAttr("base",   encodeBase(t))
    writeAttr("mixins", encodeMixins(t))
    writeAttr("flags",  encodeFlags(t.flags))
    writeAttr("loc",    encodeLoc(t, true))

    // facets
    writeFacets(t.facets)

    // doc
    writeDoc(t)

    // slots
    t.slotDefs.each |slot|
    {
      if (slot.isDocumented) writeSlot(slot)
    }
    return this
  }

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

  private Void writeSlot(SlotDef s)
  {
    // slot or method specific
    if (s is FieldDef)
      writeFieldStart(s)
    else
      writeMethodStart(s)

    // common attributes
    writeAttr("flags", encodeFlags(s.flags))
    writeAttr("loc",   encodeLoc(s, false))

    // facets
    writeFacets(s.facets)

    // doc
    writeDoc(s)
  }

  private Void writeFieldStart(FieldDef f)
  {
    w(slotPrefix).w(f.name).w(" ").w(f.fieldType.signature)
    if (f.init != null) w(":=").w(encodeExpr(f.init))
    w("\n")

    if (f.setter != null && f.flags.and(protectionMask) != f.setter.flags.and(protectionMask))
      writeAttr("set", encodeFlags(f.setter.flags.and(protectionMask)))
  }

  private Void writeMethodStart(MethodDef m)
  {
    w(slotPrefix).w(m.name).w("(\n")
    m.paramDefs.each |p, i|
    {
      w(p.name).w(" ").w(p.paramType.signature)
      if (p.def != null) w(":=").w(encodeExpr(p.def))
      w("\n")
    }
    w(") ").w(m.returnType.signature).w("\n")
  }

//////////////////////////////////////////////////////////////////////////
// Write Utils
//////////////////////////////////////////////////////////////////////////

  private Void writeAttr(Str name, Obj? val)
  {
    if (val == null) return
    w(name).w("=").w(val.toStr).w("\n")
  }

  private Void writeFacets(FacetDef[]? facets)
  {
    if (facets == null) return
    facets.each |facet| { writeFacet(facet) }
  }

  private Void writeFacet(FacetDef facet)
  {
    w("@").w(facet.type.signature)
    if (!facet.names.isEmpty)
    {
      w("{\n")
      facet.names.each |name, i|
      {
        w(name).w("=").w(encodeExpr(facet.vals[i]))
        w("\n")
      }
      w("}")
    }
    w("\n")
  }

  private Void writeDoc(DefNode node)
  {
    w("\n")
    if (node.doc == null) return
    node.docDef.lines.each |line|
    {
      if (line.startsWith(slotPrefix)) w(slotPrefix[0..0])
      w(line).w("\n")
    }
    w("\n")
  }

  This w(Str x) { out.print(x); return this }

//////////////////////////////////////////////////////////////////////////
// Str Encodings
//////////////////////////////////////////////////////////////////////////

  private Str? encodeBase(TypeDef t)
  {
    if (t.isMixin) return null
    s := StrBuf()
    b := t.base
    while (b != null)
    {
      s.join(b.signature)
      b = b.base
    }
    return s.isEmpty ? null : s.toStr
  }

  private Str? encodeMixins(TypeDef t)
  {
    if (t.mixins.isEmpty) return null
    s := StrBuf()
    t.mixins.each |m|
    {
      if (!m.isNoDoc) s.join(m.signature)
    }
    return s.isEmpty ? null : s.toStr
  }

  private Str? encodeLoc(DefNode n, Bool includeFile)
  {
    s := StrBuf()
    if (includeFile) s.add(n.loc.filename)
    if (n.loc.line != null)
    {
      s.add(":").add(n.loc.line)
      if (n.docDef != null && n.docDef.loc.line != null)
        s.add("/").add(n.docDef.loc.line)
    }
    return s.isEmpty ? null : s.toStr
  }

  private static Str encodeExpr(Expr expr)
  {
    // this string must never have a newline since that
    // is how we determine end of expressions in parser
    expr.toDocStr ?: "..."
  }

  private static Str encodeFlags(Int flags)
  {
    s := StrBuf()
    if (flags.and(FConst.Abstract)  != 0) s.join("abstract")
    if (flags.and(FConst.Const)     != 0) s.join("const")
    if (flags.and(FConst.Enum)      != 0) s.join("enum")
    if (flags.and(FConst.Facet)     != 0) s.join("facet")
    if (flags.and(FConst.Final)     != 0) s.join("final")
    if (flags.and(FConst.Internal)  != 0) s.join("internal")
    if (flags.and(FConst.Mixin)     != 0) s.join("mixin")
    if (flags.and(FConst.Native)    != 0) s.join("native")
    if (flags.and(FConst.Override)  != 0) s.join("override")
    if (flags.and(FConst.Private)   != 0) s.join("private")
    if (flags.and(FConst.Protected) != 0) s.join("protected")
    if (flags.and(FConst.Public)    != 0) s.join("public")
    if (flags.and(FConst.Static)    != 0) s.join("static")
    if (flags.and(FConst.Synthetic) != 0) s.join("synthetic")
    if (flags.and(FConst.Virtual)   != 0) s.join("virtual")
    if (flags.and(FConst.Ctor)      != 0) s.join("new")
    if (flags.and(FConst.Once)      != 0) s.join("once")
    return s.toStr
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  private const static Str typePrefix := "== "
  private const static Str slotPrefix := "-- "
  private const static Int protectionMask := (FConst.Public).or(FConst.Protected).or(FConst.Private).or(FConst.Internal)

  OutStream out
}