/*******************************************************************************
 * Copyright (c) 2018, 2020 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
 *******************************************************************************/

package org.eclipse.tracecompass.analysis.counters.ui.swtbot.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarDropDownButton;
import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
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.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ctf.core.tests.shared.CtfTmfTestTraceUtils;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotUtils;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.views.xychart.XYDataProviderBaseTest;
import org.eclipse.tracecompass.tmf.ui.tests.shared.WaitUtils;
import org.eclipse.tracecompass.tmf.ui.viewers.xychart.linechart.TmfCommonXAxisChartViewer;
import org.eclipse.tracecompass.tmf.ui.views.xychart.TmfChartView;
import org.eclipse.ui.IViewPart;
import org.junit.Test;

/**
 * SWTBot tests for pin & clone for xy-charts using Counters view example.
 *
 * @author Bernd Hufmann
 */
public class NewCounterViewPinAndCloneTest extends XYDataProviderBaseTest {

    private static final String TRACETYPE_ID = "org.eclipse.linuxtools.lttng2.kernel.tracetype";
    private static final String TRACE_NAME = "kernel_vm";
    private static final @NonNull String COUNTERS_VIEW_TITLE = "Counters";
    private static final @NonNull String MAIN_SERIES_NAME = "kernel_vm/Ungrouped/minor_faults";
    private static final @NonNull String COUNTERS_VIEW_ID = "org.eclipse.tracecompass.analysis.counters.ui.views.countersview";
    private static final String PINNED_TO_TRACE_COUNTERS_VIEW_TITLE = COUNTERS_VIEW_TITLE + " <" + TRACE_NAME + ">";
    private static final String PINNED_TO_CTX_SWITCH_VIEW_TITLE = COUNTERS_VIEW_TITLE + " <context-switches-kernel>";

    private static final String PIN_VIEW_BUTTON_NAME = "Pin View";
    private static final String UNPIN_VIEW_BUTTON_NAME = "Unpin View";
    private static final String PIN_TO_PREFIX = "Pin to ";
    private static final String NEW_COUNTER_STACK_MENU = "New " + COUNTERS_VIEW_TITLE + " view";
    private static final String PINNED_TO_PREFIX = "pinned to ";
    private static final String CLONED_TRACE_SUFFIX = " | 2";
    private static final String CLONED_VIEW_TITLE_NAME = COUNTERS_VIEW_TITLE + " <" + TRACE_NAME + " | 2>";

    private static final int SECOND = 1000000000;
    private static final long KERNEL_START = 1363700740555978750L;
    private static final long KERNEL_TEST_START = KERNEL_START + SECOND;
    private static final long KERNEL_TEST_END = KERNEL_START + 2 * SECOND;
    private static final long KERNEL_TEST_INITIAL_END = KERNEL_START + 100000000L;

    private static final long KERNEL_INITIAL_END = KERNEL_START + 100000000L;

    private static final @NonNull TmfTimeRange RANGE = new TmfTimeRange(TmfTimestamp.fromNanos(KERNEL_TEST_START), TmfTimestamp.fromNanos(KERNEL_TEST_END));

    /**
     * Ensure the data displayed in the chart viewer reflects the tree viewer's
     * selected entries.
     */
    @Test
    public void testPinSingleTrace() {
        SWTBotView originalViewBot = getSWTBotView();

        // ensure that the view name is correct before
        assertEquals(COUNTERS_VIEW_TITLE, originalViewBot.getTitle());

        // ensure that the pin drop down is present, pin the view.
        fBot.waitUntil(new DefaultCondition() {
            WidgetNotFoundException fException;

            @Override
            public boolean test() throws Exception {
                try {
                    SWTBotToolbarDropDownButton toolbarDropDownButton = originalViewBot.toolbarDropDownButton(PIN_VIEW_BUTTON_NAME);
                    toolbarDropDownButton.menuItem(PIN_TO_PREFIX + getTestTrace().getName()).click();
                    return true;
                } catch (WidgetNotFoundException e) {
                    fException = e;
                    return false;
                }
            }

            @Override
            public String getFailureMessage() {
                return "Traces not available in toolbar drop down menu: " + fException;
            }
        });

        // Ensure that the view has been renamed. Get the view by title and ensure i    private static final int SECOND = 1000000000;
        // has the same widget as there is a renaming bug.
        assertOriginalViewTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);

        originalViewBot.toolbarButton(UNPIN_VIEW_BUTTON_NAME).click();

        // Ensure that the view has been renamed. Get the view by title and ensure it
        // has the same widget as there is a renaming bug.
        assertOriginalViewTitle(COUNTERS_VIEW_TITLE);

        // Ensure that the pin button is present, pin the view.
        originalViewBot.toolbarButton(PIN_VIEW_BUTTON_NAME).click();

        // Ensure that the view has been renamed. Get the view by title and ensure it
        // has the same widget as there is a renaming bug.
        assertOriginalViewTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);

        // Ensure that the pin button is present, unpin the view.
        originalViewBot.toolbarButton(UNPIN_VIEW_BUTTON_NAME).click();
        assertOriginalViewTitle(COUNTERS_VIEW_TITLE);
    }

    /**
     * Test the behavior with two traces.
     */
    @Test
    public void testPinTwoTraces() {
        SWTBotView originalViewBot = getSWTBotView();

        ITmfTrace activeTrace = TmfTraceManager.getInstance().getActiveTrace();
        assertNotNull(activeTrace);
        ITmfTrace kernelTestTrace = CtfTmfTestTraceUtils.getTrace(CtfTestTrace.CONTEXT_SWITCHES_KERNEL);
        SWTBotUtils.openTrace(TRACE_PROJECT_NAME, kernelTestTrace.getPath(), TRACETYPE_ID);

        /// Finish waiting for the trace to index
        WaitUtils.waitForJobs();
        // wait for the editor to be ready.
        fBot.editorByTitle(kernelTestTrace.getName());

        // Assert that the pin to drop down menuItems are present for both traces.
        fBot.waitUntil(new DefaultCondition() {
            WidgetNotFoundException fException;

            @Override
            public boolean test() throws Exception {
                try {
                    SWTBotToolbarDropDownButton toolbarDropDownButton = originalViewBot.toolbarDropDownButton(PIN_VIEW_BUTTON_NAME);
                    toolbarDropDownButton.menuItem(PIN_TO_PREFIX + kernelTestTrace.getName());
                    toolbarDropDownButton.menuItem(PIN_TO_PREFIX + getTestTrace().getName()).click();
                    return true;
                } catch (WidgetNotFoundException e) {
                    fException = e;
                    return false;
                }
            }

            @Override
            public String getFailureMessage() {
                return "Traces not available in toolbar drop down menu: " + fException;
            }
        });

        /*
         * Assert that the pinned view is the kernel_vm trace despite the active trace being
         * the context-switch trace.
         */
        assertOriginalViewTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);
        activeTrace = TmfTraceManager.getInstance().getActiveTrace();
        assertNotNull("There should be an active trace", activeTrace);
        assertEquals("context-switches-kernel should be the active trace", kernelTestTrace.getName(), activeTrace.getName());

        // Get the window range of the kernel trace
        TmfTraceManager traceManager = TmfTraceManager.getInstance();
        ITmfTrace kernelTrace = traceManager.getActiveTrace();
        assertNotNull(kernelTrace);

        // Switch back and forth
        SWTBotUtils.activateEditor(fBot, getTestTrace().getName());
        assertOriginalViewTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);

        SWTBotUtils.activateEditor(fBot, kernelTestTrace.getName());
        assertOriginalViewTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);

        IViewPart viewPart = originalViewBot.getViewReference().getView(false);
        assertTrue(viewPart instanceof TmfChartView);
        final TmfCommonXAxisChartViewer chartViewer = (TmfCommonXAxisChartViewer) getChartViewer(viewPart);
        assertNotNull(chartViewer);
        TmfSignalManager.dispatchSignal(new TmfWindowRangeUpdatedSignal(this, RANGE, kernelTrace));

        // Assert that the original views trace's window range did not change
        SWTBotUtils.activateEditor(fBot, getTestTrace().getName());
        SWTBotUtils.waitUntil(v -> (v.getWindowStartTime() == KERNEL_START && v.getWindowEndTime() == KERNEL_TEST_INITIAL_END), chartViewer, "Range of cloned view changed");

        // Unpin from active trace
        SWTBotUtils.activateEditor(fBot, kernelTrace.getName());
        originalViewBot.toolbarButton(UNPIN_VIEW_BUTTON_NAME).click();
        assertOriginalViewTitle(COUNTERS_VIEW_TITLE);

        originalViewBot.toolbarButton(PIN_VIEW_BUTTON_NAME).click();

        assertOriginalViewTitle(PINNED_TO_CTX_SWITCH_VIEW_TITLE);

        // Close the pinned trace
        SWTBotEditor kernelTable = fBot.editorByTitle(kernelTestTrace.getName());
        kernelTable.close();

        // Verify that view title is reset
        SWTBotUtils.waitUntil(v -> v.getReference().getPartName().equals(COUNTERS_VIEW_TITLE), originalViewBot, "View name didn't change");
        kernelTestTrace.dispose();
    }

    /**
     * Test the cloning feature.
     */
    @Test
    public void testCloneSingleTrace() {
        SWTBotView originalViewBot = getSWTBotView();
        SWTBotMenu cloneMenu = originalViewBot.viewMenu().menu(NEW_COUNTER_STACK_MENU);

        /*
         * Assert that the original editor was not renamed and that the cloned one
         * exists and is pinned to the kernel_vm trace.
         */
        cloneMenu.menu(PINNED_TO_PREFIX + getTestTrace().getName()).click();
        assertOriginalViewTitle(COUNTERS_VIEW_TITLE);
        SWTBotView clonedView = fBot.viewByTitle(PINNED_TO_TRACE_COUNTERS_VIEW_TITLE);
        assertEquals("Should not have created a new instance", 1, fBot.editors().size());
        clonedView.close();

         // Assert that a new instance is created.
        cloneMenu.menu(PINNED_TO_PREFIX + getTestTrace().getName() + " | new instance").click();
        assertOriginalViewTitle(COUNTERS_VIEW_TITLE);
        clonedView = fBot.viewByTitle(CLONED_VIEW_TITLE_NAME);
        assertEquals("Should have created a new instance", 2, fBot.editors().size());
        SWTBotEditor cloneEditor = fBot.editorByTitle(getTestTrace().getName() + CLONED_TRACE_SUFFIX);

        // Get the window range of the cloned trace
        TmfTraceManager traceManager = TmfTraceManager.getInstance();
        ITmfTrace cloneTrace = traceManager.getActiveTrace();
        assertNotNull(cloneTrace);

        // Go back to original trace, pin it
        SWTBotUtils.activateEditor(fBot, getTestTrace().getName());
        originalViewBot.toolbarButton(PIN_VIEW_BUTTON_NAME).click();

        // Assert that the cloned trace's window range did not change
        SWTBotUtils.activateEditor(fBot, cloneTrace.getName() + CLONED_TRACE_SUFFIX);
        IViewPart viewPart = clonedView.getViewReference().getView(false);
        assertTrue(viewPart instanceof TmfChartView);
        final TmfCommonXAxisChartViewer chartViewer = (TmfCommonXAxisChartViewer) getChartViewer(viewPart);
        assertNotNull(chartViewer);

        fBot.waitUntil(ConditionHelpers.xyViewerIsReadyCondition(chartViewer));
        TmfSignalManager.dispatchSignal(new TmfWindowRangeUpdatedSignal(this, RANGE, getTestTrace()));
        fBot.waitUntil(ConditionHelpers.xyViewerIsReadyCondition(chartViewer));
        SWTBotUtils.waitUntil(v -> (v.getWindowStartTime() == KERNEL_START && v.getWindowEndTime() == KERNEL_INITIAL_END), chartViewer, "Range of cloned view changed");

        cloneEditor.close();
    }

    private void assertOriginalViewTitle(String newName) {
        Widget expectedWidget = getSWTBotView().getWidget();
        assertNotNull(expectedWidget);
        SWTBotView actualView = fBot.viewByTitle(newName);
        Widget actualWidget = actualView.getWidget();
        assertNotNull(actualWidget);
        assertEquals(expectedWidget, actualWidget);
    }

    @Override
    protected @NonNull String getMainSeriesName() {
        return MAIN_SERIES_NAME;
    }

    @Override
    protected @NonNull String getTitle() {
        return COUNTERS_VIEW_TITLE;
    }

    @Override
    protected String getViewID() {
        return COUNTERS_VIEW_ID;
    }

    @Override
    protected ITmfTrace getTestTrace() {
        return CtfTmfTestTraceUtils.getTrace(CtfTestTrace.KERNEL_VM);
    }

    @Override
    protected void disposeTestTrace() {
        CtfTmfTestTraceUtils.dispose(CtfTestTrace.KERNEL_VM);
    }
}
