package de.renew.expression;

import java.util.Enumeration;
import java.util.Hashtable;

import de.renew.unify.Copier;
import de.renew.unify.Variable;


/**
 * A variable mapper maps local variables to their respective actual
 * values in the current context.
 *
 * @author Olaf Kummer
 */
public class VariableMapper {

    /**
     * The _hashtable that contains all the variables that have been mapped by this {@code VariableMapper} so far.
     */
    private final Hashtable<LocalVariable, Variable> _hashtable;

    /**
     * Constructs a new empty {@code VariableMapper}.
     */
    public VariableMapper() {
        _hashtable = new Hashtable<LocalVariable, Variable>();
    }

    /**
     * Constructs a new {@code VariableMapper} that already maps all local variables that are known to this
     * {@code VariableMapper}. If the new {@code VariableMapper} is queried with a certain {@code LocalVariable} that
     * is mapped by this {@code VariableMapper}, a copy of the {@code Variable} that this {@code VariableMapper} maps
     * to it will be returned.
     * <p>
     * Put differently, a functional composition of the copier and this {@code VariableMapper} is returned
     *
     * @param copier the copier that maps this instance's variables to their copies
     * @return the new variable mapper
     */
    public VariableMapper makeCopy(Copier copier) {
        VariableMapper mapper = new VariableMapper();
        Enumeration<LocalVariable> enumeration = _hashtable.keys();
        while (enumeration.hasMoreElements()) {
            LocalVariable key = enumeration.nextElement();
            mapper._hashtable.put(key, (Variable) copier.copy(_hashtable.get(key)));
        }
        return mapper;
    }

    /**
     * Maps the given {@code LocalVariable} to a {@code Variable}.
     * On the first call with a certain local variable, a fresh {@code Variable} is returned.
     * On successive calls with the same local variable, the same {@code Variable} will be returned again.
     *
     * @param localVariable the local variable whose mapping in this {@code VariableMapper} should be returned
     * @return the {@code Variable} that the local variable maps to in this {@code VariableMapper}
     */
    public Variable map(LocalVariable localVariable) {
        Variable variable = null;
        if (isMapped(localVariable)) {
            variable = _hashtable.get(localVariable);
        } else {
            variable = new Variable();
            _hashtable.put(localVariable, variable);
        }
        return variable;
    }

    /**
     * Checks if a local variable is already mapped.
     * <p>
     * This method is needed to make {@code VariableMapper} useful for non-Java
     * inscription languages which use their own method of unification.
     *
     * @param localVariable the local variable to check for
     * @return {@code true} if the local variable is mapped by this {@code VariableMapper}, {@code false} if not
     */
    public boolean isMapped(LocalVariable localVariable) {
        return _hashtable.containsKey(localVariable);
    }

    /**
     * Returns an enumeration over all local variables that have been mapped by this {@code VariableMapper} so far.
     *
     * @return all local variables that were mapped by this {@code VariableMapper} so far
     */
    public Enumeration<LocalVariable> getLocalVariables() {
        return _hashtable.keys();
    }

    /**
     * Appends a representation of the bindings currently contained in this {@code VariableMapper}
     * to a given {@code StringBuffer}.
     *
     * @param result the buffer to append a representation of the bindings to
     */
    public void appendBindingsTo(StringBuffer result) {
        result.append("{");
        Enumeration<LocalVariable> variables = getLocalVariables();
        boolean firstEntry = true;
        while (variables.hasMoreElements()) {
            LocalVariable localVariable = variables.nextElement();

            // Only show those variables that want to be shown.
            if (localVariable.getIsVisible()) {
                Variable variable = map(localVariable);


                // Only add those variables to the list that are
                // completely bound.
                if (variable.isBound()) {
                    if (!firstEntry) {
                        result.append(",");
                    }
                    result.append("\n  ");
                    firstEntry = false;
                    result.append(localVariable.name);
                    result.append("=");
                    result.append(variable.getValue());
                }
            }
        }
        result.append("}");
    }
}