/*******************************************************************************
 * Copyright (c) 2011, 2014 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:
 *   Francois Chouinard - Initial API and implementation
 *   Bernd Hufmann - Changed to updated histogram data model
 *   Patrick Tasse - Update for mouse wheel zoom
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ui.views.histogram;

import java.util.Objects;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;

/**
 * A histogram widget that displays the event distribution of a whole trace.
 * <p>
 * It also features a selected range window that can be dragged and zoomed.
 *
 * @version 1.1
 * @author Francois Chouinard
 */
public class FullTraceHistogram extends Histogram {

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    private final HistogramZoom fZoom;

    private long fRangeStartTime = 0L;
    private long fRangeDuration;

    // ------------------------------------------------------------------------
    // Construction
    // ------------------------------------------------------------------------

    /**
     * Full Constructor
     *
     * @param view A reference to the parent histogram view
     * @param parent A reference to the parent composite
     */
    public FullTraceHistogram(HistogramView view, Composite parent) {
        super(view, parent);
        fZoom = new HistogramZoom(this, getStartTime(), getTimeLimit());
    }

    @Override
    public void dispose() {
        super.dispose();
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------

    @Override
    public void clear() {
        fRangeStartTime = 0L;
        fRangeDuration = 0L;
        if (fZoom != null) {
            fZoom.setFullRange(0L, 0L);
            fZoom.setNewRange(0L, 0L);
        }
        super.clear();
    }

    /**
     * Sets the time range of the full histogram.
     *
     * @param startTime A start time
     * @param endTime A end time
     */
    public void setFullRange(long startTime, long endTime) {
        fZoom.setFullRange(startTime, endTime);
        fZoom.setNewRange(fRangeStartTime, fRangeDuration);
    }

    /**
     * Sets the selected time range.
     *
     * @param startTime The histogram start time
     * @param duration The histogram duration
     */
    public void setTimeRange(long startTime, long duration) {
        fRangeStartTime = startTime;
        fRangeDuration = duration;
        fZoom.setNewRange(fRangeStartTime, fRangeDuration);
        fDataModel.complete();
    }

    // ------------------------------------------------------------------------
    // MouseListener
    // ------------------------------------------------------------------------

    private int fStartDelta;
    private boolean fMouseMoved;

    @Override
    public void mouseDown(MouseEvent event) {
        if (fScaledData != null && fDragState == DRAG_NONE && fDataModel.getStartTime() < fDataModel.getEndTime()) {
            if (event.button == 2 || (event.button == 1 && (event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL)) {
                fDragState = DRAG_RANGE;
                fDragButton = event.button;
                int center = (int) (((fRangeStartTime + fRangeDuration / 2) - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration);
                fStartDelta = center - event.x;
                fMouseMoved = false;
                fCanvas.setCursor(fMoveCursor);
                return;
            } else if (event.button == 3) {
                fDragState = DRAG_ZOOM;
                fDragButton = event.button;
                long time = Math.min(getTimestamp(event.x), getEndTime());
                if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
                    if (time < fRangeStartTime + fRangeDuration / 2) {
                        fRangeStartTime = fRangeStartTime + fRangeDuration;
                    }
                } else {
                    fRangeStartTime = time;
                }
                fRangeDuration = time - fRangeStartTime;
                fCanvas.setCursor(fZoomCursor);
                fCanvas.redraw();
                return;
            }
        }
        super.mouseDown(event);
    }

    @Override
    public void mouseUp(MouseEvent event) {
        if (fDragState == DRAG_RANGE && event.button == fDragButton) {
            fDragState = DRAG_NONE;
            fDragButton = 0;
            if (!fMouseMoved) {
                // if single click without move, center on the click
                long startTime = getTimestamp(event.x) - fRangeDuration / 2;
                fRangeStartTime = Math.max(getStartTime(), Math.min(getEndTime() - fRangeDuration, startTime));
            }
            ((HistogramView) fParentView).updateTimeRange(fRangeStartTime, fRangeStartTime + fRangeDuration);
            fCanvas.setCursor(null);
            return;
        } else if (fDragState == DRAG_ZOOM && event.button == fDragButton) {
            fDragState = DRAG_NONE;
            fDragButton = 0;
            if (fRangeDuration < 0) {
                fRangeStartTime = fRangeStartTime + fRangeDuration;
                fRangeDuration = -fRangeDuration;
            }
            if (fRangeDuration > 0) {
                ((HistogramView) fParentView).updateTimeRange(fRangeStartTime, fRangeStartTime + fRangeDuration);
            } else {
                fRangeStartTime = fZoom.getStartTime();
                fRangeDuration = fZoom.getDuration();
                fCanvas.redraw();
            }
            fCanvas.setCursor(null);
            return;
        }
        super.mouseUp(event);
    }

    // ------------------------------------------------------------------------
    // MouseMoveListener
    // ------------------------------------------------------------------------

    @Override
    public void mouseMove(MouseEvent event) {
        if (fDragState == DRAG_RANGE) {
            int center = event.x + fStartDelta;
            long newStart = getTimestamp(center) - fRangeDuration / 2;
            fRangeStartTime = Math.max(getStartTime(), Math.min(getEndTime() - fRangeDuration, newStart));
            fCanvas.redraw();
            fMouseMoved = true;
            return;
        } else if (fDragState == DRAG_ZOOM) {
            long endTime = Math.max(getStartTime(), Math.min(getEndTime(), getTimestamp(event.x)));
            fRangeDuration = endTime - fRangeStartTime;
            fCanvas.redraw();
            return;
        }
        super.mouseMove(event);
    }

    // ------------------------------------------------------------------------
    // PaintListener
    // ------------------------------------------------------------------------

    @Override
    public void paintControl(PaintEvent event) {
        super.paintControl(event);

        Image image = Objects.requireNonNull((Image) fCanvas.getData(IMAGE_KEY));

        Image rangeRectangleImage = new Image(image.getDevice(), image, SWT.IMAGE_COPY);
        GC rangeWindowGC = new GC(rangeRectangleImage);

        if ((fScaledData != null) && (fRangeDuration != 0 || fDragState == DRAG_ZOOM)) {
            drawTimeRangeWindow(rangeWindowGC, fRangeStartTime, fRangeDuration);
        }

        // Draws the buffer image onto the canvas.
        event.gc.drawImage(rangeRectangleImage, 0, 0);

        rangeWindowGC.dispose();
        rangeRectangleImage.dispose();
    }

    /**
     * Get the histogram zoom
     * @return the histogram zoom
     */
    public HistogramZoom getZoom() {
        return fZoom;
    }
}
