//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   26 Jul 06  Brian Frank  Creation
//

**
** GenericType models a parameterized generic type: List, Map, or Func
**
abstract class GenericType : CType
{

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

  new make(CType base)
  {
    this.base = base
  }

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

  override CNamespace ns() { base.ns }
  override CDoc? doc()     { base.doc }
  override CPod pod()      { ns.sysPod }
  override Str name()      { base.name }
  override Str qname()     { base.qname }

  override Bool isVal() { false }

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

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

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

  override CType[] mixins() { CType[,] }

  override CFacet? facet(Str qname) { base.facet(qname) }

  override once Str:CSlot slots() { parameterizeSlots }

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

//////////////////////////////////////////////////////////////////////////
// Parameterize
//////////////////////////////////////////////////////////////////////////

  private Str:CSlot parameterizeSlots()
  {
    base.slots.map |CSlot slot->CSlot| { parameterizeSlot(slot) }
  }

  private CSlot parameterizeSlot(CSlot slot)
  {
    if (slot is CMethod)
    {
      CMethod m := slot
      if (!m.isGeneric) return slot
      return ParameterizedMethod(this, m)
    }
    else
    {
      f := (CField)slot
      if (!f.fieldType.isGenericParameter) return slot
      return ParameterizedField(this, f)
    }
  }

  internal CType parameterize(CType t)
  {
    if (!t.isGenericParameter) return t
    nullable := t.isNullable
    nn := t.toNonNullable
    if (nn is ListType)
    {
      t = parameterizeListType(nn)
    }
    else if (nn is MapType)
    {
      t = parameterizeMapType(nn)
    }
    else if (nn is FuncType)
    {
      t = parameterizeFuncType(nn)
    }
    else
    {
      if (nn.name.size != 1) throw Err("Cannot parameterize $t.qname")
      t = doParameterize(nn.name[0])
    }
    return nullable ? t.toNullable : t
  }

  private CType parameterizeListType(ListType t)
  {
    return parameterize(t.v).toListOf
  }

  private CType parameterizeMapType(MapType t)
  {
    return MapType(parameterize(t.k), parameterize(t.v))
  }

  private CType parameterizeFuncType(FuncType t)
  {
    CType[] params := t.params.map |CType p->CType| { parameterize(p) }
    ret := parameterize(t.ret)
    return FuncType(params, t.names, ret)
  }

  abstract CType doParameterize(Int ch)

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

  override CType? base { private set }
}

**************************************************************************
** ListType
**************************************************************************

**
** ListType models a parameterized List type.
**
class ListType : GenericType
{
  new make(CType v)
    : super(v.ns.listType)
  {
    this.v = v
    this.signature = "${v.signature}[]"
  }

  override Bool isGenericParameter()
  {
    return v.isGenericParameter
  }

  override Int flags()
  {
    return v.isPublic ? FConst.Public : FConst.Internal
  }

  override Bool fits(CType t)
  {
    t = t.toNonNullable
    if (this == t) return true
    if (t.signature == "sys::List") return true
    if (t.isObj) return true
    if (t.name.size == 1 && t.pod.name == "sys") return true

    that := t.deref as ListType
    if (that == null) return false

    return v.fits(that.v)
  }

  override CType doParameterize(Int ch)
  {
    switch (ch)
    {
      case 'V': return v
      case 'L': return this
      default:  throw Err(ch.toChar)
    }
  }

  override Bool isValid() { v.isValid }

  CType v { private set }        // value type
  override const Str signature   // v[]

}

**************************************************************************
** MapType
**************************************************************************

**
** MapType models a parameterized Map type.
**
class MapType : GenericType
{
  new make(CType k, CType v)
    : super(k.ns.mapType)
  {
    this.k = k
    this.v = v
    this.signature = "[${k.signature}:${v.signature}]"
  }

  override Int flags()
  {
    return k.isPublic && v.isPublic ? FConst.Public : FConst.Internal
  }

  override Bool isGenericParameter()
  {
    return k.isGenericParameter || v.isGenericParameter
  }

  override Bool fits(CType t)
  {
    t = t.toNonNullable
    if (this == t) return true
    if (t.signature == "sys::Map") return true
    if (t.isObj) return true
    if (t.name.size == 1 && t.pod.name == "sys") return true

    that := t.deref as MapType
    if (that == null) return false

    return k.fits(that.k) && v.fits(that.v)
  }

  override CType doParameterize(Int ch)
  {
    switch (ch)
    {
      case 'K': return k
      case 'V': return v
      case 'M': return this
      default:  throw Err(ch.toChar)
    }
  }

  override Bool isValid() { k.isValid && v.isValid }

  CType k { private set }        // keytype
  CType v { private set }        // value type
  override const Str signature   // [k:v]

}

**************************************************************************
** FuncType
**************************************************************************

**
** FuncType models a parameterized Func type.
**
class FuncType : GenericType
{
  new make(CType[] params, Str[] names, CType ret)
    : super(ret.ns.funcType)
  {
    this.params = params
    this.names  = names
    this.ret    = ret
    this.isGenericParameter = ret.isGenericParameter

    s := StrBuf.make.add("|")
    params.each |CType p, Int i|
    {
      isGenericParameter = isGenericParameter.or(p.isGenericParameter)
      if (i > 0) s.add(","); s.add(p.signature)
    }
    s.add("->").add(ret.signature).add("|")
    this.signature = s.toStr
  }

  new makeItBlock(CType itType)
    : this.make([itType], ["it"], itType.ns.voidType)
  {
    // sanity check
    if (itType.isThis) throw Err("Invalid it-block func signature: $this")
  }

  override Int flags()
  {
    allPublic := ret.isPublic && params.all |CType p->Bool| { p.isPublic }
    return allPublic ? FConst.Public : FConst.Internal
  }

  override Bool fits(CType t)
  {
    t = t.toNonNullable
    if (this == t) return true
    if (t.signature == "sys::Func") return true
    if (t.isObj) return true
    if (t.name.size == 1 && t.pod.name == "sys") return true

    that := t.deref as FuncType
    if (that == null) return false

    // match return type (if void is needed, anything matches)
    if (!that.ret.isVoid && !ret.fits(that.ret)) return false

    // match params - it is ok for me to have less than
    // the type params (if I want to ignore them), but I
    // must have no more
    if (params.size > that.params.size) return false
    for (i:=0; i<params.size; ++i)
      if (!that.params[i].fits(params[i])) return false

    // this method works for the specified method type
    return true;
  }

  Int arity() { params.size }

  FuncType toArity(Int num)
  {
    if (num == params.size) return this
    if (num > params.size) throw Err("Cannot increase arity $this")
    return make(params[0..<num], names[0..<num], ret)
  }

  FuncType mostSpecific(FuncType b)
  {
    a := this
    if (a.arity != b.arity) throw Err("Different arities: $a / $b")
    params := a.params.map |p, i| { toMostSpecific(p, b.params[i]) }
    ret := toMostSpecific(a.ret, b.ret)
    return make(params, b.names, ret)
  }

  static CType toMostSpecific(CType a, CType b)
  {
    if (b.isGenericParameter) return a
    if (a.isObj || a.isVoid || a.isGenericParameter) return b
    return a
  }

  ParamDef[] toParamDefs(Loc loc)
  {
    p := ParamDef[,]
    p.capacity = params.size
    for (i:=0; i<params.size; ++i)
    {
      p.add(ParamDef(loc, params[i], names[i]))
    }
    return p
  }

  override CType doParameterize(Int ch)
  {
    if ('A' <= ch && ch <= 'H')
    {
      index := ch - 'A'
      if (index < params.size) return params[index]
      return ns.objType
    }

    switch (ch)
    {
      case 'R': return ret
      default:  throw Err(ch.toChar)
    }
  }

  **
  ** Return if this function type has 'This' type in its signature.
  **
  Bool usesThis()
  {
    return ret.isThis || params.any |CType p->Bool| { p.isThis }
  }

  **
  ** Replace any occurance of "sys::This" with thisType.
  **
  override FuncType parameterizeThis(CType thisType)
  {
    if (!usesThis) return this
    f := |CType t->CType| { t.isThis ? thisType : t }
    return FuncType(params.map(f), names, f(ret))
  }

  override Bool isValid()
  {
    (ret.isVoid || ret.isValid) && params.all |CType p->Bool| { p.isValid }
  }

  CType[] params { private set } // a, b, c ...
  Str[] names    { private set } // parameter names
  CType ret      { private set } // return type
  Bool unnamed                   // were any names auto-generated
  override const Str signature   // |a,b..n->r|
  override const Bool isGenericParameter
  Bool inferredSignature   // were one or more parameters inferred
}

**************************************************************************
** GenericParameterType
**************************************************************************

**
** GenericParameterType models the generic parameter types
** sys::V, sys::K, etc.
**
class GenericParameterType : CType
{
  new make(CNamespace ns, Str name)
  {
    this.ns = ns
    this.name = name
    this.qname = "sys::$name"
  }

  override CNamespace ns
  override CPod pod() { ns.sysPod }
  override CDoc? doc() { null }
  override Str name
  override Str qname
  override Str signature() { qname }
  override Int flags() { FConst.Public }
  override Bool isVal() { false }
  override Bool isNullable() { false }
  override once CType toNullable() { NullableType(this) }
  override Bool isGeneric() { false }
  override Bool isParameterized() { false }
  override Bool isGenericParameter() { true }
  override once CType toListOf() { ListType(this) }
  override CType? base() { ns.objType }
  override CType[] mixins() { CType[,] }
  override CFacet? facet(Str qname) { null }
  override Str:CSlot slots() { throw UnsupportedErr() }
  override COperators operators() { ns.objType.operators }
}

**************************************************************************
** ParameterizedField
**************************************************************************

class ParameterizedField : CField
{
  new make(GenericType parent, CField generic)
  {
    this.parent = parent
    this.generic = generic
    this.fieldType = parent.parameterize(generic.fieldType)
    this.getter = ParameterizedMethod(parent, generic.getter)
    this.setter = ParameterizedMethod(parent, generic.setter)
  }

  override CDoc? doc() { generic.doc }
  override Str name()  { generic.name }
  override Str qname() { generic.qname }
  override Str signature() { generic.signature }
  override Int flags() { generic.flags }
  override CFacet? facet(Str qname) { generic.facet(qname) }

  override CType fieldType
  override CMethod? getter
  override CMethod? setter
  override CType inheritedReturnType() { fieldType }

  override Bool isParameterized() { true }

  override CType parent { private set }
  CField generic { private set }
}

**************************************************************************
** ParameterizedMethod
**************************************************************************

**
** ParameterizedMethod models a parameterized CMethod
**
class ParameterizedMethod : CMethod
{
  new make(GenericType parent, CMethod generic)
  {
    this.parent = parent
    this.generic = generic

    this.returnType = parent.parameterize(generic.returnType)
    this.params = generic.params.map |CParam p->CParam|
    {
      if (!p.paramType.isGenericParameter)
        return p
      else
        return ParameterizedMethodParam(parent, p)
    }

    signature = "$returnType $name(" + params.join(", ") + ")"
  }

  override CDoc? doc() { generic.doc }
  override Str name()  { generic.name }
  override Str qname() { generic.qname }
  override Int flags() { generic.flags }
  override CFacet? facet(Str qname) { generic.facet(qname) }

  override Bool isParameterized()  { true }

  override CType inheritedReturnType()  { generic.inheritedReturnType }

  override CType parent     { private set }
  override Str signature    { private set }
  override CMethod? generic { private set }
  override CType returnType { private set }
  override CParam[] params  { private set }
}

**************************************************************************
** ParameterizedMethodParam
**************************************************************************

class ParameterizedMethodParam : CParam
{
  new make(GenericType parent, CParam generic)
  {
    this.generic = generic
    this.paramType = parent.parameterize(generic.paramType)
  }

  override Str name() { generic.name }
  override Bool hasDefault() { generic.hasDefault }
  override Str toStr() { "$paramType $name" }

  override CType paramType { private set }
  CParam generic { private set }
}