package de.renew.unify;

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

/**
 * This class represents a variable in the unification algorithm.
 * It is used to store a value and notify listeners when the value is bound.
 * The class implements the Unifiable and Referer interfaces.
 */
public final class Variable implements Unifiable, Referer {
    private final Set<Notifiable> _myListeners = new HashSet<>();
    private final Reference _reference;
    private final RecorderChecker _recorderChecker;

    /**
     * The constructor for the Variable class.
     */
    public Variable() {
        _recorderChecker = new RecorderChecker(null);
        _reference = new Reference(new Unknown(), this, null);
    }

    /**
     * Creates a new {@code Variable} using the given initial value and state recorder.
     *
     * @param initValue the initial value of the variable
     * @param recorder the {@link IStateRecorder}
     */
    public Variable(Object initValue, IStateRecorder recorder) {
        _recorderChecker = new RecorderChecker(recorder);
        _reference = new Reference(initValue, this, recorder);
    }

    @Override
    public boolean isComplete() {
        return _reference.isComplete();
    }

    @Override
    public boolean isBound() {
        return _reference.isBound();
    }

    @Override
    public void possiblyCompleted(Set<Notifiable> listeners, IStateRecorder recorder)
        throws Impossible
    {
        // I own only one reference, therefore I am guaranteed to be completed.
        // Am I bound, too?
        if (isBound()) {
            listeners.addAll(_myListeners);
        }
    }

    /**
     * This method gets the value of the variable.
     *
     * @return the value of the variable
     */
    public Object getValue() {
        return _reference.getValue();
    }

    /**
     * Adds a listener to the variable. The listener will be notified when the variable
     * becomes bound. If the listener is already present, it will not be added again.
     * <p>
     * If a state recorder is provided, the addition of the listener is recorded
     * for potential backtracking. If the variable is already bound, the listener
     * is notified immediately.
     *
     * @param listener the {@link Notifiable} listener to be added
     * @param recorder the {@link IStateRecorder} used to record the addition of the listener
     * @throws Impossible if an impossible state is encountered
     */
    public void addListener(final Notifiable listener, IStateRecorder recorder) throws Impossible {
        _recorderChecker.checkRecorder(recorder);

        if (!_myListeners.contains(listener)) {
            _myListeners.add(listener);
            if (recorder != null) {
                recorder.record(() -> _myListeners.remove(listener));
            }


            // If I am already bound, I will not have another opportunity to
            // send a notification. I'll do it now, before it's too late.
            if (isBound()) {
                listener.boundNotify(recorder);
            }
        }
    }

    /**
     * Since this class is final and Reference etc. are also opaque, non-Java
     * languages need a chance to get the reference of a local variable for
     * adding a backlink to it.
     *
     * @return the reference of the variable
     */
    public Reference getReference() {
        return _reference;
    }
}