//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
// THIS CLASS WAS ADDED WITHOUT THE CONSENT OF THE SYSTEM'S AUTHOR. IT IS \\
// NOT PART OF THE ORIGINAL DESIGN AND IT IS POLLUTING IT !!!        -HAK \\
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

package ilog.language.design.types;

/**
 * @author      <a href="mailto:pviry@ilog.fr">Patrick Viry</a>
 * @copyright   &copy; 2001 <a href="http://www.ilog.fr/">ILOG, S.A.</a>
 * @version     Last modified on Sun Dec 08 04:27:12 2002 by hak
 * @version          modified on Wed Oct 16 15:42:36 2002 by pviry
 * @version          modified on Thu May 23 19:17:38 2002 by paulin
 */

import ilog.language.util.ArrayList;
import java.util.HashMap;

/**
 * This is a handle for <a href="ClassType.html"><tt>ClassType</tt></a>, used when a
 * <tt>ClassType</tt> is required before it is actually known. Class members can be
 * declared in an incremental manner using <tt>addField(String,Type)</tt> and
 * <tt>addField(String,Type)</tt>. When all members are declared, one must end the
 * declaration phase with a call to <tt>setDeclared()</tt>.
 *
 * Note: If <tt>ClassType</tt> is modified to allow an incremental definition of
 * fields and methods, then this handle class <tt>ClassTypeHandle</tt> may be
 * removed and all uses of the handle replaced by the class itself.
 */
public class ClassTypeHandle
{
    /**
     ******************************
     * THIS CODE IS WRONG!!!!
     * It does not treat polymorphic types correctly!!!  -hak
     ******************************
     *  Test equality of types ignoring the return type of
     *  functions. If both types are functions types, check whether
     *  their domains are equal, otherwise check if the types
     *  themselves are equal.
     */
    final static boolean equalDomains(Type t1, Type t2)
    {
        if((t1 instanceof FunctionType) && (t2 instanceof FunctionType)) {
            FunctionType ft1 = (FunctionType)t1;
            FunctionType ft2 = (FunctionType)t2;
            if(ft1.arity() != ft2.arity()) return false;
            int a = ft1.arity();
            for(int i=0; i<a; i++) {
                if(!ft1.domain(i).isEqualTo(ft2.domain(i))) return false;
            }
            return true;
        } else {
            return t1.isEqualTo(t2);
        }
    }

    static public final CodeEntry getCodeEntryForDomains(Symbol symbol, Type type)
    {
        ArrayList typeTable = symbol.typeTable();

        for(int i=0; i<typeTable.size(); i++) {
            CodeEntry c = (CodeEntry)typeTable.get(i);
            if(equalDomains(type, c.type())) {
                return c;
            }
        }
        return null;
    }

  /**
   * Returns <tt>true</tt> iff this symbol already has a code entry for
   * the specified type.
   */

  public final static boolean hasCodeEntry (Symbol symbol, Type type)
    throws DuplicateCodeEntryException
    {
      // this works because DefinedEntry's for the same symbol 
      // with the same type are 'equal' (see CodeEntry.equals)
      int index = symbol.typeTable().lastIndexOf(new DefinedEntry(symbol,type));
      return index != -1;
    }

    /**
     *  Creating a new ClassTypeHandle declares the corresponding class.
     */
    public ClassTypeHandle(Tables tables, String name)
    {
        _tables = tables;
//         _classType = (OplClassType)_tables.declareClass(name, new ArrayList());
        _classType = _tables.declareClass(name, new ArrayList());
    }

    private Tables _tables;
//     private OplClassType _classType;
    private ClassType _classType;

    private ArrayList _memberNames = new ArrayList();
    private ArrayList _memberTypes = new ArrayList();
    private ArrayList _memberInits = new ArrayList();

    private ArrayList _names = new ArrayList();

    public void addName(String name)
    {
        _names.add(name);
    }

    /**
     *  Add a field, ie. record the field name and type in the
     *  internal data structures and declare the global variable
     *  corresponding to this field.
     */
    public void addField(String name, Type fieldType, Type globalType)
        throws ClassDeclarationException
    {
        _memberNames.add(name);
        _memberTypes.add(fieldType);
        _memberInits.add(this); // anything not null will do ... see ClassInfo.fillClassInfo()
        
//         Type globalType = new FunctionType(classType(), fieldType).flatten();
        if(getCodeEntryForDomains(_tables.symbol(name),globalType) != null) {
            throw new ClassDeclarationException("multiple declaration for field " + name);
        }
        ((DefinedEntry)_tables.symbol(name).getCodeEntry(globalType, true)).setIsField();
    }

    /**
     *  Add a method, ie. record the method name and type in the
     *  internal data structures and declare the global variable
     *  corresponding to this method.
     */

//     public void addMethod(String name, Type returnType, ArrayList paramTypes)
//         throws ClassDeclarationException
//     {
//         Type methodType;
//         if(paramTypes.size() == 0) {
//             methodType = returnType;
//         } else {
//             methodType = new FunctionType(paramTypes, returnType);
//         }

//         _memberNames.add(name);
//         _memberTypes.add(methodType);
//         _memberInits.add(null);
        
//         Type globalType = new FunctionType(classType(), methodType).flatten();
//         if(_tables.symbol(name).getCodeEntryForDomains(globalType) != null) {
//             throw new ClassDeclarationException("multiple declaration for method " + name + "(" + paramTypes + ")");
//         }
//         _tables.symbol(name).getCodeEntry(globalType, true);
//     }

    public void addMethod(String name, Type methodType, Type globalType)
        throws ClassDeclarationException
    {
        _memberNames.add(name);
        _memberTypes.add(methodType);
        _memberInits.add(null);
        
        if(getCodeEntryForDomains(_tables.symbol(name),globalType) != null) {
            StringBuffer b = new StringBuffer();
            b.append("multiple declaration for method ");
            b.append(_classType);
            b.append(".");
            b.append(name);
            b.append("(");
            FunctionType t = (FunctionType)methodType;
            for(int i=0; i<t.arity(); i++) {
                b.append(t.domain(i));
                if(i<t.arity()-1) b.append(", ");
            }
            b.append(")");
            throw new ClassDeclarationException(b.toString());
        }
        _tables.symbol(name).getCodeEntry(globalType, true);
    }

    public ClassType classType()
    {
        return _classType;
    }
    
    public String className()
    {
        return _classType.name();
    }
    

    /**
     *  This method is used in ClassDummy to determine whether a name
     *  is a field of this class or a global symbol. The determination
     *  should also be based on the type, not only on the name, hence
     *  this method should eventually disappear.
     */
    public boolean containsName(String name)
    {
        return _names.contains(name);
    }

    /**
     *  End the declaration of members with a call to this method. This
     *  will fill the appropriate data structures in the symbol table.
     */
    public void setDeclared()
    {
//         ((OplClassInfo)_classType.classInfo()).fillClassInfo(_tables, _classType, 
        (_classType.classInfo()).fillClassInfo(_tables, _classType, 
                                               _memberNames, _memberTypes, _memberInits, 
                                               null);
    }

    public String toString()
    {
        StringBuffer buf = new StringBuffer();
        buf.append("{ ");
        for(int i=0; i<_memberNames.size(); i++) {
            buf.append(_memberNames.get(i));
            buf.append(" ");
        }
        buf.append("}");
        return buf.toString();
   }
}
