/*******************************************************************************
 * Copyright (c) 2014, 2019 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:
 *   Bernd Hufmann - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.lttng2.kernel.ui.swtbot.tests;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
import org.eclipse.tracecompass.tmf.core.analysis.Messages;
import org.eclipse.tracecompass.tmf.ctf.core.tests.shared.CtfTmfTestTraceUtils;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotUtils;
import org.eclipse.tracecompass.tmf.ui.tests.shared.WaitUtils;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * SWTBot stress test for opening and closing of traces.
 *
 * @author Bernd Hufmann
 */
@RunWith(SWTBotJunit4ClassRunner.class)
public class OpenTraceStressTest {

    private static final String TRACE_TYPE = "org.eclipse.linuxtools.lttng2.kernel.tracetype";
    private static final String KERNEL_PERSPECTIVE_ID = "org.eclipse.linuxtools.lttng2.kernel.ui.perspective";
    private static final @NonNull CtfTestTrace CTF_TRACE = CtfTestTrace.SYNC_DEST;
    private static final String TRACE_PROJECT_NAME = "test";

    private static SWTWorkbenchBot workbenchbot;

    /**
     * Test Class setup
     */
    @BeforeClass
    public static void init() {
        SWTBotUtils.initialize();

        /* Set up for swtbot */
        SWTBotPreferences.TIMEOUT = 20000; /* 20 second timeout */

        workbenchbot = new SWTWorkbenchBot();

        /* Close welcome view */
        SWTBotUtils.closeView("Welcome", workbenchbot);

        /* Switch perspectives */
        SWTBotUtils.switchToPerspective(KERNEL_PERSPECTIVE_ID);

        /* Finish waiting for eclipse to load */
        WaitUtils.waitForJobs();
    }

    /**
     * Main test case to test opening and closing of traces concurrently.
     */
    @Test
    public void testOpenAndCloseConcurrency() {
        SWTBotUtils.createProject(TRACE_PROJECT_NAME);

        File fTestFile = new File(CtfTmfTestTraceUtils.getTrace(CTF_TRACE).getPath());
        CtfTmfTestTraceUtils.dispose(CTF_TRACE);

        String path = fTestFile.getAbsolutePath();

        assertNotNull(fTestFile);
        assumeTrue(fTestFile.exists());

        /*
         *  This opening and closing of traces will trigger several threads for analysis which
         *  will be closed concurrently. There used to be a concurrency bug (447434) which should
         *  be fixed by now and this test should run without any exceptions.
         *
         *  Since the failure depends on timing it only happened sometimes before the bug fix
         *  using this test.
         */
        final MultiStatus status = new MultiStatus("lttn2.kernel.ui.swtbot.tests", IStatus.OK, null, null);
        IJobManager mgr = Job.getJobManager();
        JobChangeAdapter changeListener = new JobChangeAdapter() {
            @Override
            public void done(IJobChangeEvent event) {
                Job job = event.getJob();
                // Check for analysis failure
                String jobNamePrefix = NLS.bind(Messages.TmfAbstractAnalysisModule_RunningAnalysis, "");
                if ((job.getName().startsWith(jobNamePrefix)) && (job.getResult().getSeverity() == IStatus.ERROR)) {
                    status.add(job.getResult());
                }
            }
        };
        mgr.addJobChangeListener(changeListener);
        for (int i = 0; i < 10; i++) {
            SWTBotUtils.openTrace(TRACE_PROJECT_NAME, path, TRACE_TYPE, false);
            SWTBotUtils.openTrace(TRACE_PROJECT_NAME, path, TRACE_TYPE, false);
            SWTBotUtils.openTrace(TRACE_PROJECT_NAME, path, TRACE_TYPE, false);
            SWTBotUtils.openTrace(TRACE_PROJECT_NAME, path, TRACE_TYPE, false);
            SWTBotUtils.openTrace(TRACE_PROJECT_NAME, path, TRACE_TYPE, false);
            // Wait for editor so that threads have a chance to start
            workbenchbot.editorByTitle(fTestFile.getName());
            workbenchbot.closeAllEditors();

            if (!status.isOK()) {
                SWTBotUtils.deleteProject(TRACE_PROJECT_NAME, workbenchbot);
                fail(handleErrorStatus(status));
            }
        }
        SWTBotUtils.deleteProject(TRACE_PROJECT_NAME, workbenchbot);
    }

    private static String handleErrorStatus(MultiStatus status) {

        // Build a string with all the children status messages, exception
        // messages and stack traces
        StringBuilder sb = new StringBuilder();
        for (IStatus childStatus : status.getChildren()) {
            StringBuilder childSb = new StringBuilder();
            if (!childStatus.getMessage().isEmpty()) {
                childSb.append(childStatus.getMessage() + '\n');
            }

            Throwable childException = childStatus.getException();
            if (childException != null) {
                String reason = childException.getMessage();
                // Some system exceptions have no message
                if (reason == null) {
                    reason = childException.toString();
                }

                String stackMessage = getExceptionStackMessage(childException);
                if (stackMessage == null) {
                    stackMessage = reason;
                }

                childSb.append(stackMessage);
            }

            if (childSb.length() > 0) {
                childSb.insert(0, '\n');
                sb.append(childSb.toString());
            }
        }
        return sb.toString();
    }

    private static String getExceptionStackMessage(Throwable exception) {
        String stackMessage = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);
        exception.printStackTrace(ps);
        ps.flush();
        try {
            baos.flush();
            stackMessage = baos.toString();
        } catch (IOException e) {
        }

        return stackMessage;
    }
}