package de.renew.expression;

import java.io.Serial;

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


/**
 * An {@code AggregateExpression} spawns the evaluation of several subexpressions upon evaluation. It then composes
 * an aggregate from the resulting values.
 *
 * @author Olaf Kummer
 */
public abstract class AggregateExpression implements Expression {
    @Serial
    private static final long serialVersionUID = -4094960769944117557L;

    /**
     * An array with the expressions that are evaluated by this {@code AggregateExpression}.
     */
    private final Expression[] _expressions;

    /**
     * An {@code AggregateExpression} is invertible if it has at least one invertible subexpression.
     * It is invertible by default.
     */
    private boolean _invertible = true;

    /**
     * Constructs a new {@code AggregateExpression} using an array of its subexpressions.
     *
     * @param expressions an array that holds the expressions that must be evaluated
     */
    public AggregateExpression(Expression[] expressions) {
        _expressions = expressions;
        _invertible = false;
        for (int i = 0; i < expressions.length; i++) {
            _invertible = _invertible || expressions[i].isInvertible();
        }
    }

    /**
     * Returns whether this {@code AggregateExpression} is invertible.
     *
     * @return {@code true}, if at least one of my subexpressions is invertible, {@code false} if not
     */
    @Override
    public boolean isInvertible() {
        return _invertible;
    }

    /**
     * Returns an array of all {@code Expression} instances contained in this {@code AggregateExpression}.
     *
     * @return an array of all {@code Expression} instances contained in this {@code AggregateExpression}.
     */
    public Expression[] getExpressions() {
        return _expressions;
    }

    @Override
    public abstract Class<?> getType();

    /**
     * This is a convenience method that prepares an array
     * that holds the result of the evaluation of the subexpressions.
     *
     * @param mapper a variable mapper which is passed to the subexpressions
     * @param recorder a state recorder which is passed to the subexpressions
     * @param checker a calculation checker which is passed to the subexpressions
     * @param forEvaluation {@code true} if an evaluation is being tried, {@code false} if
     *                      calculations are being registered
     * @return the constructed aggregate, possibly incomplete
     * @throws Impossible if the construction of the aggregate fails
     */
    protected Object[] makeArray(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker,
        boolean forEvaluation) throws Impossible
    {
        int n = _expressions.length;


        // We must store the results in variables first, because they
        // may be unknowns that are replaced by other objects later on.
        Variable[] vars = new Variable[n];
        for (int i = 0; i < n; i++) {
            Expression expr = _expressions[i];
            if (forEvaluation) {
                vars[i] = new Variable(expr.startEvaluation(mapper, recorder, checker), recorder);
            } else {
                vars[i] =
                    new Variable(expr.registerCalculation(mapper, recorder, checker), recorder);
            }
        }


        // We can now copy the variable values. It is only safe to use this
        // array until the next unification that assigns values to
        // previously established unknowns.
        Object[] result = new Object[n];
        for (int i = 0; i < n; i++) {
            result[i] = vars[i].getValue();
        }

        return result;
    }

    /**
     * Creates the aggregate that results from this {@code AggregateExpression}. It is overridden in the
     * subclasses of {@code AggregateExpression} to create an aggregate of their choice using the arguments
     * generated by the subexpressions.
     *
     * @param args the objects to be referenced by the constructed aggregate
     * @param recorder a state recorder
     * @return the constructed aggregate, possibly incomplete
     * @throws Impossible if the aggregate could not be constructed (currently unused by all subclasses)
     */
    protected abstract Object makeResultAggregate(Object[] args, IStateRecorder recorder)
        throws Impossible;

    /**
     * Evaluates the subexpressions and puts the results into an aggregate.
     *
     * @param mapper a variable mapper which is passed to the subexpressions
     * @param recorder a state recorder which is passed to the subexpressions
     * @param checker a calculation checker which is passed to the subexpressions
     * @return the constructed aggregate, possibly incomplete
     */
    @Override
    public Object startEvaluation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        return makeResultAggregate(makeArray(mapper, recorder, checker, true), recorder);
    }

    /**
     * Registers the calculations of the subexpressions and puts their results into an aggregate.
     *
     * @param mapper a variable mapper which gets passed to the subexpressions
     * @param recorder a state recorder which gets passed to the subexpressions
     * @param checker a calculation checker which gets passed to the subexpressions
     * @return the constructed aggregate, possibly incomplete
     */
    @Override
    public Object registerCalculation(
        VariableMapper mapper, IStateRecorder recorder, ICalculationChecker checker)
        throws Impossible
    {
        return makeResultAggregate(makeArray(mapper, recorder, checker, false), recorder);
    }

    @Override
    public String toString() {
        String result = "AggregateExpr(" + de.renew.util.Types.typeToString(getType()) + ": ";
        for (int i = 0; i < _expressions.length; i++) {
            if (i > 0) {
                result = result + ", ";
            }
            result = result + _expressions[i];
        }
        result = result + ")";
        return result;
    }
}