package de.renew.logging.gui;

import java.util.HashSet;
import java.util.Vector;

import de.renew.engine.common.SimulatorEvent;
import de.renew.simulator.api.SimulationManager;
import de.renew.simulatorontology.simulation.StepIdentifier;


/**
 * Manages the {@link MainRepository} by providing access methods.
 * FIXME: This is supposed to be a Singleton. Make the constructor(s) private
 * FIXME: Several methods could probably have lower visibility.
 * @author Sven Offermann
 */
public class MainRepositoryManager {
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(MainRepositoryManager.class);
    private static MainRepositoryManager _manager = null;

    // manages the repositories for different simulation runs.
    // for performance reasons this is implemented with arrays 
    // and not with util classes.
    private LongHashSet[] _simulationRunIds;
    private MainRepository[] _repositories;

    /** simulationHistoriesDim determines the dimension of the
     * simulation history. This value determines how many
     * simulation traces should be stored in the memory.
     * The simulation traces are stored in a kind of ring puffer.
     * Older simulations traces will be removed from the puffer.
     * This value should be as small as possible to prevent
     * memory consumption.
     */
    private static final int DEFAULT_SIMULATION_HISTORIES_DIM = 2;

    /**
     * Constructor without parameter using DEFAULT_SIMULATION_HISTORIES_DIM
     */
    public MainRepositoryManager() {
        this(DEFAULT_SIMULATION_HISTORIES_DIM);
    }

    /**
     * Constructor where the dimension of the simulation history is given as a parameter
     * @param simulationHistoriesDim the dimension of the simulation history
     */
    public MainRepositoryManager(int simulationHistoriesDim) {
        this._simulationRunIds = new LongHashSet[simulationHistoriesDim];
        this._repositories = new MainRepository[_simulationRunIds.length];
    }

    /**
     * Gets the current MainRepositoryManager. If there is none, one is created
     * @return the current or created MainRepositoryManager
     */
    public static MainRepositoryManager getInstance() {
        if (_manager == null) {
            _manager = new MainRepositoryManager();
        }

        return _manager;
    }

    /**
     * Getter for the SimulationRunIds
     * @return an Array of the SimulationRunIds
     */
    //TODO not used - can be removed?
    public long[] getSimulationRunIds() {
        Vector<Long> idVector = new Vector<>();

        int x = 0;
        while ((x < this._simulationRunIds.length) && (this._repositories[x] != null)) {
            idVector.addAll(this._simulationRunIds[x]);
            x++;
        }

        long[] ids = new long[idVector.size()];
        for (x = 0; x < idVector.size(); x++) {
            ids[x] = idVector.get(x).longValue();
        }

        return ids;
    }

    /**
     * Gets the MainRepository for a give simulationRunId.
     * If it's a new simulation environment, a new MainRepository is created.
     * @param simulationRunId the encountered run id
     * @return the MainRepository for the given simulationRunId or
     */
    public MainRepository getRepository(long simulationRunId) {
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(
                MainRepositoryManager.class.getSimpleName() + " fetching main repo for runId: "
                    + simulationRunId);
        }
        int x = 0;
        while ((x < _repositories.length) && (_repositories[x] != null)) {
            if (_simulationRunIds[x].contains(simulationRunId)) {
                // found the queried repository 
                return _repositories[x];
            }
            x++;
        }

        // Since the simulation run id is not known yet, we assume a new
        // simulation environment.  Collect all run ids of that environment.
        // If the encountered run id is included, we can assign a new
        // MainRepository.  If not, we are in an unknown situation.
        long[] collectedSimulationRunIds;
        try {
            collectedSimulationRunIds =
                SimulationManager.getCurrentSimulator().collectSimulationRunIds();
        } catch (NullPointerException e) {
            // There is no current simulator, so we cannot assign the run
            // id to the current simulation.
            LOGGER.warn(
                MainRepositoryManager.class.getSimpleName()
                    + ": Could not determine the simulation the run id " + simulationRunId
                    + " belongs to.");
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(MainRepositoryManager.class.getSimpleName() + ": " + e);
            }
            collectedSimulationRunIds = new long[] { simulationRunId };
        }

        LongHashSet newRunIds = new LongHashSet(collectedSimulationRunIds);

        // check whether the set of run ids includes the encountered one
        if (!newRunIds.contains(simulationRunId)) {
            LOGGER.warn(
                MainRepositoryManager.class.getSimpleName() + ": The run id " + simulationRunId
                    + " does not belong to the current simulation.");
            collectedSimulationRunIds = new long[] { simulationRunId };
            newRunIds = new LongHashSet(collectedSimulationRunIds);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                MainRepositoryManager.class.getSimpleName()
                    + ": Creating new repository covering run ids " + newRunIds);
        }

        // create a new repository.
        MainRepository repository = new MainRepository();

        // add repository with the runId to the list of known repositories.
        // For performance reasons we shift the existing repositories by one
        // position in the array list, cause normally the last created repository
        // will be queried most.
        for (int y = (_repositories.length - 1); y > 0; y--) {
            _simulationRunIds[y] = _simulationRunIds[y - 1];
            _repositories[y] = _repositories[y - 1];
        }

        _simulationRunIds[0] = newRunIds;
        _repositories[0] = repository;

        return repository;
    }

    /**
     * Gets the MainRepository for a give SimulatorEvent if the Simulation is still running
     * @param simEvent the Simulator Event for which the MainRepository is searched
     * @return the MainRepository if the simulation has not been not terminated. Else null
     */
    public MainRepository getRepository(SimulatorEvent simEvent) {
        StepIdentifier step = simEvent.getStep();

        //Simulation may be terminated while getting the repository
        if (step != null) {
            long runId = step.getSimulationRunId();
            return getRepository(runId);
        }
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(
                MainRepositoryManager.class.getSimpleName() + " found no main repo for: "
                    + simEvent);
        }
        return null;

    }

    /**
     * Gets a StepTraceRepository based on a logging event.
     * @param loggerName the name of the logging event
     * @return the StepTraceRepository found based on the logging event
     */
    public StepTraceRepository getCurrentRepository(String loggerName) {
        long currentSimId =
            SimulationManager.getCurrentSimulator().currentStepIdentifier().getSimulationRunId();

        return getRepository(currentSimId).getLoggerRepository(loggerName, -1);
    }

    /**
     * This is just a {@link HashSet} subclass parameterized with the element
     * type {@link Long}.
     *
     * @author Lichael Duvigneau
     */
    private static class LongHashSet extends HashSet<Long> {

        /**
         * Create a LongHashSet with the given initial capacity.
         * @param size the initial capacity
         * {@link HashSet#HashSet(int)}
         */
        public LongHashSet(int size) {
            super(size);
        }

        /**
         * Create a new HashSet with initial contents copied from the given
         * array of {@code long} values. The initial set capacity matching
         * the given array
         *
         * @param entries  the initial values.
         */
        public LongHashSet(long[] entries) {
            this(entries.length);
            for (long entry : entries) {
                add(entry);
            }
        }
    }
}