/*******************************************************************************
 * Copyright (c) 2015 Ericsson
 *
 * 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:
 *   Mathieu Denis - Initial API and implementation
 *   Alexis Cabana-Loriaux - Extract the class in a compilation unit
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.ui.viewers.statistics;

import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.tracecompass.internal.tmf.ui.viewers.piecharts.model.TmfPieChartStatisticsModel;
import org.eclipse.tracecompass.internal.tmf.ui.viewers.statistics.model.TmfStatisticsTree;
import org.eclipse.tracecompass.internal.tmf.ui.viewers.statistics.model.TmfStatisticsTreeManager;
import org.eclipse.tracecompass.internal.tmf.ui.viewers.statistics.model.TmfStatisticsTreeNode;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.tmf.core.statistics.ITmfStatistics;
import org.eclipse.tracecompass.tmf.core.statistics.TmfStatisticsEventTypesModule;
import org.eclipse.tracecompass.tmf.core.statistics.TmfStatisticsModule;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;

/**
 * Class used to update the Statistics view. Normally, it should only be used by
 * this class
 *
 * @author Mathieu Denis
 */
class StatisticsUpdateJob extends Job {

    private final ITmfTrace fJobTrace;
    private final boolean fIsGlobal;
    private final TmfStatisticsModule fStatsMod;
    private final TmfStatisticsViewer fViewer;

    /**
     * The delay (in ms) between each update in live-reading mode
     */
    private static final long LIVE_UPDATE_DELAY = 1000;

    private TmfTimeRange fTimerange;

    /**
     * @param name
     *            The name of the working job
     * @param trace
     *            The trace to query
     * @param isGlobal
     *            If the query is for the global time-range or a selection
     *            time-range
     * @param timerange
     *            The timerange of
     * @param statsMod
     *            The statistics module of the trace
     * @param viewer
     *            The viewer to update
     */
    public StatisticsUpdateJob(String name, ITmfTrace trace, boolean isGlobal, TmfTimeRange timerange, TmfStatisticsModule statsMod, TmfStatisticsViewer viewer) {
        super(name);
        fJobTrace = trace;
        fIsGlobal = isGlobal;
        fTimerange = timerange;
        fStatsMod = statsMod;
        fViewer = viewer;
    }

    @Override
    protected IStatus run(IProgressMonitor monitor) {
        IStatus st = fStatsMod.schedule();
        if (!st.isOK()) {
            return st;
        }

        /* Wait until the analysis is ready to be queried */
        if (!fStatsMod.waitForInitialization()) {
            return Status.CANCEL_STATUS;
        }
        ITmfStatistics stats = fStatsMod.getStatistics();
        if (stats == null) {
            /* It should have worked, but didn't */
            throw new IllegalStateException();
        }

        /*
         * TODO Eventually this could be exposed through the
         * TmfStateSystemAnalysisModule directly.
         */
        ITmfStateSystem ss = fStatsMod.getStateSystem(TmfStatisticsEventTypesModule.ID);
        if (ss == null) {
            /*
             * It should be instantiated after the
             * statsMod.waitForInitialization() above.
             */
            throw new IllegalStateException();
        }

        /*
         * Periodically update the statistics while they are being built (or, if
         * the back-end is already completely built, it will skip over the
         * while() immediately.
         */
        long start = 0;
        long end = 0;
        boolean finished = false;
        do {
            /* This model update is done every second */
            if (monitor.isCanceled()) {
                fViewer.removeFromJobs(fIsGlobal, fJobTrace);
                return Status.CANCEL_STATUS;
            }
            finished = ss.waitUntilBuilt(LIVE_UPDATE_DELAY);
            TmfTimeRange localtimeRange = fTimerange;
            /*
             * The generic statistics are stored in nanoseconds, so we must make
             * sure the time range is scaled correctly.
             */
            start = localtimeRange.getStartTime().toNanos();
            end = localtimeRange.getEndTime().toNanos();

            Map<String, Long> map = stats.getEventTypesInRange(start, end);
            updateStats(map);
        } while (!finished);


        /* Query one last time for the final values */
        Map<String, Long> map = stats.getEventTypesInRange(start, end);
        updateStats(map);
        fViewer.refreshPieCharts(fIsGlobal, !fIsGlobal);
        /*
         * Remove job from map so that new range selection updates can be
         * processed.
         */
        fViewer.removeFromJobs(fIsGlobal, fJobTrace);
        return Status.OK_STATUS;
    }

    /*
     * Update the tree for a given trace
     */
    private void updateStats(Map<String, Long> eventsPerType) {

        final TmfStatisticsTree statsData = TmfStatisticsTreeManager.getStatTree(fViewer.getTreeID());
        if (statsData == null) {
            /* The stat tree has been disposed, abort mission. */
            return;
        }

        Map<String, Long> map = eventsPerType;
        String name = fJobTrace.getName();

        /**
         * <pre>
         * "Global", "partial", "total", etc., it's all very confusing...
         *
         * The base view shows the total count for the trace and for
         * each even types, organized in columns like this:
         *
         *                   |  Global  |  Time range |
         * trace name        |    A     |      B      |
         *    Event Type     |          |             |
         *       <event 1>   |    C     |      D      |
         *       <event 2>   |   ...    |     ...     |
         *         ...       |          |             |
         *
         * Here, we called the cells like this:
         *  A : GlobalTotal
         *  B : TimeRangeTotal
         *  C : GlobalTypeCount(s)
         *  D : TimeRangeTypeCount(s)
         * </pre>
         */

        if (map.isEmpty() && !fIsGlobal) {
            /* Reset all time range event counts (cells D) */
            TmfStatisticsTreeNode eventTypeNode = statsData.getNode(name, TmfStatisticsTree.HEADER_EVENT_TYPES);
            if (eventTypeNode != null) {
                eventTypeNode.resetTimeRangeValue();
            }
        } else {
            /* Fill in the event counts (either cells C or D) */
            for (Map.Entry<String, Long> entry : map.entrySet()) {
                statsData.setTypeCount(name, entry.getKey(), fIsGlobal, entry.getValue());
            }
        }

        /*
         * Calculate the totals (cell A or B, depending if isGlobal). We will
         * use the results of the previous request instead of sending another
         * one.
         */
        long globalTotal = 0;
        for (long val : map.values()) {
            globalTotal += val;
        }
        /* Update both the tree model and the piechart model */
        statsData.setTotal(name, fIsGlobal, globalTotal);
        TmfPieChartStatisticsModel model = fViewer.getPieChartModel();
        if (model != null) {
            model.setPieChartTypeCount(fIsGlobal, fJobTrace, eventsPerType);
        }
        /* notify that the viewer needs to be refreshed */
        fViewer.modelComplete(fIsGlobal);
    }
}
