//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   15 Sep 05  Brian Frank  Creation
//    3 Jun 06  Brian Frank  Ported from Java to Fantom - Megan's b-day!
//

**
** TypeDef models a type definition for a class, mixin or enum
**
class TypeDef : DefNode, CType
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  new make(CNamespace ns, Loc loc, CompilationUnit unit, Str name, Int flags := 0)
    : super(loc)
  {
    this.ns          = ns
    this.pod         = unit.pod
    this.unit        = unit
    this.name        = name
    this.qname       = pod.name + "::" + name
    this.flags       = flags
    this.isVal       = CType.isValType(qname)
    this.mixins      = CType[,]
    this.enumDefs    = EnumDef[,]
    this.slotMap     = Str:CSlot[:]
    this.slotDefMap  = Str:SlotDef[:]
    this.slotDefList = SlotDef[,]
    this.closures    = ClosureExpr[,]
  }

//////////////////////////////////////////////////////////////////////////
// CType
//////////////////////////////////////////////////////////////////////////

  override Str signature() { qname }

  override Bool isNullable() { false }
  override once CType toNullable() { NullableType(this) }

  override Bool isGeneric() { false }
  override Bool isParameterized() { false }
  override Bool isGenericParameter() { false }

  override once CType toListOf() { ListType(this) }

//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////

  **
  ** Return if this type is the anonymous class of a closure
  **
  Bool isClosure()
  {
    return closure != null
  }

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

  **
  ** Return all the all slots (inherited and defined)
  **
  override Str:CSlot slots() { slotMap }

  **
  ** Cached COperators map
  **
  override once COperators operators() { COperators(this) }

  **
  ** Add a slot to the type definition.  The method is used to add
  ** SlotDefs declared by this type as well as slots inherited by
  ** this type.
  **
  Void addSlot(CSlot s, Int? slotDefIndex := null)
  {
    // if MethodDef
    m := s as MethodDef
    if (m != null)
    {
      // static initializes are just temporarily added to the
      // slotDefList but never into the name map - we just need
      // to keep them in declared order until they get collapsed
      // and removed in the Normalize step
      if (m.isStaticInit)
      {
        slotDefList.add(m)
        return
      }

      // field accessors are added only to slotDefList,
      // name lookup is always the field itself
      if (m.isFieldAccessor)
      {
        slotDefList.add(m)
        return
      }
    }

    // sanity check
    name := s.name
    if (hasSlot(name))
      throw Err("Internal error: duplicate slot $name [$loc.toLocStr]")

    // add to all slots table
    slotMap[name] = s

    // if my own SlotDef
    def := s as SlotDef
    if (def != null && def.parent === this)
    {
      // add to my slot definitions
      slotDefMap[name] = def
      if (slotDefIndex == null)
        slotDefList.add(def)
      else
        slotDefList.insert(slotDefIndex, def)

      // if non-const FieldDef, then add getter/setter methods
      if (s is FieldDef)
      {
        f := (FieldDef)s
        if (f.get != null) addSlot(f.get)
        if (f.set != null) addSlot(f.set)
      }
    }
  }

  **
  ** Replace oldSlot with newSlot in my slot tables.
  **
  Void replaceSlot(CSlot oldSlot, CSlot newSlot)
  {
    // sanity checks
    if (oldSlot.name != newSlot.name)
      throw Err("Internal error: not same names: $oldSlot != $newSlot [$loc.toLocStr]")
    if (slotMap[oldSlot.name] !== oldSlot)
      throw Err("Internal error: old slot not mapped: $oldSlot [$loc.toLocStr]")

    // remap in slotMap table
    name := oldSlot.name
    slotMap[name] = newSlot

    // if old is SlotDef
    oldDef := oldSlot as SlotDef
    if (oldDef != null && oldDef.parent === this)
    {
      slotDefMap[name] = oldDef
      slotDefList.remove(oldDef)
    }

    // if new is SlotDef
    newDef := newSlot as SlotDef
    if (newDef != null && newDef.parent === this)
    {
      slotDefMap[name] = newDef
      slotDefList.add(newDef)
    }
  }

  **
  ** Get static initializer if one is defined.
  **
  MethodDef? staticInit()
  {
    return slotDefMap["static\$init"]
  }

  **
  ** If during parse we added any static initializer methods,
  ** now is the time to remove them all and replace them with a
  ** single collapsed MethodDef (processed in Normalize step)
  **
  Void normalizeStaticInits(MethodDef m)
  {
    // remove any temps we had in slotDefList
    slotDefList = slotDefList.exclude |SlotDef s->Bool|
    {
      return MethodDef.isNameStaticInit(s.name)
    }

    // fix enclosingSlot of closures used in those temp statics
    closures.each |ClosureExpr c|
    {
      if (c.enclosingSlot is MethodDef && ((MethodDef)c.enclosingSlot).isStaticInit)
        c.enclosingSlot = m
    }

    // now we add into all slot tables
    slotMap[m.name] = m
    slotDefMap[m.name] = m
    slotDefList.add(m)
  }

//////////////////////////////////////////////////////////////////////////
// SlotDefs
//////////////////////////////////////////////////////////////////////////

  **
  ** Return if this class has a slot definition for specified name.
  **
  Bool hasSlotDef(Str name)
  {
    return slotDefMap.containsKey(name)
  }

  **
  ** Return SlotDef for specified name or null.
  **
  SlotDef? slotDef(Str name)
  {
    return slotDefMap[name]
  }

  **
  ** Return FieldDef for specified name or null.
  **
  FieldDef? fieldDef (Str name)
  {
    return (FieldDef)slotDefMap[name]
  }

  **
  ** Return MethodDef for specified name or null.
  **
  MethodDef? methodDef(Str name)
  {
    return (MethodDef)slotDefMap[name]
  }

  **
  ** Get the SlotDefs declared within this TypeDef.
  **
  SlotDef[] slotDefs()
  {
    return slotDefList
  }

  **
  ** Get the FieldDefs declared within this TypeDef.
  **
  FieldDef[] fieldDefs()
  {
    return (FieldDef[])slotDefList.findType(FieldDef#)
  }

  **
  ** Get the static FieldDefs declared within this TypeDef.
  **
  FieldDef[] staticFieldDefs()
  {
    return fieldDefs.findAll |FieldDef f->Bool| { f.isStatic }
  }

  **
  ** Get the instance FieldDefs declared within this TypeDef.
  **
  FieldDef[] instanceFieldDefs()
  {
    return fieldDefs.findAll |FieldDef f->Bool| { !f.isStatic }
  }

  **
  ** Get the MethodDefs declared within this TypeDef.
  **
  MethodDef[] methodDefs()
  {
    return (MethodDef[])slotDefList.findType(MethodDef#)
  }

  **
  ** Get the constructor MethodDefs declared within this TypeDef.
  **
  MethodDef[] ctorDefs()
  {
    return methodDefs.findAll |MethodDef m->Bool| { m.isCtor }
  }

//////////////////////////////////////////////////////////////////////////
// Enum
//////////////////////////////////////////////////////////////////////////

  **
  ** Return EnumDef for specified name or null.
  **
  public EnumDef? enumDef(Str name)
  {
    return enumDefs.find |EnumDef def->Bool| { def.name == name }
  }

//////////////////////////////////////////////////////////////////////////
// Tree
//////////////////////////////////////////////////////////////////////////

  Void walk(Visitor v, VisitDepth depth)
  {
    v.enterUnit(unit)
    v.enterTypeDef(this)
    walkFacets(v, depth)
    if (depth >= VisitDepth.slotDef)
    {
      slotDefs.each |SlotDef slot| { slot.walk(v, depth) }
    }
    v.visitTypeDef(this)
    v.exitTypeDef(this)
    v.exitUnit(unit)
  }

//////////////////////////////////////////////////////////////////////////
// Debug
//////////////////////////////////////////////////////////////////////////

  override Void print(AstWriter out)
  {
    out.nl
    printFacets(out)
    if (isMixin)
      out.w("mixin $qname").nl
    else if (isEnum)
      out.w("enum $qname").nl
    else
      out.w("class $qname").nl

    if (base != null || !mixins.isEmpty)
    {
      out.w(" : ")
      if (base != null) out.w(" $base")
      if (!mixins.isEmpty) out.w(", ").w(mixins.join(", ")).nl
    }

    out.w("{").nl
    out.indent
    enumDefs.each |EnumDef e| { e.print(out) }
    slotDefs.each |SlotDef s| { s.print(out) }
    out.unindent
    out.w("}").nl
  }

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

  override CNamespace ns           // compiler's namespace
  CompilationUnit unit             // parent unit
  override CPod pod                // parent pod
  override const Str name          // simple class name
  override const Str qname         // podName::name
  override const Bool isVal        // is this a value type (Bool, Int, etc)
  Bool baseSpecified := true       // was base assigned from source code
  override CType? base             // extends class
  override CType[] mixins          // mixin types
  EnumDef[] enumDefs               // declared enumerated pairs (only if enum)
  ClosureExpr[] closures           // closures where I am enclosing type (Parse)
  ClosureExpr? closure             // if I am a closure anonymous class
  private Str:CSlot slotMap        // all slots
  private Str:SlotDef slotDefMap   // declared slot definitions
  private SlotDef[] slotDefList    // declared slot definitions
  FacetDef[]? indexedFacets        // used by WritePod

}