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

**
** JsType
**
class JsType : JsNode
{
  new make(JsCompilerSupport s, TypeDef def) : super(s)
  {
    this.def         = def
    this.base        = JsTypeRef(s, def.base, def.loc)
    this.qname       = qnameToJs(def)
    this.pod         = def.pod.name
    this.name        = def.name
    this.sig         = def.signature
    this.flags       = def.flags
    this.peer        = findPeer(s, def)
    this.isNative    = def.isNative
    this.hasNatives  = null != def.slots.find |n| { n.isNative && n.parent.qname == def.qname }
    this.isMixin     = def.isMixin
    this.isSynthetic = def.isSynthetic
    this.facets      = def.facets?.map |f| { JsFacet(s, f) } ?: [,]
    this.mixins      = def.mixins.map |r| { JsTypeRef(s, r, def.loc) }
    this.fields      = def.fieldDefs.map |f| { JsField(s, f) }
    if (def.staticInit != null) this.staticInit = def.staticInit.name

    this.methods = JsMethod[,]
    def.methodDefs.each |m|
    {
      if (m.isInstanceInit) instanceInit = JsBlock(s, m.code)
      else this.methods.add(JsMethod(s, m))
    }
  }

  override TypeDef? node() { super.node }

  static JsTypeRef? findPeer(JsCompilerSupport cs, CType def)
  {
    CType? t := def
    while (t != null)
    {
      slot := t.slots.find |s| { s.isNative && s.parent.qname == t.qname }
      if (slot != null)
        return JsTypeRef(cs, slot.parent, def is Node ? ((Node)def).loc : null)
      t = t.base
    }
    return null
  }

  override Void write(JsWriter out)
  {
    loc := def.loc

    // class/mixin
    if (isMixin) out.w("${qname} = function() {}", loc).nl
    else out.w("${qname} = fan.sys.Obj.\$extend($base.qname);", loc).nl
    mixins.each |m| { copyMixin(m, out) }

    // ctor
    out.w("${qname}.prototype.\$ctor = function()", loc).nl
    out.w("{").nl
    out.indent
    out.w("${base.qname}.prototype.\$ctor.call(this);", loc).nl
    if (peer != null) out.w("this.peer = new ${peer.qname}Peer(this);", loc).nl
    out.w("var \$this = this;", loc).nl
    instanceInit?.write(out)
    out.unindent
    out.w("}").nl

    // type
    if (!isSynthetic)
      out.w("${qname}.prototype.\$typeof = function() { return ${qname}.\$type; }", loc).nl

    // slots
    methods.each |m| { m.write(out) }
    fields.each |f| { f.write(out) }
  }

  // see JsPod.write
  Void writeStatic(JsWriter out)
  {
    // static inits
    if (staticInit != null)
      out.w("${qname}.static\$init();").nl
  }

  Void copyMixin(JsTypeRef ref, JsWriter out)
  {
    ref.slots.each |s|
    {
      if (s.parent == "fan.sys.Obj") return
      if (s.isAbstract) return
      if (s.isStatic) return

      if (!s.isPrivate)
      {
        // check if this mixin's slot was resolved by the compiler as the implementation
        // for the corresponding slot on the this JsType
        resolved := def.slots.find { it.qname == s.cslot.qname }
        if (resolved == null) return
      }

      // use mixin implementation
      out.w("${qname}.prototype.${s.name} = ${s.parent}.prototype.${s.name};").nl
    }
  }

  override Str toStr() { sig }

  static Bool checkJsSafety(CType ctype, JsCompilerSupport cs, Loc? loc)
  {
    if (ctype is TypeRef)           return checkJsSafety(ctype->t, cs, loc)
    else if (ctype is NullableType) return checkJsSafety(ctype->root, cs, loc)
    else if (ctype is ListType)     return checkJsSafety(ctype->v, cs, loc)
    else if (ctype is MapType)
    {
      return checkJsSafety(ctype->k, cs, loc) && checkJsSafety(ctype->v, cs, loc)
    }
    else if (ctype is FuncType)
    {
      safe := true
      ft := (FuncType)ctype
      ft.params.each |param| { safe = safe && checkJsSafety(param, cs, loc) }
      safe = safe && checkJsSafety(ft.ret, cs, loc)
      return safe
    }
    else if (!(ctype.pod.name == "sys" || ctype.isSynthetic || ctype.facet("sys::Js") != null || cs.forceJs))
    {
      cs.warn("Type '$ctype.qname' not available in Js", loc)
      return false
    }
    return true
  }

  TypeDef def            // compiler TypeDef
  JsTypeRef base         // base type qname
  Str qname              // type qname
  Str pod                // pod name for type
  Str name               // simple type name
  Str sig                // full type signature
  Int flags              // flags
  Bool isMixin           // is this type a mixin
  Bool isSynthetic       // is type synthetic
  JsTypeRef? peer        // peer type if has one
  Bool isNative          // is this a full native class
  Bool hasNatives        // does type have any native slots directly
  JsFacet[] facets       // facets for this type
  JsTypeRef[] mixins     // mixins for this type
  JsMethod[] methods     // methods
  JsField[] fields       // fields
  JsBlock? instanceInit  // instanceInit block
  Str? staticInit        // name of static initializer if has one - see JsPod
}

**************************************************************************
** JsTypeRef
**************************************************************************

**
** JsTypeRef
**
class JsTypeRef : JsNode
{
  static new make(JsCompilerSupport cs, CType ref, Loc loc)
  {
    key := ref.signature
    js  := cs.typeRef[key]
    if (js == null) cs.typeRef[key] = js = JsTypeRef.makePriv(cs, ref, loc)
    return js
  }

  private new makePriv(JsCompilerSupport cs, CType ref, Loc? loc) : super.make(cs)
  {
    try
    {

    this.qname = qnameToJs(ref)
    this.pod   = ref.pod.name
    this.name  = ref.name
    this.sig   = ref.signature
    this.slots = ref.slots.vals.map |CSlot s->JsSlotRef| { JsSlotRef(cs, s) }
    this.isSynthetic = ref.isSynthetic
    this.isNullable  = ref.isNullable
    this.isList = ref.isList
    this.isMap  = ref.isMap
    this.isFunc = ref.isFunc
    this.isForeign = ref.isForeign
    this.loc = loc

    deref := ref.deref
    if (deref is ListType) v = JsTypeRef.make(cs, deref->v, loc)
    if (deref is MapType)
    {
      k = JsTypeRef.make(cs, deref->k, loc)
      v = JsTypeRef.make(cs, deref->v, loc)
    }

    JsType.checkJsSafety(ref, cs, loc)
    }
    catch (Err err)
    {
      throw Err("JsTypeRef: ${ref} [${loc}]", err)
    }

  }



  override Void write(JsWriter out)
  {
    out.w(qname, loc)
  }

  override Str toStr() { sig }

  Str qname          // qname of type ref
  Str pod            // pod name for type
  Str name           // simple type name
  Str sig            // full type signature
  JsSlotRef[] slots  // slots
  Bool isSynthetic   // is type synthetic
  Bool isNullable    // is type nullable
  Bool isList        // is type a sys::List
  Bool isMap         // is type a sys::Map
  Bool isFunc        // is type a sys::Func
  Bool isForeign     // is type foreign

  JsTypeRef? k       // only valid for MapType
  JsTypeRef? v       // only valid for ListType, MapType
}