//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   26 Dec 05  Brian Frank  Creation
//   19 Aug 06  Brian Frank  Ported from Java to Fan
//

**
** FPrinter is used to pretty print fcode
**
class FPrinter : FConst
{

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

  new make(FPod pod, OutStream out := Env.cur.out)
  {
    this.pod = pod
    this.out = out
  }

//////////////////////////////////////////////////////////////////////////
// Dump
//////////////////////////////////////////////////////////////////////////

  Void all()
  {
    tables
    ftypes
    out.flush
  }

//////////////////////////////////////////////////////////////////////////
// Const Tables
//////////////////////////////////////////////////////////////////////////

  Void tables()
  {
    printLine("##### Tables #####");
    table("--- names ---",      pod.names)
    table("--- typeRefs ---",   pod.typeRefs)
    table("--- fieldRefs ---",  pod.fieldRefs)
    table("--- methodRefs ---", pod.methodRefs)
    table("--- ints ---",       pod.ints)
    table("--- floats ---",     pod.floats)
    table("--- strs ---",       pod.strs)
    table("--- durations ---",  pod.durations)
    table("--- uris ---",       pod.uris)
    out.flush
  }

  Void table(Str title, FTable table)
  {
    printLine(title)
    table.table.each |Obj obj, Int index|
    {
      m := obj.typeof.method("format", false)
      s := m != null ? m.callList([obj, pod]) : obj.toStr
      printLine("  [$index]  $s")
    }
  }

//////////////////////////////////////////////////////////////////////////
// Types
//////////////////////////////////////////////////////////////////////////

  Void ftypes()
  {
    printLine("##### Types #####")
    pod.ftypes.each |FType t| { ftype(t) }
    out.flush
  }

  Void ftype(FType t)
  {
    printLine("--" + typeRef(t.self) + " : " + typeRef(t.fbase) + "--")
    if (!t.fmixins.isEmpty)
    {
      printLine("  mixin " + t.fmixins.join(", ") |Int m->Str| { typeRef(m) });
    }
    attrs(t.fattrs)
    printLine
    t.ffields.each |FField f| { field(f) }
    t.fmethods.each |FMethod m| { method(m) }
    out.flush
  }

  Void slot(FSlot s)
  {
    if (s is FField)
      field((FField)s)
    else
      method((FMethod)s)
    out.flush
  }

  Void field(FField f)
  {
    printLine("  " + name(f.nameIndex) + " -> " + typeRef(f.typeRef) + " [" + flags(f.flags) + "]")
    attrs(f.fattrs)
    printLine
  }

  Void method(FMethod m)
  {
    print("  " + name(m.nameIndex) + " (")
    print(m.fparams.join(", ") |FMethodVar p->Str| { typeRef(p.typeRef) + " " + name(p.nameIndex) })
    print(") -> " + typeRef(m.ret))
    if (m.ret != m.inheritedRet) print(" {" + typeRef(m.inheritedRet) + "}")
    printLine(" [" + flags(m.flags) + "]")
    m.vars.each |FMethodVar v, Int reg|
    {
      role := v.isParam ?  "Param" : "Local"
      if (m.flags.and(FConst.Static) == 0) reg++
      printLine("    [" + role + " " + reg + "] " + pod.n(v.nameIndex) + " -> " + typeRef(v.typeRef))
      if (v.def != null) code(v.def)
    }
    if (m.code != null)
    {
      printLine("    [Code]")
      code(m.code)
    }
    attrs(m.fattrs)
    printLine
  }

  Void code(Buf code)
  {
    if (!showCode) return;
    out.flush
    codePrinter := FCodePrinter(pod, out)
    codePrinter.showIndex = showIndex
    codePrinter.code(code)
  }

//////////////////////////////////////////////////////////////////////////
// Attributes
//////////////////////////////////////////////////////////////////////////

  Void attrs(FAttr[]? attrs)
  {
    if (attrs == null) return
    attrs.each |FAttr a| { attr(a) }
  }

  Void attr(FAttr attr)
  {
    name := name(attr.name)
    if (name == LineNumbersAttr && !showLines) return
    printLine("    [$name] size=$attr.data.size")
    if (name == SourceFileAttr)  sourceFileAttr(attr)
    if (name == ErrTableAttr)    errTableAttr(attr)
    if (name == LineNumberAttr)  lineNumberAttr(attr)
    if (name == LineNumbersAttr) lineNumbersAttr(attr)
    if (name == FacetsAttr)      facetsAttr(attr)
    if (name == EnumOrdinalAttr) enumOrdinalAttr(attr)
  }

  Void sourceFileAttr(FAttr attr)
  {
    printLine("       $attr.utf")
  }

 Void lineNumberAttr(FAttr attr)
  {
    printLine("       $attr.u2")
  }

  Void facetsAttr(FAttr attr)
  {
    buf := attr.data
    buf.seek(0)
    buf.readU2.times
    {
      type := pod.typeRef(buf.readU2)
      val  := buf.readUtf
      printLine("       @" + (val.isEmpty ? type : val))
    }
  }

 Void enumOrdinalAttr(FAttr attr)
  {
    printLine("       $attr.u2")
  }

  Void errTableAttr(FAttr attr)
  {
    buf := attr.data
    buf.seek(0)
    buf.readU2.times
    {
      start   := buf.readU2
      end     := buf.readU2
      handler := buf.readU2
      tr      := buf.readU2
      printLine("      $start  to $end -> $handler  " + typeRef(tr))
    }
  }

  Void lineNumbersAttr(FAttr attr)
  {
    buf := attr.data
    buf.seek(0)
    buf.readU2.times
    {
      pc   := buf.readU2
      line := buf.readU2
      printLine("       $pc: $line")
    }
  }

//////////////////////////////////////////////////////////////////////////
// Dump Utils
//////////////////////////////////////////////////////////////////////////

  Str typeRef(Int i)
  {
    if (i == 65535) return "null"
    return pod.typeRefStr(i) + index(i)
  }

  Str name(Int i)
  {
    return pod.n(i) + index(i)
  }

  Str flags(Int flags)
  {
    s := StrBuf.make
    if (flags.and(FConst.Abstract)  != 0) s.add("abstract ")
    if (flags.and(FConst.Const)     != 0) s.add("const ")
    if (flags.and(FConst.Ctor)      != 0) s.add("ctor ")
    if (flags.and(FConst.Enum)      != 0) s.add("enum ")
    if (flags.and(FConst.Final)     != 0) s.add("final ")
    if (flags.and(FConst.Getter)    != 0) s.add("getter ")
    if (flags.and(FConst.Internal)  != 0) s.add("internal ")
    if (flags.and(FConst.Mixin)     != 0) s.add("mixin ")
    if (flags.and(FConst.Native)    != 0) s.add("native ")
    if (flags.and(FConst.Override)  != 0) s.add("override ")
    if (flags.and(FConst.Private)   != 0) s.add("private ")
    if (flags.and(FConst.Protected) != 0) s.add("protected ")
    if (flags.and(FConst.Public)    != 0) s.add("public ")
    if (flags.and(FConst.Setter)    != 0) s.add("setter ")
    if (flags.and(FConst.Static)    != 0) s.add("static ")
    if (flags.and(FConst.Storage)   != 0) s.add("storage ")
    if (flags.and(FConst.Synthetic) != 0) s.add("synthetic ")
    if (flags.and(FConst.Virtual)   != 0) s.add("virtual ")
    if (flags.and(FConst.Once)      != 0) s.add("once ")
    return s.toStr[0..-2]
  }

  Str index(Int index)
  {
    if (showIndex) return "[" + index + "]"
    return ""
  }

//////////////////////////////////////////////////////////////////////////
// Print
//////////////////////////////////////////////////////////////////////////

  FPrinter print(Obj obj) { out.print(obj); return this }
  FPrinter printLine(Obj obj := "") { out.printLine(obj); return this }

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

  FPod pod
  OutStream out
  Bool showIndex := false
  Bool showCode  := true
  Bool showLines := false

}