//
// Copyright (c) 2025, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   12 May 2025  Brian Frank  Creation
//

using compiler

**
** Base class for Java transpiler print
**
internal class JavaPrinter : CodePrinter
{

//////////////////////////////////////////////////////////////////////////
// Constructors
//////////////////////////////////////////////////////////////////////////

  new makeTop(OutStream out)
  {
    this.mRef = JavaPrinterState(out)
  }

  new make(JavaPrinter parent)
  {
    this.mRef = parent.mRef
  }

//////////////////////////////////////////////////////////////////////////
// Choke point for qualified names
//////////////////////////////////////////////////////////////////////////

  This qnOpUtil() { w("OpUtil") }

  This qnFanObj() { w("FanObj") }

  This qnFanVal(CType t)
  {
    // decimal rare, always use full qname
    if (t.isDecimal) return w("fan.sys.FanDecimal")
    // FanStr, FanInt, FanBool, etc
    return w("Fan").w(t.name)
  }

  This qnList() { w("List") }

  This qnMap() { w("Map") }

  This qnType() { w("Type") }

  This qnFunc() { w("Func") }

  This qnSys() { w("Sys") }

//////////////////////////////////////////////////////////////////////////
// Identifier Handling
//////////////////////////////////////////////////////////////////////////

  This typeName(CType t) { w(JavaUtil.typeName(t)) }

  This fieldName(CField x) { w(JavaUtil.fieldName(x)) }

  This methodName(CMethod x) { w(JavaUtil.methodName(x)) }

  This varName(Str x) { w(JavaUtil.varName(x)) }

//////////////////////////////////////////////////////////////////////////
// Type Signatures
//////////////////////////////////////////////////////////////////////////

  This typeSig(CType t, JavaParameterize p := JavaParameterize.yes)
  {
    // speical handling for system types
    if (t.pod.name == "sys")
    {
      base := t.toNonNullable
      if (t.isVoid)      return w("void")
      if (t.isObj)       return w("Object")
      if (t.isStr)       return w("String")
      if (t.isBool)      return t.isNullable ? w("Boolean") : w("boolean")
      if (t.isInt)       return t.isNullable ? w("Long") : w("long")
      if (t.isFloat)     return t.isNullable ? w("Double") : w("double")
      if (t.isDecimal)   return w("java.math.BigDecimal")
      if (t.isNum)       return w("java.lang.Number")
      if (t.isType)      return qnType
      if (t.isFunc)      return qnFunc
      if (t.isThis)      return typeSig(curType)
      if (base is ListType) return listSig(base, p)
      if (base is MapType)  return mapSig(base, p)
      if (base.isGenericParameter)
      {
        if (t.name == "L") return qnList.w("<V>")
        if (t.name == "M") return qnMap.w("<K,V>")
        return w(t.name)
      }
    }

    // assume synthetics are my own inner classes
    if (t.isSynthetic)
    {
      if (JavaUtil.isSyntheticWrapper(t))
      {
        // keep track of synthetic wrappers used by parent type
        name := JavaUtil.syntheticWrapperName(t)
        wrappers[name] = t
        return w(name)
      }
      else
      {
        // closure synthetic
        name := JavaUtil.syntheticClosureName(t)
        return w(name)
      }
    }

    // Java FFI
    if (t.isForeign)
    {
      pod := t.pod.name
      if (!pod.startsWith("[java]")) throw Err("Invalid FFI type: $t")
      return w(pod[6..-1]).w(".").w(t.name)
    }

    // qname
    return w("fan.").w(t.pod.name).w(".").typeName(t)
  }

  This typeSigNullable(CType t, JavaParameterize p := JavaParameterize.yes)
  {
    if (t.isVal)
    {
      if (t.isBool)    return w("Boolean")
      if (t.isInt)     return w("Long")
      if (t.isFloat)   return w("Double")
    }
    return typeSig(t, p)
  }

  private This listSig(ListType t, JavaParameterize p)
  {
    qnList
    if (isParameterizeSig(p))
      w("<").parameterSig(t.v, p).w(">")
    return this
  }

  private This mapSig(MapType t, JavaParameterize p)
  {
    qnMap
    if (isParameterizeSig(p))
      w("<").parameterSig(t.k, p).w(",").parameterSig(t.v, p).w(">")
    return this
  }

  private Bool isParameterizeSig(JavaParameterize mode)
  {
    if (mode.isNo) return false
    if (closure != null && curMethod?.name == "callList") return false
    return true
  }

  private This parameterSig(CType t, JavaParameterize p)
  {
    typeSigNullable(t)
  }

//////////////////////////////////////////////////////////////////////////
// JavaDoc
//////////////////////////////////////////////////////////////////////////

  This doc(DefNode node)
  {
    if (node.isNoDoc) return w("/** NoDoc */").nl
    if (node.doc == null) return this

    lines := toJavadoc(node.doc.text)

    w("/**").nl
    lines.each |line| { w(" * ").w(line).nl }
    w(" */").nl
    return this
  }

  static Str[] toJavadoc(Str fandoc)
  {
    lines := fandoc.splitLines
    return lines.map |line| { escapeJavadoc(line) }
  }

  static Str escapeJavadoc(Str line)
  {
    line.replace("/*", "/ *")
        .replace("*/", "* /")
        .replace("\\uxxxx", "\\u1234")
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
  }

//////////////////////////////////////////////////////////////////////////
// Misc Utils
//////////////////////////////////////////////////////////////////////////

  This slotScope(SlotDef x)
  {
    if (x.isPublic) return w("public ")
    if (x.isProtected) return w("public ")
    if (x.isPrivate && !x.parent.isMixin) return w("private ")
    return this
  }

  This str(Obj x)
  {
    w(x.toStr.toCode.replace("\\\$", "\$"))
  }

  This eos() { w(";").nl }

  Void warn(Str msg, Loc loc)
  {
    echo("WARN: $msg [$loc.toLocStr]")
  }

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

  override JavaPrinterState m() { mRef }
  private JavaPrinterState mRef

  TypeDef? curType() { m.curType }

  MethodDef? curMethod() { m.curMethod }

  TypeDef? closure() { m.closure }

  Str:TypeDef wrappers() { m.wrappers }

  Str? selfVar() { m.selfVar }
}

**************************************************************************
** JavaParameterize
**************************************************************************

enum class JavaParameterize
{
  no,
  yes

  Bool isNo() { this === no }
  Bool isYes() { this === yes }
}

**************************************************************************
** JavaPrinterState
**************************************************************************

class JavaPrinterState : CodePrinterState
{
  new make(OutStream out) : super(out) {}

  TypeDef? curType
  Str:TypeDef wrappers := [:]
  MethodDef? curMethod
  TypeDef? closure
  Str? selfVar
}