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

**
** FType is the read/write fcode representation of sys::Type.
**
class FType : CType
{

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

  new make(FPod fpod)
  {
    this.fpod = fpod
    this.fattrs = FAttr[,]
  }

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

  override CNamespace ns() { fpod.ns }
  override FPod pod() { fpod }
  override once Str name() { fpod.n(fpod.typeRef(self).typeName) }
  override once Str qname() { "${fpod.name}::${name}" }
  override Str signature() { qname }

  FAttr? attr(Str name)
  {
    fattrs.find |a| { fpod.n(a.name) == name }
  }

  override CType? base
  {
    get
    {
      if (&base == null) &base = fpod.toType(fbase)
      return &base
    }
  }

  override once CType[] mixins()
  {
    return fpod.resolveTypes(fmixins)
  }

  override once Bool isVal()
  {
    return isValType(qname)
  }

  override CFacet? facet(Str qname)
  {
    if (ffacets == null) reflect
    return ffacets.find |f| { f.qname == qname }
  }

  override Str:CSlot slots()
  {
    if (slotsCached == null) reflect
    return slotsCached
  }
  private [Str:CSlot]? slotsCached

  override once COperators operators() { COperators(this) }

  override Bool isNullable() { false }

  override once CType toNullable() { NullableType(this) }

  override Bool isGeneric()
  {
    return fpod.name == "sys" && (name == "List" || name == "Map" || name == "Func")
  }

  override Bool isParameterized() { false }

  override Bool isGenericParameter()
  {
    return fpod.name == "sys" && name.size == 1
  }

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

//////////////////////////////////////////////////////////////////////////
// Reflection
//////////////////////////////////////////////////////////////////////////

  private Void reflect()
  {
    // lazy read from the pod file
    read

    // map all the declared fields and methods
    slotsCached = Str:CSlot[:]
    ffields.each  |FField f|  { slots[f.name] = f }
    fmethods.each |FMethod m|
    {
      f := (FField?)slots[m.name]
      if (f != null)
      {
        // if already mapped to field must be getter/setter
        if (m.flags.and(FConst.Getter) != 0)
          f.getter = m
        else if (m.flags.and(FConst.Setter) != 0)
          f.setter = m
        else
          throw Err("Conflicting slots: $f and $m")
      }
      else
      {
        slotsCached[m.name] = m
      }
    }

    // inherited slots
    if (base != null) inherit(base)
    mixins.each |CType t| { inherit(t) }
  }

  private Void inherit(CType t)
  {
    t.slots.each |CSlot newSlot|
    {
      // if slot already mapped, skip it
      if (slotsCached[newSlot.name] != null) return

      // we never inherit constructors, private slots,
      // or internal slots outside of the pod
      if (newSlot.isCtor || newSlot.isPrivate ||
          (newSlot.isInternal && newSlot.parent.pod != t.pod))
        return

      // inherit it
      slotsCached[newSlot.name] = newSlot
    }
  }

//////////////////////////////////////////////////////////////////////////
// Meta IO
//////////////////////////////////////////////////////////////////////////

  Void writeMeta(OutStream out)
  {
    out.writeI2(self)
    out.writeI2(fbase)
    out.writeI2(fmixins.size)
    fmixins.each |Int m| { out.writeI2(m) }
    out.writeI4(flags.and(FConst.FlagsMask))
  }

  This readMeta(InStream in)
  {
    self    = in.readU2
    fbase   = in.readU2
    fmixins = Int[,]
    in.readU2.times { fmixins.add(in.readU2) }
    flags   = in.readU4
    return this
  }

//////////////////////////////////////////////////////////////////////////
// Body IO
//////////////////////////////////////////////////////////////////////////

  Uri uri()
  {
    return Uri.fromStr("/fcode/" + fpod.n(fpod.typeRef(self).typeName) + ".fcode")
  }

  Void write()
  {
    out := fpod.out(uri)

    out.writeI2(ffields.size)
    ffields.each |FField f| { f.write(out) }

    out.writeI2(fmethods.size)
    fmethods.each |FMethod m| { m.write(out) }

    out.writeI2(fattrs.size)
    fattrs.each |FAttr a| { a.write(out) }

    out.close
  }

  Void read()
  {
    in := fpod.in(uri)

    ffields = FField[,]
    in.readU2.times { ffields.add(FField(this).read(in)) }

    fmethods = FMethod[,]
    in.readU2.times { fmethods.add(FMethod(this).read(in)) }

    fattrs = FAttr[,]
    in.readU2.times { fattrs.add(FAttr.make.read(in)) }

    ffacets = FFacet.decode(fpod, attr(FConst.FacetsAttr))

    in.close
  }

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

  override Int flags    // bitmask
  Bool hollow := true   // have we only read meta-data
  FPod fpod             // parent pod
  Int self              // self typeRef index
  Int fbase             // base typeRef index
  Int[]? fmixins        // mixin typeRef indexes
  FField[]? ffields     // fields
  FMethod[]? fmethods   // methods
  FAttr[]? fattrs       // type attributes
  FFacet[]? ffacets     // decoded facet attributes

}