package de.renew.unify;

import java.util.HashSet;
import java.util.Set;

/**
 * This class checks if a calculation can actually be performed.
 * <p>
 * On the fly:
 *   Check that no calculated unknown is bound.
 *   Check that no calculated unknown is registered twice.
 *   Check that no two calculated unknowns depend on each other.
 * During the consistency check:
 *   Check that all early variables have become bound.
 *   Check that all late variables are either bound or calculated.
 */
final public class CalculationChecker implements ICalculationChecker {
    private final Set<Variable> _lateVariables;
    private final Set<Variable> _earlyVariables;

    /**
     * Constructor of class CalculationChecker.
     * <p>
     * Initializes the sets of early and late variables.
     */
    public CalculationChecker() {
        _lateVariables = new HashSet<>();
        _earlyVariables = new HashSet<>();
    }

    @Override
    public void reset() {
        _lateVariables.clear();
        _earlyVariables.clear();
    }

    @Override
    public void addEarlyVariable(final Variable var, IStateRecorder recorder) throws Impossible { //NOTICE throws
        if (!_earlyVariables.contains(var)) {
            if (recorder != null) {
                recorder.record(() -> _earlyVariables.remove(var));
            }

            _earlyVariables.add(var);
        }
    }

    @Override
    public void addCalculated(
        Class<?> targetType, Object target, Object source, IStateRecorder recorder)
        throws Impossible
    {
        Unify.unify(target, new Calculator(targetType, source, recorder), recorder);
    }

    @Override
    public void addLateVariable(final Variable var, IStateRecorder recorder) throws Impossible {
        if (!_lateVariables.contains(var)) {
            if (recorder != null) {
                recorder.record(() -> _lateVariables.remove(var));
            }

            _lateVariables.add(var);
        }
    }

    @Override
    public boolean isConsistent() {
        return checkLateVariables() && checkEarlyVariables();
    }

    /**
     * Checks if all late variables are complete.
     *
     * @return if all late variables are complete
     */
    private boolean checkLateVariables() {
        return _lateVariables.stream().allMatch(Variable::isComplete);
    }

    /**
     * Checks if all early variables are bound.
     * (They have to be bound)
     *
     * @return {@code true} if all early variables are bound, {@code false} otherwise
     */
    private boolean checkEarlyVariables() {
        return _earlyVariables.stream().allMatch(Variable::isBound);
    }
}