//
// Copyright (c) 2009, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   9 Jul 09  Andy Frank  Creation
//

using compiler

**
** JsMethod
**
class JsMethod : JsSlot
{
  new make(JsCompilerSupport s, MethodDef m) : super(s, m)
  {
    this.parentPeer = JsType.findPeer(s, m.parent)
    this.isInstanceCtor = m.isInstanceCtor
    this.isGetter   = m.isGetter
    this.isSetter   = m.isSetter
    this.params     = m.params.map |CParam p->JsMethodParam| { JsMethodParam(s, p) }
    this.ret        = JsTypeRef(s, m.ret, m.loc)
    this.hasClosure = ClosureFinder(m).exists
    // this.doc        = m.doc?.lines?.join("\n")
    if (m.ctorChain != null) this.ctorChain = JsExpr.makeFor(s, m.ctorChain)
    if (m.code != null) this.code = JsBlock(s, m.code)
  }

  override MethodDef? node() { super.node }

  Bool isFieldAccessor() { isGetter || isSetter }

  override Void write(JsWriter out)
  {
    if (isInstanceCtor)
    {
      // write static factory make method
      ctorParams := [JsMethodParam.makeSelf(support)].addAll(params)
      out.w("${parent}.$name = function${sig(params)} {", loc).nl
         .indent
         .w("var self = new $parent();").nl
         .w("${parent}.$name\$${sig(ctorParams)};").nl
         .w("return self;").nl
         .w("}").nl
         .unindent

      // write factory make$ method
      support.thisName = "self"
      writeMethod(out, "$name\$", ctorParams)
      support.thisName = "this"
    }
    else if (isSetter) writeMethod(out, "$name\$", params)
    else writeMethod(out, name, params)
  }

  Void writeMethod(JsWriter out, Str methName, JsMethodParam[] methParams)
  {
    // skip abstract methods
    if (isAbstract) return

    out.w(parent, loc)
    if (!isStatic && !isInstanceCtor) out.w(".prototype")
    out.w(".$methName = function${sig(methParams)}", loc).nl
    out.w("{").nl
    out.indent

    // def params
    params.each |p|
    {
      if (!p.hasDef) return
      out.w("if ($p.name === undefined) $p.name = ", p.loc)
      p.defVal.write(out)
      out.w(";").nl
    }

    // closure support
    if (hasClosure) out.w("var \$this = $support.thisName;", loc).nl

    if (isNative)
    {
      if (isStatic)
      {
        out.w("return ${parentPeer.qname}Peer.$methName${sig(methParams)};", loc).nl
      }
      else
      {
        pars := isStatic ? params : [JsMethodParam.makeThis(support)].addAll(methParams)
        out.w("return this.peer.$methName${sig(pars)};", loc).nl
      }
    }
    else
    {
      // ctor chaining
      if (ctorChain != null)
      {
        ctorChain.write(out)
        out.w(";").nl
      }

      // method body
      code?.write(out)
    }

    out.unindent
    out.w("}").nl
  }

  Str sig(JsMethodParam[] pars)
  {
    buf := StrBuf().addChar('(')
    pars.each |p,i|
    {
      if (i > 0) buf.addChar(',')
      buf.add(p.name)
    }
    buf.addChar(')')
    return buf.toStr
  }

  JsTypeRef? parentPeer   // parent peer if has one
  Bool isInstanceCtor     // is this method an instance constructor
  Bool isGetter           // is this method a field getter
  Bool isSetter           // is this method a field setter
  JsMethodParam[] params  // method params
  JsTypeRef ret           // return type for method
  Bool hasClosure         // does this method contain a closure
  JsExpr? ctorChain       // ctorChain if has one
  JsBlock? code           // method body if has one
  Str? doc                // method documentation
}

**************************************************************************
** JsMethodRef
**************************************************************************

**
** JsMethodRef
**
class JsMethodRef : JsSlotRef
{
  new make(JsCompilerSupport s, CMethod m) : super(s, m) {}
}

**************************************************************************
** JsMethodParam
**************************************************************************

**
** JsMethodParam
**
class JsMethodParam : JsNode
{
  new make(JsCompilerSupport s, CParam p) : super(s)
  {
    this.loc = p is Node ? ((Node)p).loc : null
    this.reflectName = p.name
    this.name = vnameToJs(p.name)
    this.paramType = JsTypeRef(s, p.paramType, this.loc)
    this.hasDef = p.hasDefault
    if (hasDef) this.defVal = JsExpr.makeFor(s, p->def)
  }

  new makeThis(JsCompilerSupport s) : super.make(s)
  {
    this.reflectName = this.name = "this"
  }

  new makeSelf(JsCompilerSupport s) : super.make(s)
  {
    this.reflectName = this.name = "self"
  }

  override Void write(JsWriter out)
  {
    out.w(name)
  }

  Str reflectName       // reflected parameter name
  Str name              // js code param name
  JsTypeRef? paramType  // param type
  Bool hasDef           // has default value
  JsNode? defVal        // default value
}

**************************************************************************
** ClosureFinder
**************************************************************************

internal class ClosureFinder : Visitor
{
  new make(Node node) { this.node = node }
  Bool exists()
  {
    node->walk(this, VisitDepth.expr)
    return found
  }
  override Expr visitExpr(Expr expr)
  {
    if (expr is ClosureExpr) found = true
    return Visitor.super.visitExpr(expr)
  }
  Node node
  Bool found := false
}