package de.renew.expression;

import java.io.Serializable;

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


/** An expression might be:
  *   - an assignment
  *   - a unification (invertible)
  *   - a method
  *   - a constructor
  *   - an operator
  *   - a field read
  *   - a field write (only late!)
  *   - an array read
  *   - an array write (only late!)
  *   - a constant
  *   - a tuple (possibly invertible)
  *   - a local variable (invertible, l-value)
  */
public interface Expression extends Serializable {

    /**
     * {@code true} if this {@code Expression} is invertible, {@code false} if not.
     * <p>
     * An invertible {@code Expression} can propagate information both from its arguments to its result
     * variable and from its result variable to its arguments. This means that if its result variable
     * is unified with a value, this leads to the unification of its arguments with appropriate values.
     * <p>
     * A non-invertible {@code Expression} can only determine the value of its result variable based on its arguments,
     * not the other way around.
     *
     * @return {@code true} if this {@code Expression} is invertible, {@code false} if not
     */
    boolean isInvertible();

    /**
     * Starts the evaluation. The resulting object might be an unknown.
     * It will usually be stored in a variable.
     *
     * @param mapper the {@code VariableMapper} that is used to look up local variables during the evaluation
     * @param recorder The {@code IStateRecorder} that is used to record states during the calculations so that they
     *                 can be reverted if necessary. If it is {@code null}, the calculations cannot be undone, which
     *                 is usually the case during late evaluations.
     * @param checker The {@code ICalculationChecker} that is used during the evaluation. If it is {@code null}, no
     *                information on desired bindings will be provided, which is usually the case during late
     *                evaluations.
     * @return an {@code Object} that is the result of the evaluation of this {@code Expression}, which may be a
     *         {@link de.renew.unify.Unknown} and is usually stored in a variable
     * @throws Impossible if evaluating this {@code Expression} was not possible
     */
    Object startEvaluation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible;

    /**
     * Informs the calculation checker how an evaluation of this expression would proceed.
     *
     * @param mapper the {@code VariableMapper} that is used to look up local variables during the calculation
     * @param recorder the {@code IStateRecorder} that is used to record states during the calculations so that they
     *                 can be reverted if necessary
     * @param checker the {@code ICalculationChecker} that is used during the calculation
     * @return an {@code Object} that is the result of the calculation of this {@code Expression}, which may be a
     *         {@link de.renew.unify.Unknown} and is usually stored in a variable
     * @throws Impossible if evaluating this {@code Expression} was not possible
     */
    Object registerCalculation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible;

    /**
     * Returns the type of the value that this expression will compute, or {@link de.renew.util.Types#UNTYPED} if no
     * type information can be given.
     *
     * @return the type of the value that this expression will compute, or {@code Types.UNTYPED} if no
     *         type information can be given
     */
    Class<?> getType();
}