package code.type;

import java.util.Hashtable;
import java.util.HashSet;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Iterator;

import code.stuff.Tracer;
import code.stuff.Logger;
import static code.type.PredefinedTypes.*;
import code.type.visitor.ToStringTypeVisitor;

/**
 * TypeFactory
 *
 * TypeFactory is responsible for the creation of types.
 * Type construction is no longer public.
 * Instead this factory is called to create the type,
 * and keep track of which types have been created.
 * The goal is elimination of multiple creations of the same type.
 *
 * @author David Shapiro
 * @since May 6, 2005
 */

/* DS:
    This version stores complex types using a composite data structure.
    Complex type are those types made up of other types.
    Complex types comprise function and application types.
    Each complex type argument is stored as the key of a hashtable.
    It maps to a value which is the composite structure.
    The value contains another hashtable with
    <key, value> = <next argument, composite structure>.
    After all the arguments have been placed in the structures, one more
    structure is added. This last structure contains the actual type.
    Thus, all arguments are in a recursive, nested structure:
    <key, value> = <argument, composite containing<argument, composite...>>>>.
    Then there's one more structure with a flag key type indicating the
    value is the type: <flag type, stored type>.

    The cache is traversed by recursion.
    Each hash value, which contains the hashtable containing the next argument
    and map to the succeeding hashtable (and so on), is examined by iterating
    through the arguments.
    The recursive call allows traversal of all the arguments by simply
    'priming the pump' with argument 0.
    
    This version differs from the previous by pushing all the work down
    to the Holder class. The latter class creates the type and returns it
    to the caller. The makeXXXType caller does little other than invoke the Holder class.
*/

public class TypeFactory {

  // Private Constructor
  private TypeFactory() {}

  private static ToStringTypeVisitor stringer = new ToStringTypeVisitor();



  // A cache to hold the simple types created by this factory
  private static Hashtable<String, TypeConstructor> simpleTypes =
                  new Hashtable<String, TypeConstructor>();


  // Store the predefined simple types.
  static
  {
    simpleTypes.put(boolTypeName, boolType);
    simpleTypes.put(intTypeName, intType);
    simpleTypes.put(charTypeName, charType);
    simpleTypes.put(floatTypeName, floatType);
    simpleTypes.put(successTypeName, successType);
    simpleTypes.put(ioTypeName, ioType);
    simpleTypes.put(unitTypeName, unitType);
    simpleTypes.put(listTypeName, listType);
    simpleTypes.put(opaqueTypeName, opaqueType);
    simpleTypes.put(tcaTypeName, tcaType);
    simpleTypes.put(functionTypeName, functionType);
	}



  final private static int nTypeVariables = 20;

  // A cache to hold type variables
  private static Hashtable<String, TypeVariable> typeVariables =
                 new Hashtable<String, TypeVariable>(nTypeVariables+1);

  // Create a bunch of normalized type variables
  static
  {
    for (int ii = 0; ii < nTypeVariables; ii++)
      typeVariables.put("" + ii, new TypeVariable(ii));
    typeVariables.put("X", new TypeVariable("X"));
  }




  // A cache to hold the function types
  private static TableHolder functionTypes = new TableHolder();

  // A cache to hold the application types
  private static TableHolder applicationTypes = new TableHolder();


  // Initialize argument store and store predefined application types
  static
  {
	  makeApplicationType(listTypeName,charType);   // string type
	  makeApplicationType(ioTypeName, unitType);    // output type
  }



  // Create new simple type or retrieve already-created simple type by name
  public static TypeExpression makeSimpleType(String name)
  {
    TypeConstructor aTC = null;
    if ((aTC = simpleTypes.get(name)) == null)
    {
      aTC = new TypeConstructor(name);
      simpleTypes.put(name, aTC);
      if (Tracer.typecache)
        Logger.logln("Added simple number: " + simpleTypes.size());
    }
    if (Tracer.typecache)
      Logger.logln("Simple: " + aTC.accept(stringer, null));
    return aTC;
  }



  // Create new type variable or retrieve already-created type variable by name
  public static TypeVariable makeTypeVariable(int num)
  {
    TypeVariable aTV = null;
    if ((aTV = typeVariables.get("" + num)) == null)
    {
      aTV = new TypeVariable(num);
      typeVariables.put("" + num, aTV);
      if (Tracer.typecache)
        Logger.logln("Added type variable: " + typeVariables.size());
    }
    if (Tracer.typecache)
      Logger.logln("Type Variable: " + aTV.accept(stringer, null));
    return aTV;
  }

  public static TypeVariable makeTypeVariable(String name)
  {
    TypeVariable aTV = null;
    if ((aTV = typeVariables.get(name)) == null)
    {
      aTV = new TypeVariable(name);
      typeVariables.put(name, aTV);
      if (Tracer.typecache)
        Logger.logln("Added type variable: " + typeVariables.size());
    }
    if (Tracer.typecache)
      Logger.logln("Type Variable: " + aTV.accept(stringer, null));
    return aTV;
  }



  // Create a new function type or retrieve already-created function type
  public static FunctionType makeFunctionType(TypeExpression domain, TypeExpression range)
  {
    FunctionType aFT = (FunctionType) functionTypes.getType(functionType, "", 0, domain, range);
    if (Tracer.typecache)
      Logger.logln("Function Type: " + aFT.accept(stringer, null));
    return aFT;
  }



  // Create a new application type or retrieve already-created application type
  public static TypeConstructorApplication makeApplicationType(String name, TypeExpression ... arguments)
  {

    TypeConstructorApplication aTCA = (TypeConstructorApplication)
                     applicationTypes.getType(tcaType, name, 0, arguments);
    if (Tracer.typecache)
      Logger.logln("Application: " + aTCA.accept(stringer, null));
    return aTCA;
  }




  // Debug methods

  // Print out all type expressions in specified caches.
  public static void dump(String cacheName)
  {
    if (cacheName.equals("TypeConstructor") || cacheName.equals("TC") || cacheName.equals(""))
      dumpSimpleTypes("");
    if (cacheName.equals("TypeVariable") || cacheName.equals("TV") || cacheName.equals(""))
      dumpTypeVariables("");
    if (cacheName.equals("FunctionType") || cacheName.equals("FT") || cacheName.equals(""))
      dumpFunctionTypes("");
    if (cacheName.equals("TypeConstructorApplication") || cacheName.equals("TCA") || cacheName.equals(""))
      dumpApplicationTypes("");
  }

  // Print out all type expressions containing this string (usually a module name)
  public static void grep(String name)
  {
    dumpSimpleTypes(name);
    dumpTypeVariables(name);
    dumpFunctionTypes(name);
    dumpApplicationTypes(name);
  }


  private static void dumpSimpleTypes(String grepString)
  {
    System.out.println("TYPE CONSTRUCTORS:\n");
    for (TypeConstructor tc : simpleTypes.values())
    {
      String typeStr = tc.accept(stringer, null);
      if (grepString.equals("") || typeStr.indexOf(grepString) > -1)
        System.out.println(typeStr);
    }
    System.out.println("\n");
  }

  private static void dumpTypeVariables(String grepString)
  {
    System.out.println("TYPE VARIABLES:\n");
    for (TypeVariable tv : typeVariables.values())
    {
      String typeStr = tv.accept(stringer, null);
      if (grepString.equals("") || typeStr.indexOf(grepString) > -1)
        System.out.println(typeStr);
    }
    System.out.println("\n");
  }

  private static void dumpFunctionTypes(String grepString)
  {
    System.out.println("FUNCTION TYPES:\n");
    HolderIterator itCache = new HolderIterator(functionTypes);
    while (itCache.hasNext())
    {
      FunctionType ft = (FunctionType) itCache.next();
      String typeStr = ft.accept(stringer, null);
      if (grepString.equals("") || typeStr.indexOf(grepString) > -1)
        System.out.println(typeStr);
    }
    System.out.println("\n");
  }

  private static void dumpApplicationTypes(String grepString)
  {
    System.out.println("TYPE CONSTRUCTOR APPLICATIONS:\n");
    HolderIterator itCache = new HolderIterator(applicationTypes);
    while (itCache.hasNext())
    {
      TypeConstructorApplication tca = (TypeConstructorApplication) itCache.next();
      String typeStr = tca.accept(stringer, null);
      if (grepString.equals("") || typeStr.indexOf(grepString) > -1)
        System.out.println(typeStr);
    }
    System.out.println("\n");
  }

}

