package ilog.language.design.types;

/**
 * @version     Last modified on Wed Sep 25 17:43:49 2002 by hak
 * @version          modified on Thu Jun 13 16:22:47 2002 by pviry
 * @author      <a href="mailto:hak@ilog.fr">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; 2000-2002 <a href="http://www.ilog.fr/">ILOG, S.A.</a>
 */

import ilog.language.design.kernel.DefinitionException;
import ilog.language.design.base.Instruction;

import ilog.language.util.Comparable;

import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;

/**
 * A symbol object is essentially a (global) name and its type table. The type table
 * is a list of <a href="./CodeEntry.html"><tt>CodeEntry</tt></a> objects.
 */

public class Symbol
{
  private String _name;
  private int _index;
  private boolean _noCurrying = false;
  private ArrayList _typeTable = new ArrayList();

  public Symbol (String name)
    {
      _name = name;
    }

  public Symbol (String name, int index)
    {
      _name = name;
      _index = index;
    }

  public final String name ()
    {
      return _name;
    }

  public final int index ()
    {
      return _index;
    }

  public final ArrayList typeTable ()
    {
      return _typeTable;
    }

  public final Symbol setNoCurrying (boolean flag)
    {
      _noCurrying = flag;
      return this;
    }

  //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

  /**
   * This is a dummy symbol that is given the types allowed for array index sets.
   * It is used for type-checking array dimension expressions for allocation via a
   * dummy <a href="./Global.html"><tt>Global</tt></a> constructed with this
   * symbol, thus enabling a choice point to be created for typing these expressions.
   */
  public static final Symbol INDEX_SET = new Symbol("INDEX_SET");
  static
    {
      INDEX_SET.getCodeEntry(Type.INT());
      INDEX_SET.getCodeEntry(Type.INT_RANGE);
      INDEX_SET.getCodeEntry(new SetType());
    }

  /**
   * This is a dummy symbol that is given the types allowed for map index sets.
   * It is used for type-checking array dimension expressions for allocation via a
   * dummy <a href="../kernel/Global.html"><tt>Global</tt></a> constructed with this
   * symbol, thus enabling a choice point to be created for typing these expressions.
   */
  public static final Symbol INDEXABLE = new Symbol("INDEXABLE");
  static
    {
      INDEXABLE.getCodeEntry(Type.INT_RANGE);
      INDEXABLE.getCodeEntry(new SetType());
    }

  /**
   * This is a dummy symbol that is given the types allowed for collections.
   * It is used for type-checking it using a dummy <a href="../kernel/Global.html"><tt>Global</tt></a>
   * constructed with this symbol, thus enabling choice points to be created for typing its
   * expression.
   */
  public static final Symbol COLLECTION = new Symbol("COLLECTION");

  static
    { // NB: Although this is currently identical to INDEXABLE, this will not be the case
      // when other collections such as lists and bags are implemented.

      COLLECTION.getCodeEntry(Type.INT_RANGE);
      COLLECTION.getCodeEntry(new SetType());
//        COLLECTION.getCodeEntry(new ListType());
//        COLLECTION.getCodeEntry(new BagType());
    }

  //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

  public final DefinedEntry registerCodeEntry (Type type) throws DefinitionException
    {
      CodeEntry entry = getCodeEntry(type);

      if (entry.isBuiltIn() || entry.isDefinedField())
        throw new DefinitionException("cannot redefine "+entry);

      return (DefinedEntry)entry;
    }

  /**
   * Assigns and returns a <a href="./CodeEntry.html"><tt>CodeEntry</tt></a>
   * corresponding to the specified <a href="./Type.html"><tt>Type</tt></a>
   * for this symbol if one does not exist for with this type; otherwise, returns
   * the existing one.
   */
  public final CodeEntry getCodeEntry (Type type)
    {
      return getCodeEntry(type,false);
    }

  /**
   * Assigns and returns a <a href="./CodeEntry.html"><tt>CodeEntry</tt></a>
   * corresponding to the specified <a href="./Type.html"><tt>Type</tt></a>
   * for this symbol if one does not exist for with this type.  If the
   * <tt>noDuplicates</tt> is true, a <tt>DuplicateCodeEntryException</tt>
   * is thrown whenever an entry already existed for this type; otherwise,
   * returns the existing code entry.
   */
  public final CodeEntry getCodeEntry (Type type, boolean noDuplicates)
  throws DuplicateCodeEntryException
    {
      return getCodeEntry(new DefinedEntry(this,type),noDuplicates);
    }

  /**
   * Assigns the specified <a href="./CodeEntry.html"><tt>CodeEntry</tt></a>
   * to this symbol if none exists with its type for this symbol.  If the
   * <tt>noDuplicates</tt> is true, a <tt>DuplicateCodeEntryException</tt>
   * is thrown whenever an entry already existed for this type; otherwise,
   * returns the existing code entry.
   */
  public final CodeEntry getCodeEntry (CodeEntry entry, boolean noDuplicates)
  throws DuplicateCodeEntryException
    {
      //Type.resetNames();
      //ilog.language.tools.Debug.step("Looking up code entry "+entry+" in:\n\t"+_typeTable);
      int index = _typeTable.lastIndexOf(entry);
      
      if (index == -1)
        {
          _typeTable.add(entry);
          //ilog.language.tools.Debug.step("Not found - adding it:\n\t"+_typeTable);
          return entry;
        }

      //ilog.language.tools.Debug.step("Found at position "+index);
      if (noDuplicates) throw new DuplicateCodeEntryException(entry);

      return (CodeEntry)_typeTable.get(index);
    }



    /**
     *  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.
     */
    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);
        }
    }

    public final CodeEntry getCodeEntryForDomains(Type type)
    {
        for(int i=0; i<_typeTable.size(); i++) {
            CodeEntry c = (CodeEntry)_typeTable.get(i);
            if(equalDomains(type, c.type())) {
                return c;
            }
        }
        return null;
    }


  /**
   *  Added by PV.
   *
   *  True if this symbol already has a CodeEntry for type. This method is needed since
   *  getCodeEntry always adds a new CodeEntry as a side-effect.
   */

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

  /**
   * Removes the latest code entry in this symbol's type table.
   */
  public final void removeLatestEntry ()
    {
      _typeTable.remove(_typeTable.size()-1);
    }      

  /**
   * Installs this symbol as a built-in with specified type and instruction.
   * <b>N.B.:</b> If this type was defined for this symbol before, a
   * <tt>DuplicateCodeEntryException</tt> is thrown.
   */
  public final void defineBuiltIn (Type type, Instruction builtIn) throws DuplicateCodeEntryException
    {
      if (isDefined(type))
        throw new DuplicateCodeEntryException(this + " : " + type);

      if (type.kind() == Type.FUNCTION && _noCurrying)
        ((FunctionType)type).setNoCurrying();

      _typeTable.add(new BuiltinEntry(this,type.standardize(),builtIn));
    }

  public final boolean isDefined ()
    {
      return !_typeTable.isEmpty();
    }

  public final boolean isDefined (Type type)
    {
      if (!isDefined())
        return false;

      type = type.standardize();

      for (int i=_typeTable.size(); i-->0;)
        if (type.equals(((CodeEntry)_typeTable.get(i)).type()))
          return true;

      return false;
    }

  public final void showDefinedEntries ()
    {
      boolean foundEntries = false;

      for (Iterator i=_typeTable.iterator(); i.hasNext();)
        {
          CodeEntry entry = (CodeEntry)i.next();
          if (entry.isBuiltIn()) continue;
          Type.resetNames();
          System.out.println("\t"+entry);
          foundEntries = true;
        }

      if (foundEntries) System.out.println();
    }

  public final void showCodeEntries ()
    {
      for (Iterator i=_typeTable.iterator(); i.hasNext();)
        {
          Type.resetNames();
          System.out.println("\t"+i.next());
        }

      System.out.println();
    }

  public final String toString ()
    {
      return _name;
    }
}
