/*******************************************************************************
 * Copyright (c) 2013, 2015 École Polytechnique de Montréal
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Geneviève Bastien - Initial API and implementation
 *   Bernd Hufmann - Integrated history builder functionality
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.core.statesystem;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.common.core.log.TraceCompassLog;
import org.eclipse.tracecompass.common.core.log.TraceCompassLogUtils.ScopeLog;
import org.eclipse.tracecompass.internal.tmf.core.Activator;
import org.eclipse.tracecompass.internal.tmf.core.statesystem.backends.partial.PartialHistoryBackend;
import org.eclipse.tracecompass.internal.tmf.core.statesystem.backends.partial.PartialStateSystem;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystemBuilder;
import org.eclipse.tracecompass.statesystem.core.StateSystemFactory;
import org.eclipse.tracecompass.statesystem.core.backend.IStateHistoryBackend;
import org.eclipse.tracecompass.statesystem.core.backend.StateHistoryBackendFactory;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.snapshot.StateSnapshot;
import org.eclipse.tracecompass.tmf.core.analysis.TmfAbstractAnalysisModule;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider.FutureEventType;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceCompleteness;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.core.trace.experiment.TmfExperiment;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;

/**
 * Abstract analysis module to generate a state system. It is a base class that
 * can be used as a shortcut by analysis who just need to build a single state
 * system with a state provider.
 *
 * Analysis implementing this class should only need to provide a state system
 * and optionally a backend (default to NULL) and, if required, a filename
 * (defaults to the analysis'ID)
 *
 * @author Geneviève Bastien
 */
public abstract class TmfStateSystemAnalysisModule extends TmfAbstractAnalysisModule
        implements ITmfAnalysisModuleWithStateSystems {

    private static final Logger LOGGER = TraceCompassLog.getLogger(TmfStateSystemAnalysisModule.class);

    private static final String EXTENSION = ".ht"; //$NON-NLS-1$

    private final CountDownLatch fInitialized = new CountDownLatch(1);
    private final Object fRequestSyncObj = new Object();

    private @Nullable ITmfStateSystemBuilder fStateSystem;
    private @Nullable ITmfEventRequest fRequest;
    private @Nullable TmfTimeRange fTimeRange = null;

    private int fNbRead = 0;
    private boolean fInitializationSucceeded;

    private volatile @Nullable ITmfStateProvider fStateProvider;
    private @Nullable Integer fProviderVersion = null;

    /**
     * State system backend types
     *
     * @author Geneviève Bastien
     */
    protected enum StateSystemBackendType {
        /** Full history in file */
        FULL,
        /** In memory state system */
        INMEM,
        /** Null history */
        NULL,
        /** State system backed with partial history */
        PARTIAL
    }

    /**
     * Retrieve a state system belonging to trace, by passing the ID of the relevant
     * analysis module.
     *
     * This will start the execution of the analysis module, and start the
     * construction of the state system, if needed.
     *
     * @param trace
     *            The trace for which you want the state system
     * @param moduleId
     *            The ID of the state system analysis module
     * @return The state system, or null if there was no match or the module was not
     *         initialized correctly
     */
    public static @Nullable ITmfStateSystem getStateSystem(ITmfTrace trace, String moduleId) {
        TmfStateSystemAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, TmfStateSystemAnalysisModule.class, moduleId);
        if (module != null) {
            ITmfStateSystem ss = module.getStateSystem();
            if (ss != null) {
                return ss;
            }
            IStatus status = module.schedule();
            if (status.isOK()) {
                return module.waitForInitialization() ? module.getStateSystem() : null;
            }
        }
        return null;
    }

    /**
     * Get the state provider for this analysis module
     *
     * @return the state provider
     */
    protected abstract ITmfStateProvider createStateProvider();

    /**
     * Get the state system backend type used by this module
     *
     * @return The {@link StateSystemBackendType}
     */
    protected StateSystemBackendType getBackendType() {
        /* Using full history by default, sub-classes can override */
        return StateSystemBackendType.FULL;
    }

    /**
     * Get the supplementary file name where to save this state system. The default
     * is the ID of the analysis followed by the extension.
     *
     * @return The supplementary file name
     */
    protected String getSsFileName() {
        return getId() + EXTENSION;
    }

    /**
     * Get the state system generated by this analysis, or null if it is not yet
     * created.
     *
     * @return The state system
     */
    @Nullable
    public ITmfStateSystem getStateSystem() {
        return fStateSystem;
    }

    @Override
    public Map<String, Integer> getProviderVersions() {
        Integer providerVersion = fProviderVersion;
        if (providerVersion == null) {
            return Collections.emptyMap();
        }
        ImmutableMap.Builder<String, Integer> builder = new Builder<>();
        for (ITmfStateSystem ss : getStateSystems()) {
            builder.put(ss.getSSID(), providerVersion);
        }
        return builder.build();
    }

    /**
     * @since 2.0
     */
    @Override
    public boolean waitForInitialization() {
        try {
            fInitialized.await();
        } catch (InterruptedException e) {
            return false;
        }
        return fInitializationSucceeded;
    }

    @Override
    protected void onFail() {
        super.onFail();
        // Make sure any analysis waiting for initialization can continue
        if (fInitialized.getCount() > 0) {
            analysisReady(false);
        }
    }

    /**
     * @since 2.0
     */
    @Override
    public boolean isQueryable(long ts) {
        /*
         * Return true if there is no state provider available (the analysis is not
         * being built)
         */
        ITmfStateProvider provider = fStateProvider;
        if (provider == null) {
            return true;
        }
        return ts <= provider.getLatestSafeTime();
    }

    // ------------------------------------------------------------------------
    // TmfAbstractAnalysisModule
    // ------------------------------------------------------------------------

    /**
     * Get the file where to save the results of the analysis
     *
     * @return The file to save the results in
     * @since 2.3
     */
    @VisibleForTesting
    protected @Nullable File getSsFile() {
        ITmfTrace trace = getTrace();
        if (trace == null) {
            return null;
        }
        String directory = TmfTraceManager.getSupplementaryFileDir(trace);
        File htFile = new File(directory + getSsFileName());
        return htFile;
    }

    @Override
    protected boolean executeAnalysis(@Nullable final IProgressMonitor monitor) {
        IProgressMonitor mon = (monitor == null ? new NullProgressMonitor() : monitor);
        final ITmfStateProvider provider = createStateProvider();
        fProviderVersion = provider.getVersion();

        String id = getId();

        /*
         * FIXME: State systems should make use of the monitor, to be cancelled
         */
        try (ScopeLog log = new ScopeLog(LOGGER, Level.FINE, "StateSystemAnalysis:executing", "id", id)) { //$NON-NLS-1$ //$NON-NLS-2$
            /* Get the state system according to backend */
            StateSystemBackendType backend = getBackendType();

            ITmfTrace trace = getTrace();
            if (trace == null) {
                // Analysis was cancelled in the meantime
                analysisReady(false);
                return false;
            }
            File htFile;
            switch (backend) {
            case FULL:
                htFile = getSsFile();
                if (htFile == null) {
                    return false;
                }
                createFullHistory(id, provider, htFile);
                break;
            case PARTIAL:
                htFile = getSsFile();
                if (htFile == null) {
                    return false;
                }
                createPartialHistory(id, provider, htFile);
                break;
            case INMEM:
                createInMemoryHistory(id, provider);
                break;
            case NULL:
                createNullHistory(id, provider);
                break;
            default:
                break;
            }
        } catch (TmfTraceException e) {
            analysisReady(false);
            return false;
        }
        return !mon.isCanceled();
    }

    /**
     * Make the module available and set whether the initialization succeeded or
     * not. If not, no state system is available and
     * {@link #waitForInitialization()} should return false.
     *
     * @param success
     *            True if the initialization succeeded, false otherwise
     */
    private void analysisReady(boolean succeeded) {
        fInitializationSucceeded = succeeded;
        fInitialized.countDown();
    }

    @Override
    protected void canceling() {
        ITmfEventRequest req = fRequest;
        if ((req != null) && (!req.isCompleted())) {
            req.cancel();
        }
    }

    @Override
    public void dispose() {
        super.dispose();
        if (fStateSystem != null) {
            fStateSystem.dispose();
        }
    }

    // ------------------------------------------------------------------------
    // History creation methods
    // ------------------------------------------------------------------------

    /*
     * Load the history file matching the target trace. If the file already exists,
     * it will be opened directly. If not, it will be created from scratch.
     */
    private void createFullHistory(String id, ITmfStateProvider provider, File htFile) throws TmfTraceException {

        /* If the target file already exists, do not rebuild it uselessly */
        // TODO for now we assume it's complete. Might be a good idea to check
        // at least if its range matches the trace's range.

        if (htFile.exists()) {
            /* Load an existing history */
            final int version = provider.getVersion();
            try {
                IStateHistoryBackend backend = StateHistoryBackendFactory.createHistoryTreeBackendExistingFile(
                        id, htFile, version);
                fStateSystem = StateSystemFactory.newStateSystem(backend, false);
                analysisReady(true);
                return;
            } catch (IOException e) {
                /*
                 * There was an error opening the existing file. Perhaps it was corrupted,
                 * perhaps it's an old version? We'll just fall-through and try to build a new
                 * one from scratch instead.
                 */
            }
        }

        /* Size of the blocking queue to use when building a state history */
        final int QUEUE_SIZE = 10000;

        try {
            IStateHistoryBackend backend = StateHistoryBackendFactory.createHistoryTreeBackendNewFile(
                    id, htFile, provider.getVersion(), provider.getStartTime(), QUEUE_SIZE);
            fStateSystem = StateSystemFactory.newStateSystem(backend);
            provider.assignTargetStateSystem(fStateSystem);
            build(provider);
        } catch (IOException e) {
            /*
             * If it fails here however, it means there was a problem writing to the disk,
             * so throw a real exception this time.
             */
            throw new TmfTraceException(e.toString(), e);
        }
    }

    /*
     * Create a new state system backed with a partial history. A partial history is
     * similar to a "full" one (which you get with {@link #newFullHistory}), except
     * that the file on disk is much smaller, but queries are a bit slower.
     *
     * Also note that single-queries are implemented using a full-query underneath,
     * (which are much slower), so this might not be a good fit for a use case where
     * you have to do lots of single queries.
     */
    private void createPartialHistory(String id, ITmfStateProvider provider, File htPartialFile)
            throws TmfTraceException {
        /*
         * The order of initializations is very tricky (but very important!) here. We
         * need to follow this pattern: (1 is done before the call to this method)
         * <ol>
         * <li>Instantiate realStateProvider</li>
         * <li>Instantiate realBackend</li>
         * <li>Instantiate partialBackend, with prereqs:
         * <ol>
         * <li>Instantiate partialProvider, via realProvider.getNew()</li>
         * <li>Instantiate nullBackend (partialSS's backend)</li>
         * <li>Instantiate partialSS</li>
         * <li>partialProvider.assignSS(partialSS)</li>
         * </ol>
         * <li>Instantiate realSS</li>
         * <li>partialSS.assignUpstream(realSS)</li>
         * <li>realProvider.assignSS (realSS)</li>
         * <li>Call HistoryBuilder(realProvider, realSS, partialBackend) to build the
         * thing.</li></li>
         */

        /* Size of the blocking queue to use when building a state history */
        final int QUEUE_SIZE = 10000;

        final long granularity = 50000;

        /* 2 */
        IStateHistoryBackend realBackend = null;
        try {
            realBackend = StateHistoryBackendFactory.createHistoryTreeBackendNewFile(
                    id, htPartialFile, provider.getVersion(), provider.getStartTime(), QUEUE_SIZE);
        } catch (IOException e) {
            throw new TmfTraceException(e.toString(), e);
        }

        /* 3a */
        ITmfStateProvider partialProvider = provider.getNewInstance();

        /* 3b-3c, constructor automatically uses a NullBackend */
        PartialStateSystem pss = new PartialStateSystem();

        /* 3d */
        partialProvider.assignTargetStateSystem(pss);

        /* 3 */
        IStateHistoryBackend partialBackend = new PartialHistoryBackend(id + ".partial", partialProvider, pss, realBackend, granularity); //$NON-NLS-1$

        /* 4 */
        ITmfStateSystemBuilder realSS = StateSystemFactory.newStateSystem(partialBackend);

        /* 5 */
        pss.assignUpstream(realSS);

        /* 6 */
        provider.assignTargetStateSystem(realSS);

        /* 7 */
        fStateSystem = realSS;

        build(provider);
    }

    /*
     * Create a new state system using a null history back-end. This means that no
     * history intervals will be saved anywhere, and as such only {@link
     * ITmfStateSystem#queryOngoingState} will be available.
     */
    private void createNullHistory(String id, ITmfStateProvider provider) {
        IStateHistoryBackend backend = StateHistoryBackendFactory.createNullBackend(id);
        fStateSystem = StateSystemFactory.newStateSystem(backend);
        provider.assignTargetStateSystem(fStateSystem);
        build(provider);
    }

    /*
     * Create a new state system using in-memory interval storage. This should only
     * be done for very small state system, and will be naturally limited to 2^31
     * intervals.
     */
    private void createInMemoryHistory(String id, ITmfStateProvider provider) {
        IStateHistoryBackend backend = StateHistoryBackendFactory.createInMemoryBackend(id, provider.getStartTime());
        fStateSystem = StateSystemFactory.newStateSystem(backend);
        provider.assignTargetStateSystem(fStateSystem);
        build(provider);
    }

    private void disposeProvider(boolean deleteFiles) {
        ITmfStateProvider provider = fStateProvider;
        boolean shouldDeleteFiles = deleteFiles;
        if (provider != null) {
            provider.dispose();
            Throwable failureCause = provider.getFailureCause();
            /*
             * Fail the analysis if the provider failed and force the file deletion
             */
            if (failureCause != null) {
                fail(failureCause);
                shouldDeleteFiles = true;
            }
        }
        fStateProvider = null;
        if (shouldDeleteFiles && (fStateSystem != null)) {
            fStateSystem.removeFiles();
        }
        completingBuild(shouldDeleteFiles);
    }

    /**
     * The state system and analysis construction has completed. The state provider
     * has been disposed, either at the end of the analysis, when it is cancelled or
     * has failed. Children classes should override this if they need to close or
     * dispose of anything at the end of the analysis execution.
     *
     * @param deleteFiles
     *            If <code>true</code>, files that were built during the execution
     *            should be deleted, otherwise they should be saved.
     * @since 3.3
     */
    protected void completingBuild(boolean deleteFiles) {
        // Nothing to do, classes may override this
    }

    private void build(ITmfStateProvider provider) {
        if (fStateSystem == null) {
            throw new IllegalArgumentException();
        }

        /*
         * Note we have to do this before fStateProvider is assigned. After that, the
         * signal listener below will start sending real trace events through the state
         * provider.
         */
        loadInitialState(provider);

        /* Continue on initializing the event request to read trace events. */
        ITmfEventRequest request = fRequest;
        if ((request != null) && (!request.isCompleted())) {
            request.cancel();
        }

        fTimeRange = TmfTimeRange.ETERNITY;
        final ITmfTrace trace = provider.getTrace();
        if (!isCompleteTrace(trace)) {
            fTimeRange = trace.getTimeRange();
        }

        fStateProvider = provider;
        synchronized (fRequestSyncObj) {
            startRequest();
            request = fRequest;
        }

        /*
         * The state system object is now created, we can consider this module
         * "initialized" (components can retrieve it and start doing queries).
         */
        analysisReady(true);

        /*
         * Block the executeAnalysis() construction is complete (so that the progress
         * monitor displays that it is running).
         */
        try {
            if (request != null) {
                request.waitForCompletion();
                if (request.isFailed()) {
                    Throwable failureCause = request.getFailureCause();
                    if (failureCause != null) {
                        fail(failureCause);
                    } else {
                        fail(new RuntimeException("Event request failed without a cause")); //$NON-NLS-1$
                    }
                }
            }
        } catch (InterruptedException e) {
            fail(e);
        }
    }

    /**
     * Batch-load the initial state, if there is any.
     */
    private void loadInitialState(ITmfStateProvider provider) {
        final ITmfTrace trace = provider.getTrace();
        File path = new File(trace.getPath());
        path = path.isDirectory() ? path : path.getParentFile();
        if (path == null) {
            return;
        }
        for (ITmfStateSystem ss : getStateSystems()) {
            if (ss instanceof ITmfStateSystemBuilder) {
                StateSnapshot snapshot = StateSnapshot.read(path.toPath(), ss.getSSID());
                if (snapshot == null || provider.getVersion() != snapshot.getVersion()) {
                    /*
                     * No statedump found, nothing to pre-load or Do not load the statedump if its
                     * version does not match the current provider.
                     */

                    continue;
                }

                List<List<String>> paths = new ArrayList<>();
                /* create quark list */
                for (Entry<List<String>, ITmfStateInterval> attributeSnapshot : snapshot.getStates().entrySet()) {
                    List<String> attributePath = Objects.requireNonNull(attributeSnapshot.getKey());
                    ITmfStateInterval state = Objects.requireNonNull(attributeSnapshot.getValue());
                    while (paths.size() <= state.getAttribute()) {
                        paths.add(Collections.singletonList("Dummy" + paths.size())); //$NON-NLS-1$
                    }
                    paths.set(state.getAttribute(), attributePath);
                }
                /*
                 * Populate quarks in order
                 */
                int i = 0;
                for (List<String> attributePath : paths) {
                    int quark = ((ITmfStateSystemBuilder) ss).getQuarkAbsoluteAndAdd(attributePath.toArray(new String[attributePath.size()]));
                    if (i != quark) {
                        Activator.logWarning("Quark for analysis " + getClass().getCanonicalName() + " not the same ( " + quark + " != " + i + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
                    }
                    i++;
                }

                /* Load the statedump into the statesystem */
                for (ITmfStateInterval interval : snapshot.getStates().values()) {
                    Object initialState = interval.getValue();
                    int attribute = interval.getAttribute();
                    provider.addFutureEvent(interval.getStartTime(), initialState, attribute, FutureEventType.MODIFICATION);
                    if (interval.getEndTime() != Long.MIN_VALUE) {
                        provider.addFutureEvent(interval.getEndTime() + 1, (Object) null, attribute, FutureEventType.MODIFICATION);
                    }
                }
            }
        }
    }

    /**
     * A request to build a state system from a state provider
     *
     * @since 2.3
     */
    @VisibleForTesting
    protected class StateSystemEventRequest extends TmfEventRequest {

        private final ITmfStateProvider sci;
        private final ITmfTrace trace;

        /**
         * Constructor
         *
         * @param sp
         *            The state provider used to build the state system
         * @param timeRange
         *            The requested time range for the request
         * @param index
         *            The event number at which to start the request
         */
        public StateSystemEventRequest(ITmfStateProvider sp, TmfTimeRange timeRange, int index) {
            super(ITmfEvent.class,
                    timeRange,
                    index,
                    ITmfEventRequest.ALL_DATA,
                    ITmfEventRequest.ExecutionType.BACKGROUND,
                    TmfStateSystemAnalysisModule.this.getDependencyLevel());
            this.sci = sp;
            trace = sci.getTrace();

        }

        /**
         * Constructor
         *
         * @param sp
         *            The state provider used to build the state system
         * @param timeRange
         *            The requested time range for the request
         * @param index
         *            The event number at which to start the request
         * @param nbRequested
         *            The number of events requested
         * @since 4.1
         */
        public StateSystemEventRequest(ITmfStateProvider sp, TmfTimeRange timeRange, int index, int nbRequested) {
            super(ITmfEvent.class,
                    timeRange,
                    index,
                    nbRequested,
                    ITmfEventRequest.ExecutionType.BACKGROUND,
                    TmfStateSystemAnalysisModule.this.getDependencyLevel());
            sci = sp;
            trace = sci.getTrace();

        }

        @Override
        public void handleData(final ITmfEvent event) {
            super.handleData(event);
            processEvent(event, trace);
        }

        private void processEvent(final ITmfEvent event, ITmfTrace tmfTrace) {
            if (event.getTrace() == tmfTrace) {
                sci.processEvent(event);
            } else if (tmfTrace instanceof TmfExperiment) {
                /*
                 * If the request is for an experiment, check if the event is from one of the
                 * child trace
                 */
                for (ITmfTrace childTrace : ((TmfExperiment) tmfTrace).getTraces()) {
                    processEvent(event, childTrace);
                }
            }
        }

        @Override
        public void handleSuccess() {
            super.handleSuccess();
            if (isCompleteTrace(trace)) {
                disposeProvider(false);
            } else {
                fNbRead += getNbRead();
                synchronized (fRequestSyncObj) {
                    final TmfTimeRange timeRange = fTimeRange;
                    if (timeRange != null && getRange().getEndTime().toNanos() < timeRange.getEndTime().toNanos()) {
                        startRequest();
                    }
                }
            }
        }

        @Override
        public void handleCancel() {
            super.handleCancel();
            disposeProvider(true);
        }

        @Override
        public void handleFailure() {
            super.handleFailure();
            disposeProvider(true);
        }

    }

    // ------------------------------------------------------------------------
    // ITmfAnalysisModuleWithStateSystems
    // ------------------------------------------------------------------------

    @Override
    @Nullable
    public ITmfStateSystem getStateSystem(String id) {
        if (id.equals(getId())) {
            return fStateSystem;
        }
        return null;
    }

    @Override
    public @NonNull Iterable<@NonNull ITmfStateSystem> getStateSystems() {
        ITmfStateSystemBuilder stateSystem = fStateSystem;
        if (stateSystem == null) {
            return Collections.emptySet();
        }
        return Collections.singleton(stateSystem);
    }

    /**
     * Signal handler for the TmfTraceRangeUpdatedSignal signal
     *
     * @param signal
     *            The incoming signal
     */
    @TmfSignalHandler
    public void traceRangeUpdated(final TmfTraceRangeUpdatedSignal signal) {
        fTimeRange = signal.getRange();
        ITmfStateProvider stateProvider = fStateProvider;
        synchronized (fRequestSyncObj) {
            if (signal.getTrace() == getTrace() && stateProvider != null && stateProvider.getAssignedStateSystem() != null) {
                ITmfEventRequest request = fRequest;
                if ((request == null) || request.isCompleted()) {
                    startRequest();
                }
            }
        }
    }

    private void startRequest() {
        ITmfStateProvider stateProvider = fStateProvider;
        TmfTimeRange timeRange = fTimeRange;
        if (stateProvider == null || timeRange == null) {
            return;
        }
        ITmfEventRequest request = createEventRequest(stateProvider, timeRange, fNbRead);
        stateProvider.getTrace().sendRequest(request);
        fRequest = request;
    }

    /**
     * Create a new event request
     *
     * @param stateProvider
     *            The state provider used to build the state system
     * @param timeRange
     *            The requested time range for the request
     * @param nbRead
     *            The event number at which to start the request
     * @return A new event request
     * @since 2.3
     */
    @VisibleForTesting
    protected ITmfEventRequest createEventRequest(ITmfStateProvider stateProvider, TmfTimeRange timeRange, int nbRead) {
        return new StateSystemEventRequest(stateProvider, timeRange, nbRead);
    }

    private static boolean isCompleteTrace(ITmfTrace trace) {
        return !(trace instanceof ITmfTraceCompleteness) || ((ITmfTraceCompleteness) trace).isComplete();
    }

    // ------------------------------------------------------------------------
    // ITmfPropertiesProvider
    // ------------------------------------------------------------------------

    /**
     * @since 2.0
     */
    @Override
    public @NonNull Map<@NonNull String, @NonNull String> getProperties() {
        Map<@NonNull String, @NonNull String> properties = super.getProperties();

        StateSystemBackendType backend = getBackendType();
        properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesBackend), backend.name());
        switch (backend) {
        case FULL:
        case PARTIAL:
            File htFile = getSsFile();
            if (htFile != null) {
                if (htFile.exists()) {
                    properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesFileSize), FileUtils.byteCountToDisplaySize(htFile.length()));
                } else {
                    properties.put(NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesFileSize), NonNullUtils.checkNotNull(Messages.TmfStateSystemAnalysisModule_PropertiesAnalysisNotExecuted));
                }
            }
            break;
        case INMEM:
        case NULL:
        default:
            break;

        }
        return properties;
    }
}
