package de.renew.expression;

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


/**
 * An {@code EqualsExpression} is an {@code Expression} that tries to unify (see {@link Unify}) the values of
 * two other {@code Expression}s upon evaluation.
 */
public class EqualsExpression extends ExpressionWithTypeField {
    /**
     * The first {@code Expression} whose value is compared in this {@code EqualsExpression}.
     */
    private final Expression _left;
    /**
     * The second {@code Expression} whose value is compared in this {@code EqualsExpression}.
     */
    private final Expression _right;

    /**
     * Constructs a new {@code EqualsExpression} based on a {@code Class} and the two {@code Expression} instances
     * that should be set to equal.
     *
     * @param targetType the type that the value of the {@code EqualsExpression} should have
     * @param left the first of the expressions whose values the {@code EqualsExpression} compares
     * @param right the second of the expressions whose values the {@code EqualsExpression} compares
     */
    public EqualsExpression(Class<?> targetType, Expression left, Expression right) {
        super(targetType);
        _left = left;
        _right = right;
    }

    @Override
    public boolean isInvertible() {
        return true;
    }

    /**
     * Returns the first {@code Expression} whose value is compared in this {@code EqualsExpression}.
     *
     * @return the first {@code Expression} whose value is compared in this {@code EqualsExpression}
     */
    public Expression getLeft() {
        return _left;
    }

    /**
     * Returns the second {@code Expression} whose value is compared in this {@code EqualsExpression}.
     *
     * @return the second {@code Expression} whose value is compared in this {@code EqualsExpression}
     */
    public Expression getRight() {
        return _right;
    }

    @Override
    public Object startEvaluation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        // The left result must be stored in a variable, because
        // it might be an unknown that changes its value
        // during the evluation of the right hand side.
        Variable result = new Variable(_left.startEvaluation(mapper, recorder, checker), recorder);

        Object rightResult = _right.startEvaluation(mapper, recorder, checker);

        Unify.unify(result, rightResult, recorder);

        return result.getValue();
    }

    @Override
    public Object registerCalculation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        // The left result must be stored in a variable, because
        // it might be an unknown that changes its value
        // during the evluation of the right hand side.
        Variable result =
            new Variable(_left.registerCalculation(mapper, recorder, checker), recorder);
        Object rightObject = _right.registerCalculation(mapper, recorder, checker);

        checker.addCalculated(getType(), result.getValue(), rightObject, recorder);

        return result.getValue();
    }

    @Override
    public String toString() {
        return "EqualsExpr(" + de.renew.util.Types.typeToString(getType()) + ": " + _left + " = "
            + _right + ")";
    }
}