/*******************************************************************************
 * Copyright (c) 2014, 2019 École Polytechnique de Montréal and others.
 *
 * 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:
 *   Bernd Hufmann - Initial API and implementation in TmfXYChartViewer
 *   Geneviève Bastien - Moved methods from TmfXYChartViewer to this interface
 *******************************************************************************/

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

import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.signal.TmfUiSignalThrottler;
import org.eclipse.tracecompass.tmf.ui.views.ITmfPinnable;

/**
 * Abstract class that extends {@link TmfViewer} that adds methods to
 * synchronize with a trace's time information.
 *
 * This class will be extended by viewers who require time information to update
 * their content.
 *
 * <pre>
 * It provides three times of data:
 *   - start and end time of the trace (available)
 *   - start, end and duration of the current time window, ie the visible time range
 *   - start and end of the time range selected
 * </pre>
 *
 * @author Bernd Hufmann
 * @author Geneviève Bastien
 */
public abstract class TmfTimeViewer extends TmfViewer implements ITmfTimeProvider, ITmfPinnable {

    /** Start time of trace */
    private long fStartTime;
    /** End time of trace */
    private long fEndTime;
    /** Start time of current time range */
    private long fWindowStartTime;
    /** End time of current time range */
    private long fWindowEndTime;
    /** Current begin time of selection range */
    private long fSelectionBeginTime;
    /** Current end of selection range */
    private long fSelectionEndTime;
    /** The trace that is displayed by this viewer */
    private ITmfTrace fTrace;
    /** A signal throttler for range updates */
    private final TmfUiSignalThrottler fTimeRangeSyncThrottle = new TmfUiSignalThrottler(this, 200);

    /**
     * Default constructor.
     */
    public TmfTimeViewer() {
        super();
    }

    /**
     * Constructor that initializes the parent of the viewer
     *
     * @param parent
     *            The parent composite that holds this viewer
     */
    public TmfTimeViewer(Composite parent) {
        this(parent, ""); //$NON-NLS-1$
    }

    /**
     * Constructor that initializes the parent of the viewer and that sets the
     * name of the viewer
     *
     * @param parent
     *            The parent composite that holds this viewer
     * @param name
     *            The name of the viewer
     */
    public TmfTimeViewer(Composite parent, String name) {
        init(parent, name);
    }

    // ------------------------------------------------------------------------
    // Getter/Setters
    // ------------------------------------------------------------------------

    /**
     * Sets the start time of the trace
     *
     * @param startTime
     *            The start time to set
     */
    protected void setStartTime(long startTime) {
        fStartTime = startTime;
    }

    /**
     * Sets the end time of the trace
     *
     * @param endTime
     *            The start time to set
     */
    protected void setEndTime(long endTime) {
        fEndTime = endTime;
    }

    /**
     * Sets the start time and end of the current time range window (visible
     * range)
     *
     * @param windowStartTime
     *            The start time to set
     * @param windowEndTime
     *            The start time to set
     * @since 1.0
     */
    protected void setWindowRange(long windowStartTime, long windowEndTime) {
        fWindowStartTime = windowStartTime;
        fWindowEndTime = windowEndTime;
    }

    /**
     * Sets the begin and end time of the selected range without sending the
     * {@link TmfSelectionRangeUpdatedSignal} signal.
     *
     * @param selectionBeginTime
     *            The begin time to set
     * @param selectionEndTime
     *            The end time to set
     *
     * @since 1.0
     */
    protected void setSelectionRange(long selectionBeginTime, long selectionEndTime) {
        fSelectionBeginTime = selectionBeginTime;
        fSelectionEndTime = selectionEndTime;
    }

    /**
     * Sets the trace that is displayed by this viewer.
     *
     * @param trace
     *            The trace to set
     */
    protected void setTrace(ITmfTrace trace) {
        fTrace = trace;
    }

    /**
     * Gets the trace that is displayed by this viewer.
     *
     * @return the trace
     * @since 3.3
     */
    @Override
    public ITmfTrace getTrace() {
        return fTrace;
    }

    // ------------------------------------------------------------------------
    // ITmfTimeProvider
    // ------------------------------------------------------------------------

    @Override
    public long getStartTime() {
        return fStartTime;
    }

    @Override
    public long getEndTime() {
        return fEndTime;
    }

    @Override
    public long getWindowStartTime() {
        return fWindowStartTime;
    }

    @Override
    public long getWindowEndTime() {
        return fWindowEndTime;
    }

    @Override
    public long getWindowDuration() {
        return getWindowEndTime() - getWindowStartTime();
    }

    @Override
    public long getSelectionBeginTime() {
        return fSelectionBeginTime;
    }

    @Override
    public long getSelectionEndTime() {
        return fSelectionEndTime;
    }

    @Override
    public void updateSelectionRange(final long currentBeginTime, final long currentEndTime) {
        if (fTrace != null) {
            setSelectionRange(currentBeginTime, currentEndTime);

            final ITmfTimestamp startTimestamp = TmfTimestamp.fromNanos(getSelectionBeginTime());
            final ITmfTimestamp endTimestamp = TmfTimestamp.fromNanos(getSelectionEndTime());

            TmfSelectionRangeUpdatedSignal signal = new TmfSelectionRangeUpdatedSignal(this, startTimestamp, endTimestamp, getTrace());
            broadcast(signal);
        }
    }

    @Override
    public void updateWindow(long windowStartTime, long windowEndTime) {

        setWindowRange(windowStartTime, windowEndTime);

        // Build the new time range; keep the current time
        TmfTimeRange timeRange = new TmfTimeRange(
                TmfTimestamp.fromNanos(getWindowStartTime()),
                TmfTimestamp.fromNanos(getWindowEndTime()));

        // Send the signal
        TmfWindowRangeUpdatedSignal signal = new TmfWindowRangeUpdatedSignal(this, timeRange, getTrace());
        fTimeRangeSyncThrottle.queue(signal);
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------
    /**
     * A Method to load a trace into the viewer.
     *
     * @param trace
     *            A trace to apply in the viewer
     */
    public void loadTrace(ITmfTrace trace) {
        fTrace = trace;
        if (trace == null) {
            return;
        }
        TmfTraceContext ctx = TmfTraceManager.getInstance().getTraceContext(trace);
        long selectionStart = ctx.getSelectionRange().getStartTime().toNanos();
        long selectionEnd = ctx.getSelectionRange().getEndTime().toNanos();
        TmfTimeRange windowRange = ctx.getWindowRange();

        long windowStartTime = windowRange.getStartTime().toNanos();
        long windowEndTime = windowRange.getEndTime().toNanos();
        long startTime = fTrace.getStartTime().toNanos();
        long endTime = fTrace.getEndTime().toNanos();

        setSelectionRange(selectionStart, selectionEnd);

        setStartTime(startTime);
        setWindowRange(windowStartTime, windowEndTime);
        setEndTime(endTime);
    }

    /**
     * Resets the content of the viewer
     */
    public void reset() {
        // Reset the internal data
        setSelectionRange(0, 0);
        setStartTime(0);
        setWindowRange(0, 0);
        setEndTime(0);
        setTrace(null);
    }

    @Override
    public synchronized void setPinned(ITmfTrace trace) {
        if (trace != null) {
            /* Ignore relevant inbound signals */
            TmfSignalManager.addIgnoredInboundSignal(this, TmfTraceOpenedSignal.class);
            TmfSignalManager.addIgnoredInboundSignal(this, TmfTraceSelectedSignal.class);

            if (!trace.equals(fTrace)) {
                loadTrace(trace);
            }
        } else {
            /* Handle relevant inbound signals */
            TmfSignalManager.removeIgnoredInboundSignal(this, TmfTraceOpenedSignal.class);
            TmfSignalManager.removeIgnoredInboundSignal(this, TmfTraceSelectedSignal.class);

            ITmfTrace activeTrace = TmfTraceManager.getInstance().getActiveTrace();
            if (activeTrace != null && !activeTrace.equals(fTrace)) {
                loadTrace(activeTrace);
            } else {
                refresh();
            }
        }
    }

    // ------------------------------------------------------------------------
    // Signal Handler
    // ------------------------------------------------------------------------

    /**
     * Signal handler for handling of the trace opened signal.
     *
     * @param signal
     *            The trace opened signal {@link TmfTraceOpenedSignal}
     */
    @TmfSignalHandler
    public void traceOpened(TmfTraceOpenedSignal signal) {
        fTrace = signal.getTrace();
        loadTrace(getTrace());
    }

    /**
     * Signal handler for handling of the trace selected signal.
     *
     * @param signal
     *            The trace selected signal {@link TmfTraceSelectedSignal}
     */
    @TmfSignalHandler
    public void traceSelected(TmfTraceSelectedSignal signal) {
        if (fTrace != signal.getTrace()) {
            fTrace = signal.getTrace();
            loadTrace(getTrace());
        }
    }

    /**
     * Signal handler for handling of the trace closed signal.
     *
     * @param signal
     *            The trace closed signal {@link TmfTraceClosedSignal}
     */
    @TmfSignalHandler
    public void traceClosed(@Nullable TmfTraceClosedSignal signal) {

        if (signal == null || signal.getTrace() != fTrace) {
            return;
        }

        // Reset the internal data
        fTrace = null;
        reset();
    }

    /**
     * Signal handler for handling of the selected range signal.
     *
     * @param signal
     *            The {@link TmfSelectionRangeUpdatedSignal}
     * @since 1.0
     */
    @TmfSignalHandler
    public void selectionRangeUpdated(@Nullable TmfSelectionRangeUpdatedSignal signal) {
        final ITmfTrace trace = fTrace;
        if (signal != null && (signal.getSource() != this) && (trace != null)) {
            TmfTraceContext ctx = TmfTraceManager.getInstance().getTraceContext(trace);
            long selectedTime = ctx.getSelectionRange().getStartTime().toNanos();
            long selectedEndTime = ctx.getSelectionRange().getEndTime().toNanos();
            setSelectionRange(selectedTime, selectedEndTime);
        }
    }

    /**
     * Signal handler for handling of the window range signal.
     *
     * @param signal
     *            The {@link TmfWindowRangeUpdatedSignal}
     * @since 1.0
     */
    @TmfSignalHandler
    public void windowRangeUpdated(@Nullable TmfWindowRangeUpdatedSignal signal) {
        final ITmfTrace trace = fTrace;
        if (signal != null && trace != null) {
            TmfTraceContext ctx = TmfTraceManager.getInstance().getTraceContext(trace);

            // Validate the time range
            TmfTimeRange range = ctx.getWindowRange().getIntersection(trace.getTimeRange());
            if (range == null) {
                return;
            }

            if (signal.getSource() != this) {
                // Update the time range
                long windowStartTime = range.getStartTime().toNanos();
                long windowEndTime = range.getEndTime().toNanos();

                setWindowRange(windowStartTime, windowEndTime);
            }
        }
    }

    /**
     * Signal handler for handling of the trace range updated signal.
     *
     * @param signal
     *            The trace range signal {@link TmfTraceRangeUpdatedSignal}
     */
    @TmfSignalHandler
    public void traceRangeUpdated(@Nullable TmfTraceRangeUpdatedSignal signal) {
        if (signal == null || signal.getTrace() != fTrace) {
            return;
        }

        TmfTimeRange fullRange = signal.getRange();

        long traceStartTime = fullRange.getStartTime().toNanos();
        long traceEndTime = fullRange.getEndTime().toNanos();

        setStartTime(traceStartTime);
        setEndTime(traceEndTime);
    }

    /**
     * Signal handler for handling of the trace updated signal.
     *
     * @param signal
     *            The trace updated signal {@link TmfTraceUpdatedSignal}
     */
    @TmfSignalHandler
    public void traceUpdated(@Nullable TmfTraceUpdatedSignal signal) {
        if (signal == null || signal.getTrace() != fTrace) {
            return;
        }
        TmfTimeRange fullRange = signal.getTrace().getTimeRange();

        long traceStartTime = fullRange.getStartTime().toNanos();
        long traceEndTime = fullRange.getEndTime().toNanos();

        setStartTime(traceStartTime);
        setEndTime(traceEndTime);
    }

}
