package de.renew.engine.searchqueue;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

import de.renew.engine.searcher.Searchable;
import de.renew.engine.thread.SimulationThreadPool;


/**
 * A random search queue returns possibly enabled searchable
 * objects in a random order. This is useful for running many
 * simulations that explore the possible behaviour of a net.
 * <p>
 * The implementation is based on an array. During the
 * extraction, a random element is removed and the
 * void position is filled in with the very last element.
 *
 * @author Olaf Kummer
 **/
class RandomSearchQueue implements SearchQueueData {
    private final double _time;
    private final List<RandomQueueNode> _elements;
    private final Hashtable<Searchable, RandomQueueNode> _lookup;

    RandomSearchQueue(double time) {
        _time = time;
        _elements = new ArrayList<>();
        _lookup = new Hashtable<>();
    }

    @Override
    public double getTime() {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        return _time;
    }

    @Override
    public void include(Searchable searchable) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        if (!_lookup.containsKey(searchable)) {
            RandomQueueNode node = new RandomQueueNode(searchable);
            _elements.add(node);
            _lookup.put(searchable, node);
        }
    }

    private void discard(RandomQueueNode node) {
        _elements.remove(node);
        _lookup.remove(node.getSearchable());
    }

    @Override
    public void exclude(Searchable searchable) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        RandomQueueNode node = _lookup.get(searchable);
        if (node != null) {
            discard(node);
        }
    }

    @Override
    public Searchable extract() {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        if (_elements.isEmpty()) {
            return null;
        }

        // Choose a random element.
        int pos = ThreadLocalRandom.current().nextInt(_elements.size());

        // Remember element.
        RandomQueueNode node = _elements.get(pos);

        // Discard element from data structure.
        discard(node);

        return node.getSearchable();
    }

    @Override
    public Enumeration<Searchable> elements() {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
        return _lookup.keys();
    }

    @Override
    public int size() {
        return _elements.size();
    }
}