package de.renew.logging.gui;

import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import de.renew.engine.common.SimulatorEvent;
import de.renew.simulatorontology.simulation.StepIdentifier;


/**
 * A {@code LoggerRepository} groups simulation events related to some
 * category (e.g. a specific logger name). It keeps references to
 * {@link StepTrace} objects up to a specified capacity which can be changed
 * dynamically.
 * <p>
 * In fact the {@link StepTrace} objects are stored at the
 * {@link MainRepository} and therefore shared with other
 * {@code LoggerRepository} instances. This class just stores
 * {@link StepIdentifier} objects to refer to corresponding {@link StepTrace}s
 * so that it presents a filtered view of step traces. Objects of this class also
 * listen to step trace changes in the {@link MainRepository} so that they do
 * not miss any updates not directly sent to them.
 * </p>
 *
 * @author Sven Offermann (code)
 * @author Michael Duvigneau (documentation)
 * {@link MainRepository}
 * {@link #setCapacity(int)}
 **/
public class LoggerRepository extends AbstractRepository implements RepositoryChangeListener {

    /**
     * the current capacity setting.
     **/
    private int _capacity = 0;

    /**
     * We identify our local list of {@link StepTrace}s of interest
     * by keeping the corresponding {@link StepIdentifier}s.
     **/
    private List<StepIdentifier> _stepTraces = new LinkedList<>();

    /**
     * a reference to our shared {@code MainRepository} that manages the
     * {@link StepTrace} objects
     **/
    private MainRepository _repository;

    /**
     * Create a new logger repository with the given {@code capacity} that
     * shares information with the given {@link MainRepository}.
     * <p>
     * <code>LoggerRepository</code> objects should be created by
     * {@link MainRepository#getLoggerRepository(String, int)} only.
     * </p>
     *
     * @param repository the main repository to share {@link StepTrace}s with.
     * @param capacity the initial limit on the number of {@link StepTrace}s
     *            this repository will store.
     */
    LoggerRepository(MainRepository repository, int capacity) {
        if (capacity >= 0) {
            this._capacity = capacity;
        }
        this._repository = repository;
    }

    /**
     * Reconfigure the capacity of this repository. The change will take effect
     * the next time the capacity limit is reached when a new step trace is
     * added.
     *
     * @param capacity the new limit on the number of {@link StepTrace}s this
     *            repository will store.
     **/
    public void setCapacity(int capacity) {
        if (capacity >= 0) {
            this._capacity = capacity;
        }
    }

    @Override
    public StepTrace[] getStepTraces(StepIdentifier[] steps) {
        // To prohibit retrieval of step traces from the main repository
        // that are not visible in this LoggerRepository, we filter the
        // argument for known step identifiers.
        Vector<StepIdentifier> traces = new Vector<>();
        for (int x = 0; x < steps.length; x++) {
            if (_stepTraces.contains(steps[x])) {
                traces.add(steps[x]);
            }
        }

        // Now we can delegate step trace retrieval to the main repository
        // with the filtered identifiers.
        return _repository.getStepTraces(traces.toArray(new StepIdentifier[] { }));
    }

    @Override
    public StepTrace[] getAllStepTraces() {
        // pass our list of known step identifiers to the main
        // repository for step trace retrieval
        return _repository.getStepTraces(_stepTraces.toArray(new StepIdentifier[] { }));
    }

    @Override
    public StepTrace getStepTrace(StepIdentifier stepIdentifier) {
        // To prohibit retrieval of step traces from the main repository
        // that are not visible in this LoggerRepository, we filter the
        // argument for known step identifiers.
        if (!_stepTraces.contains(stepIdentifier)) {
            return null;
        }
        return _repository.getStepTrace(stepIdentifier);
    }

    @Override
    public boolean removeStepTrace(StepIdentifier stepIdentifier) {
        boolean success = this._stepTraces.remove(stepIdentifier);
        if (success) {
            StepTrace stepTrace = _repository.getStepTrace(stepIdentifier);

            // remove from the underlying repository
            _repository.removeStepTrace(stepIdentifier);

            // so we must inform our listeners about the removal of the StepTrace.
            this.fireStepTraceRemoved(stepTrace);
        }
        return success;
    }

    /**
     * {@inheritDoc}
     * <p>
     * If a new step trace is created and the configured capacity of this
     * repository is reached, old step traces will be discarded.
     * </p>
     **/
    @Override
    public void addEvent(SimulatorEvent event) {
        // add messages to the underlying repository in any case.
        _repository.addEvent(event);

        // if the step identifier is yet unknown to this LoggerRepository,
        // we have a visible addition.  Check the capacity, store a reference
        // and inform listeners.
        if (!this._stepTraces.contains(event.getStep())) {
            if ((_capacity > 0) && (_stepTraces.size() > _capacity - 1)) {
                while (_stepTraces.size() > _capacity - 1) {
                    StepIdentifier rStep = _stepTraces.get(0);
                    removeStepTrace(rStep);
                }
            }

            this._stepTraces.add(event.getStep());

            // inform listeners that we added some logging information
            this.fireStepTraceAdded(_repository.getStepTrace(event.getStep()));
        }
    }

    // ------------------------------ listener for step trace repository changes

    @Override
    public void stepTraceAdded(StepTraceRepository repository, StepTrace stepTrace) {
        // forward only if we know the added StepTrace. Normally we added it.
        if (this._stepTraces.contains(stepTrace.getStepIdentifier())) {
            this.fireStepTraceAdded(stepTrace);
        }
    }

    @Override
    public void stepTraceRemoved(StepTraceRepository repository, StepTrace stepTrace) {
        // do nothing. we inform our listeners in the remove method.
    }

    @Override
    public void stepTraceRemoveRequest(StepTraceRemoveRequest request) {
        if (this._stepTraces.contains(request.getStepTrace().getStepIdentifier())) {
            request.veto();
        } else {
            // forward the event to registered listeners
            this.fireStepTraceRemoveRequest(request.getStepTrace());
        }
    }

    @Override
    public void stepTraceChanged(StepTrace stepTrace) {
        // forward only if we know the added StepTrace. Normally we added it.
        if (this._stepTraces.contains(stepTrace.getStepIdentifier())) {
            this.fireStepTraceChanged(stepTrace);
        }
    }
}