package ilog.language.design.types;

/**
 * @version     Last modified on Sat Oct 12 13:02:18 2002 by hak
 * @version          modified on Fri Jun 14 17:40:09 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.kernel.Constant;
import ilog.language.design.base.Instruction;

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

/**
 * This class encapsulates the tables in which defined symbols and declared types
 * are registered.
 */

public class Tables
{
  //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

  /**
   * This table associates a global name to its <a href="./Symbol.html">
   * <tt>Symbol</tt></a> object.
   */
  private final HashMap _symbolTable = new HashMap();

  /**
   * This table associates a type name to its <a href="./TypeDefinition.html">
   * <tt>TypeDefinition</tt></a> object containing the specifics of its definition.
   */
  private final HashMap _typeTable = new HashMap();

  /**
   * This clears all definitions from all (symbol and type) tables.
   */
  public final void clear ()
    {
      _symbolTable.clear();
      _typeTable.clear();
    }

  /**
   * This resets the (symbol and type) tables, erasing all definitions except
   * for the built-ins.
   */
  public final void reset ()
    {
      clear();
      _redefineBuiltins();
      Constant.initialize(this);
    }

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

  public final Symbol symbol (String name)
    {
      Symbol s = (Symbol)_symbolTable.get(name);

      if (s == null)
        _symbolTable.put(name,s = new Symbol(name,_symbolTable.size()));

      return s;
    }

  public final Symbol symbol (String name, boolean flag)
    {
      return symbol(name).setNoCurrying(flag);
    }

  public final boolean isDefined (String name)
    {
      return _symbolTable.get(name) != null;
    }

  public final void showSymbols ()
    {
      System.out.println("Showing declared symbols:\n");

      for (Iterator i = _symbolTable.values().iterator(); i.hasNext();)
       ((Symbol)i.next()).showCodeEntries();
    }

  /**
   * This lists all the defined symbol in lexicographic order.
   */
  public final void showSortedSymbols ()
    {
      System.out.println("Showing known symbols in lexicographic order:\n");

      for (Iterator i = new TreeMap(_symbolTable).values().iterator(); i.hasNext();)
       ((Symbol)i.next()).showCodeEntries();
    }

  /**
   * This lists all the defined symbol in the order in which they have been defined.
   */
  public final void showOrderedSymbols ()
    {
      Symbol[] symbols = new Symbol[_symbolTable.size()];

      for (Iterator i = _symbolTable.values().iterator(); i.hasNext();)
        {
          Symbol symbol = (Symbol)i.next();
          symbols[symbol.index()] = symbol;
        }

      System.out.println("Showing known symbols in the order they have been defined:\n");

      for (int i=0; i<symbols.length; i++)
        symbols[i].showCodeEntries();
    }

  /**
   * This lists all the defined symbol in the (random) order of the symbol table's iterator.
   */
  public final void showDefined ()
    {
      System.out.println("Showing known symbols:\n");

      for (Iterator i = _symbolTable.values().iterator(); i.hasNext();)
       ((Symbol)i.next()).showDefinedEntries();
    }

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

  public final ClassType declareClass (String name, ArrayList members, ArrayList types,
                                       ArrayList fieldInits, ArrayList typeParameters)
      throws ClassDeclarationException
    {
      Type type = getType(name,typeParameters);

      if (type.kind() != Type.CLASS)
        throw new ClassDeclarationException("a type is already defined with name "+name);

      ClassType classType = (ClassType)type;

      if (classType.isDeclared())
        throw new ClassDeclarationException("duplicate class type declaration: "+name);

      if (classType.arguments() != null
          && typeParameters != null
          && typeParameters.size() != classType.arity())
        throw new ClassDeclarationException("wrong number of arguments for class "+name+
                                            "; expected: "+classType.arity()+", found: "+
                                            typeParameters.size());

      return classType.declareMembers(members,types,fieldInits,typeParameters);
    }

    //PV
    // This version of declareClass does not declare the members.

  public final ClassType declareClass (String name, ArrayList typeParameters)
      throws ClassDeclarationException
    {
      Type type = getType(name,typeParameters);

      if (type.kind() != Type.CLASS)
        throw new ClassDeclarationException("a type is already defined with name "+name);

      ClassType classType = (ClassType)type;

      if (classType.isDeclared())
        throw new ClassDeclarationException("duplicate class type declaration: "+name);

      if (classType.arguments() != null
          && typeParameters != null
          && typeParameters.size() != classType.arity())
        throw new ClassDeclarationException("wrong number of arguments for class "+name+
                                            "; expected: "+classType.arity()+", found: "+
                                            typeParameters.size());

      return classType;
    }

  /**
   * Registers a type alias definition for the specified name with the given type.
   */
  public final TypeDefinition defineTypeAlias (String name, Type definition)
    throws TypeDefinitionException
    {
      return defineTypeAlias(name,definition,null);
    }         

  /**
   * Registers a type alias definition for the specified name with the given type and the
   * list of type parameters.
   */
  public final TypeDefinition defineTypeAlias (String name, Type definition, ArrayList parameters)
    throws TypeDefinitionException
    {
      if (_typeTable.get(name) != null)
        throw new TypeDefinitionException("a type is already defined with name "+name);

      TypeDefinition typeDef = new TypeDefinition(name,definition,parameters);
      _typeTable.put(name,typeDef);

      return typeDef;
    }   
  
  /**
   * Registers a builtin type alias definition for the specified name with the given type.
   */
  public final TypeDefinition defineBuiltinTypeAlias (String name, Type definition)
    throws TypeDefinitionException
    {
      return defineBuiltinTypeAlias(name,definition,null);
    }         

  /**
   * Registers a builtin type alias definition for the specified name with the given type and parameters.
   */
  public final TypeDefinition defineBuiltinTypeAlias (String name, Type definition, ArrayList parameters)
    throws TypeDefinitionException
    {
      TypeDefinition typeDef = defineTypeAlias(name,definition,parameters);
      _builtinTypeDefinitions.add(new BuiltinTypeDefinition(name,definition,parameters,false));
      return typeDef;
    }         

  /**
   * Registers a new (opaque) type definition for the specified name with the given type.
   */
  public final TypeDefinition defineNewType (String name, Type definition)
    throws TypeDefinitionException
    {
      return defineNewType(name,definition,null);
    }         

  /**
   * Registers a new (opaque) type definition for the specified name with the given type and the
   * list of type parameters.
   */
  public final TypeDefinition defineNewType (String name, Type definition, ArrayList parameters)
    throws TypeDefinitionException
    {
      if (_typeTable.get(name) != null)
        throw new TypeDefinitionException("a type is already defined with name "+name);

      TypeDefinition typeDef = new TypeDefinition(name,
                                                  new DefinedType(name,definition,parameters),
                                                  parameters);
      _typeTable.put(name,typeDef);

      return typeDef;
    }   
  
  /**
   * Registers a builtin new opaque type definition for the specified name with the given type.
   */
  public final TypeDefinition defineBuiltinNewType (String name, Type definition)
    throws TypeDefinitionException
    {
      return defineBuiltinNewType(name,definition,null);
    }         

  /**
   * Registers a builtin new opaque type definition for the specified name with the given type and parameters.
   */
  public final TypeDefinition defineBuiltinNewType (String name, Type definition, ArrayList parameters)
    throws TypeDefinitionException
    {
      TypeDefinition typeDef = defineNewType(name,definition,parameters);
      _builtinTypeDefinitions.add(new BuiltinTypeDefinition(name,definition,parameters,true));
      return typeDef;
    }         

  /**
   * Returns the <i>already<i> defined type with the specified name from the type table.
   * NB: returns <tt>null</tt> if no type is defined for this name.
   */
  public final Type getDefinedType (String name)
    {
      TypeDefinition typeDef = ((TypeDefinition)_typeTable.get(name));
      return typeDef == null ? null : typeDef.definition();
    }

  /**
   * Returns the type defined with the specified name in the type table; or,
   * if no type is defined with this name, a new registered undeclared class type
   * with this name.
   */
  public final Type getType (String name)
    {
      TypeDefinition typeDef = ((TypeDefinition)_typeTable.get(name));

      if (typeDef == null)
        _typeTable.put(name,typeDef = new TypeDefinition(name,new ClassType(this,name)));

      return typeDef.definition();
    }

  /**
   * Returns the type defined with the specified name in the type table
   * instantiated with the given types. If no type is declared with this name,
   * this will return an instance of a new undeclared class type and register
   * it as a definition for this name. If the number of types specified is not
   * the same as the expected arity, a <tt>StaticSemanticsErrorException</tt>
   * is thrown.
   */
  public final Type getType (String name, ArrayList types) throws StaticSemanticsErrorException
    {
      TypeDefinition typeDef = ((TypeDefinition)_typeTable.get(name));

      if (typeDef == null)
        {
          TypeParameter[] parameters = null;

          if (types != null)
            {
              parameters = new TypeParameter[types.size()];
              for (int i=0; i<parameters.length; i++)
                parameters[i] = new TypeParameter();
            }

          typeDef = new TypeDefinition(name,new ClassType(this,name).setArguments(parameters),
                                       parameters);
          _typeTable.put(name,typeDef);
        }

      return typeDef.instantiate(types);
    }

  /**
   * Shows the declared classes.
   */
  public final void showTypes ()
    {
      System.out.println("Showing registered types:\n");

      for (Iterator i = _typeTable.values().iterator(); i.hasNext();)
        System.out.println(i.next());
    }

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

  // These are conveniences to associate a new builtin instruction to a surface syntax
  // symbol and type. This will complain if the pair <i>(symbol,type)</i> is already
  // defined.

  public final void defineBuiltIn (Symbol symbol, Type type, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      _builtinSymbols.add(new BuiltinDefinition(symbol.name(),type,builtin));
      symbol.defineBuiltIn(type,builtin);
    }

  public final void defineBuiltIn (String name, Type type, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol(name),type,builtin);
    }

  public final void defineBuiltIn (Symbol symbol, Type domain, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol,new FunctionType(domain,range),builtin);
    }

  public final void defineBuiltIn (String name, Type domain, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol(name),domain,range,builtin);
    }

  public final void defineBuiltIn (Symbol symbol, Type domain1, Type domain2, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      Type[] domains = { domain1, domain2 };
      defineBuiltIn(symbol,new FunctionType(domains,range),builtin);
    }

  public final void defineBuiltIn (String name, Type domain1, Type domain2, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol(name),domain1,domain2,range,builtin);
    }

  public final void defineBuiltIn (Symbol symbol, Type domain1, Type domain2, Type domain3, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      Type[] domains = { domain1, domain2, domain3 };
      defineBuiltIn(symbol,new FunctionType(domains,range),builtin);
    }

  public final void defineBuiltIn (String name, Type domain1, Type domain2, Type domain3, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol(name),domain1,domain2,domain3,range,builtin);
    }

  public final void defineBuiltIn (Symbol symbol, Type[] domains, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol,new FunctionType(domains,range),builtin);
    }

  public final void defineBuiltIn (String name, Type[] domains, Type range, Instruction builtin)
    throws DuplicateCodeEntryException
    {
      defineBuiltIn(symbol(name),domains,range,builtin);
    }

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

  private ArrayList _builtinSymbols = new ArrayList();
  private ArrayList _builtinTypeDefinitions = new ArrayList();

  private final void _redefineBuiltins () throws DuplicateCodeEntryException, TypeDefinitionException
    {
      for (Iterator i = _builtinSymbols.iterator(); i.hasNext();)
        {
          BuiltinDefinition bid = (BuiltinDefinition)i.next();
          symbol(bid.name).defineBuiltIn(bid.type,bid.instruction);
        }
      for (Iterator i =  _builtinTypeDefinitions.iterator(); i.hasNext();)
        {
          BuiltinTypeDefinition bid = (BuiltinTypeDefinition)i.next();
          if (bid.isNew)
            defineNewType(bid.name,bid.definition,bid.parameters);
          else
            defineTypeAlias(bid.name,bid.definition,bid.parameters);
        }
    }

  private static class BuiltinDefinition
    {
      String name;
      Type type;
      Instruction instruction;

      BuiltinDefinition (String name, Type type, Instruction instruction)
        {
          this.name = name;
          this.type = type;
          this.instruction = instruction;
        }
    }

  private static class BuiltinTypeDefinition
    {
      String name;
      Type definition;
      ArrayList parameters;
      boolean isNew;

      BuiltinTypeDefinition (String name, Type definition, ArrayList parameters, boolean isNew)
        {
          this.name = name;
          this.definition = definition;
          this.parameters = parameters;
          this.isNew = isNew;
        }
    }
}
