package code.type.typechecker;

import code.type.*;
import code.type.visitor.TypeVisitor;
import static code.type.TypeFactory.*;

import java.util.Hashtable;

/**
 * This class renames type variables occurring in a type expression.
 * Type variables are assumed to be shared in the expression, e.g.
 * in (a->b)->a, there are two references to the type 'a' but only
 * one instance. We do not rename the second occurance of 'a'.
 * <p/>
 * Normally, same variable names (represented as a String) denote
 * equivalent type variables, but these names get obscured once
 * types are unified and synthesized. Hence, the need for renaming
 * type variables based on shared variables.
 *
 * @author Khai Pham
 */
public class RenameTypeVisitor implements TypeVisitor<TypeExpression,Object> {

    public RenameTypeVisitor() {}

    /**
     * If we have not generated a new name for var, then create a fresh type
     * variable name for it. Otherwise, we return the variable type with
     * the name assigned previously. We make use of a hash table to keep
     * track of whether variables have been assigned.
     *
     * @param var - the type variable
     * @param ignore   - not used
     * @return a new variable type with a fresh name or one previously created through this function.
     */
    public TypeExpression visit(TypeVariable var, Object ignore) {
        if (!table.containsKey(var)) {
            String name = generateName();
            TypeVariable newvar = (TypeVariable) makeTypeVariable(name);
            table.put(var, newvar);
            return newvar;
        } else
            return table.get(var);
    }

    public TypeExpression visit(TypeConstructor cons, Object ignore) {
        return cons;
    }

    public TypeExpression visit(FunctionType fun, Object ignore) {

        TypeExpression domain = fun.domain.accept(this, ignore);
        TypeExpression range = fun.range.accept(this, ignore);

        return makeFunctionType(domain, range);
    }

    public TypeExpression visit(TypeConstructorApplication app, Object ignore) {

        TypeExpression[] args = new TypeExpression[app.arguments.length];

        for (int i = 0; i < app.arguments.length; ++i)
            args[i] = app.arguments[i].accept(this, ignore);

        return makeApplicationType(app.typeConstructor, args);
    }

    /**
     * This function generates new type names. 
     * It first generates a,b,c,..,z. Once
     * those identifiers run out, it generates aa,bb,cc,..,zz, etc.
     *
     * @return a unique name for the type variable
     */
    // TODO: as of Sat Apr 16 15:44:43 PDT 2005
    // names of type variables are 0, 1, 2, ...
    // This method should conform to this convention.
    private String generateName() {

        int idx = nextid % (varlist.length() - 1);
        String name = varlist.substring(idx, idx + 1);

        String n = name;
        for (int i = 0; i < nextid / varlist.length(); i++)
            name += n;

        nextid++;
        return name;
    }

    private int nextid = 0;
    private Hashtable<TypeVariable,TypeVariable> table
	= new Hashtable<TypeVariable,TypeVariable>();
    private static final String varlist = "abcdefghijklmnopqrstuvwxyz";
}

