/*******************************************************************************
 * Copyright (c) 2015, 2018 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:
 *   Patrick Tasse - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.ui.markers;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.swt.graphics.RGBA;
import org.eclipse.tracecompass.internal.tmf.ui.Messages;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.StateSystemUtils;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.statistics.TmfStateStatistics.Attributes;
import org.eclipse.tracecompass.tmf.core.statistics.TmfStatisticsEventTypesModule;
import org.eclipse.tracecompass.tmf.core.statistics.TmfStatisticsModule;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEventSource;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent;

/**
 * Marker event source for lost events.
 */
public class LostEventsMarkerEventSource implements IMarkerEventSource {

    private static final @NonNull String LOST_EVENTS = checkNotNull(Messages.MarkerEvent_LostEvents);

    private static final RGBA COLOR = new RGBA(255, 0, 0, 50);

    private final @NonNull ITmfTrace fTrace;
    private long[] fLastRequest;
    private @NonNull List<@NonNull IMarkerEvent> fLastMarkers = Collections.emptyList();

    /**
     * Constructor.
     *
     * @param trace
     *            the trace
     */
    public LostEventsMarkerEventSource(@NonNull ITmfTrace trace) {
        fTrace = trace;
    }

    @Override
    public @NonNull List<@NonNull String> getMarkerCategories() {
        return Arrays.asList(LOST_EVENTS);
    }

    @Override
    public synchronized @NonNull List<@NonNull IMarkerEvent> getMarkerList(@NonNull String category, long startTime, long endTime, long resolution, @NonNull IProgressMonitor monitor) {
        if (!category.equals(LOST_EVENTS)) {
            return Collections.emptyList();
        }
        ITmfStateSystem ss = getStateSystem();
        if (ss == null) {
            return Collections.emptyList();
        }
        int lostEventsQuark = getLostEventsQuark(ss);
        if (lostEventsQuark == -1) {
            return Collections.emptyList();
        }
        long[] request = new long[] { startTime, endTime, resolution, ss.getCurrentEndTime() };
        if (Arrays.equals(request, fLastRequest)) {
            return fLastMarkers;
        }
        List<@NonNull IMarkerEvent> markers = new ArrayList<>();
        try {
            long start = Math.max(startTime, ss.getStartTime());
            long end = Math.min(endTime, ss.getCurrentEndTime());
            if (start <= end) {
                /* Update start to ensure that the previous marker is included. */
                start = Math.max(start - 1, ss.getStartTime());
                /* Update end to ensure that the next marker is included. */
                long nextStartTime = ss.querySingleState(end, lostEventsQuark).getEndTime() + 1;
                end = Math.min(nextStartTime, ss.getCurrentEndTime());
                List<ITmfStateInterval> intervals = StateSystemUtils.queryHistoryRange(ss, lostEventsQuark, start, end, resolution, monitor);
                if (monitor.isCanceled()) {
                    return Collections.emptyList();
                }
                for (ITmfStateInterval interval : intervals) {
                    if (interval.getStateValue().isNull()) {
                        continue;
                    }
                    long lostEventsStartTime = interval.getStartTime();
                    /*
                     * The end time of the lost events range is the value of the
                     * attribute, not the end time of the interval.
                     */
                    long lostEventsEndTime = interval.getStateValue().unboxLong();
                    long duration = lostEventsEndTime - lostEventsStartTime;
                    IMarkerEvent marker = new MarkerEvent(null, lostEventsStartTime, duration, LOST_EVENTS, COLOR, null, false);
                    markers.add(marker);
                }
            }
        } catch (AttributeNotFoundException | StateSystemDisposedException e) {
            /* ignored */
        }
        fLastRequest = request;
        fLastMarkers = Collections.unmodifiableList(markers);
        return fLastMarkers;
    }

    private ITmfStateSystem getStateSystem() {
        TmfStatisticsModule module = TmfTraceUtils.getAnalysisModuleOfClass(fTrace, TmfStatisticsModule.class, TmfStatisticsModule.ID);
        if (module == null) {
            return null;
        }
        return module.getStateSystem(TmfStatisticsEventTypesModule.ID);
    }

    private static int getLostEventsQuark(ITmfStateSystem ss) {
        try {
            return ss.getQuarkAbsolute(Attributes.LOST_EVENTS);
        } catch (AttributeNotFoundException e) {
            return -1;
        }
    }
}
