package de.renew.engine.simulator;

import java.util.Enumeration;
import java.util.Hashtable;

import de.renew.engine.searcher.Finder;
import de.renew.engine.searcher.Searchable;
import de.renew.engine.searcher.Searcher;
import de.renew.engine.searchqueue.SearchQueue;
import de.renew.engine.thread.SimulationThreadPool;

/**
 * A finder that delays the insertion of searchables into the search queue
 * until all searchables have been checked.
 * <p>
 * This is necessary to avoid that a searchable is inserted into the
 * search queue while it is still being checked. This could lead to
 * multiple insertions of the same searchable into the search queue.
 * <p>
 * The delaying insertion finder keeps track of the earliest time
 * suggested for each searchable and inserts the searchable with
 * that time into the search queue when {@link #flushIntoSearchQueue()}
 * is called.
 */
class DelayingInsertionFinder implements Finder {
    private final Finder _finder;
    private Searchable _searchable;
    private final Hashtable<Searchable, Double> _times = new Hashtable<>();

    DelayingInsertionFinder(Finder finder) {
        _finder = finder;
    }

    void setSearchable(Searchable searchable) {
        _searchable = searchable;
    }

    private double getTimeFor(Searchable searchable) {
        return _times.get(searchable);
    }

    @Override
    public void found(Searcher searcher) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        // We must prepare a reinsertion of this transition
        // into the list of activated elements, regardless whether this
        // transition instance is actually selected by the binder.
        double time = searcher.getEarliestTime();

        // Keep track of the earliest time stamp found.
        // In theory, no searchable should be checked twice.
        double oldTime;
        if (_times.containsKey(_searchable)) {
            oldTime = getTimeFor(_searchable);
        } else {
            oldTime = Double.POSITIVE_INFINITY;
        }
        if (time < oldTime) {
            // Make suggested search time earlier.
            _times.put(_searchable, time);
        }

        // Only inform aggregated finder if searchable is usable now.
        if (time <= SearchQueue.getTime()) {
            _finder.found(searcher);
        }
    }

    @Override
    public boolean isCompleted() {
        return _finder.isCompleted();
    }

    public void flushIntoSearchQueue() {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        Enumeration<Searchable> enumeration = _times.keys();
        while (enumeration.hasMoreElements()) {
            Searchable searchable = enumeration.nextElement();
            double time = getTimeFor(searchable);
            SearchQueue.include(searchable, time);
        }
    }
}