package de.renew.expression;

import de.renew.unify.ICalculationChecker;
import de.renew.unify.IStateRecorder;
import de.renew.unify.Impossible;
import de.renew.unify.TypeConstrainer;
import de.renew.unify.Variable;


/**
 * A {@code TypeCheckingExpression} checks the type of the value of another {@code Expression}.
 * If the type matches, it evaluates to the value of the checked {@code Expression},
 * if not, an {@link Impossible} is thrown to force backtracking.
 */
public class TypeCheckingExpression extends ExpressionWithTypeField {
    /**
     * The expression whose type this {@code TypeCheckingExpression} checks.
     */
    private final Expression _argument;

    /**
     * Constructs a new {@code TypeCheckingExpression} based on a {@code Class} and another {@code Expression}.
     *
     * @param type the type that the {@code TypeCheckingExpression}'s value should be, and therefore the type against
     *             which it checks the value of {@code expression}
     * @param argument the expression whose value the {@code TypeCheckingExpression} should check
     */
    public TypeCheckingExpression(Class<?> type, Expression argument) {
        super(type);
        _argument = argument;
    }

    /**
     * Returns the {@code Expression} whose type this {@code TypeCheckingExpression} checks.
     *
     * @return the {@code Expression} whose type this {@code TypeCheckingExpression} checks
     */
    public Expression getArgument() {
        return _argument;
    }

    @Override
    public boolean isInvertible() {
        return _argument.isInvertible();
    }

    @Override
    public Object startEvaluation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        final Object result = _argument.startEvaluation(mapper, recorder, checker);
        TypeConstrainer.constrain(getType(), result, recorder);
        return result;
    }

    @Override
    public Object registerCalculation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        // During an action, no backward information transfer is supposed
        // to happen. Hence we can simply register a new calculation.
        // This has the advantage of showing the correct result type.
        Variable target = new Variable();

        checker.addCalculated(
            getType(), target, _argument.registerCalculation(mapper, recorder, checker), recorder);

        return target.getValue();
    }

    @Override
    public String toString() {
        return "TypeCheckingExpr(" + de.renew.util.Types.typeToString(getType()) + ": " + _argument
            + ")";
    }
}