package de.renew.unify;

import java.util.Set;

/**
 * A {@code Calculator} is used to perform calculations while firing a transition.
 */
public final class Calculator implements Unifiable, Referable, Referer {
    private BacklinkSet _backlinkSet;
    private final Reference _reference;
    private final RecorderChecker _recorderChecker;
    private final Class<?> _type;

    Calculator(Class<?> type, Object object, IStateRecorder recorder) {
        _recorderChecker = new RecorderChecker(recorder);
        _reference = new Reference(object, this, recorder);
        _type = type;
        if (!isComplete()) {
            _backlinkSet = new BacklinkSet();
        }
    }

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

    @Override
    public boolean isBound() {
        return false;
    }

    /**
     * Returns the type of the calculator.
     *
     * @return the type of the calculator
     */
    public Class<?> getType() {
        return _type;
    }

    private void checkBacklinkSet() {
        if (_backlinkSet == null) {
            if (isComplete()) {
                throw new RuntimeException("A complete tuple " + "received a backlink. Strange");
            } else {
                throw new RuntimeException(
                    "An incomplete tuple lacks " + "a backlink set. Strange");
            }
        }
    }

    @Override
    public void addBacklink(Reference reference, IStateRecorder recorder) {
        _recorderChecker.checkRecorder(recorder);
        checkBacklinkSet();
        _backlinkSet.addBacklink(reference, recorder);
    }

    @Override
    public void possiblyCompleted(Set<Notifiable> listeners, IStateRecorder recorder)
        throws Impossible
    {
        if (isComplete()) {
            checkBacklinkSet();
            _backlinkSet.updateBacklinked(this, this, listeners, recorder);


            // I have now done the update, so the backlinked objects should be
            // forgotten. In fact, they have to be dumped into the state
            // recorder for future backtracking.
            final BacklinkSet oldBacklinkSet = _backlinkSet;
            if (recorder != null) {
                recorder.record(() -> _backlinkSet = oldBacklinkSet);
            }
            _backlinkSet = null;
        }
    }

    @Override
    public void occursCheck(Unknown that, Set<IdentityWrapper> visited) throws Impossible {
        // Am I complete? If yes, no unknown can possibly
        // be contained within me.
        if (_reference.isComplete()) {
            return;
        }

        // Did I check myself earlier?
        IdentityWrapper thisWrapper = new IdentityWrapper(this);
        if (visited.contains(thisWrapper)) {
            return;
        }


        // I do not want to check me again.
        visited.add(thisWrapper);

        _reference.occursCheck(that, visited);
    }

    /**
     * Disable the normal <code>equals</code> and <code>hashCode</code>
     * methods.  Because calculators are not values, it would be disastrous
     * to insert them into a hashtable.
     *
     * @throws RuntimeException always.
     **/
    @Override
    public boolean equals(Object that) {
        throw new RuntimeException("Somebody compared a calculator.");
    }

    /**
     * Disable the normal <code>equals</code> and <code>hashCode</code>
     * methods.  Because calculators are not values, it would be disastrous
     * to insert them into a hashtable.
     *
     * @throws RuntimeException always.
     **/
    @Override
    public int hashCode() {
        throw new RuntimeException("Somebody took the hash code of a calculator.");
    }

    @Override
    public String toString() {
        return "<pending: " + getType() + ">";
    }
}