/*******************************************************************************
 * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
 *
 * 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.internal.provisional.analysis.lami.ui.handler;

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

import java.util.List;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysis;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiAnalysisReport;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.views.LamiReportViewFactory;
import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysis;
import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysisReport;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfCommonProjectElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfOnDemandAnalysisElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfReportsElement;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;

/**
 * The command handler for the "Run External Analysis" menu option.
 *
 * @author Alexandre Montplaisir
 */
public class RunAnalysisHandler extends AbstractHandler {

    @Override
    public boolean isEnabled() {
        final Object element = HandlerUtils.getSelectedModelElement();
        if (element == null) {
            return false;
        }

        /*
         * plugin.xml should have done type verifications already
         */
        TmfOnDemandAnalysisElement elem = (TmfOnDemandAnalysisElement) element;
        return (elem.getAnalysis() instanceof LamiAnalysis && elem.canRun());
    }

    @Override
    public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException {

        /* Types should have been checked by the plugin.xml already */
        ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
        Object element = ((IStructuredSelection) selection).getFirstElement();
        final TmfOnDemandAnalysisElement analysisElem = (TmfOnDemandAnalysisElement) element;

        TmfCommonProjectElement traceElem = analysisElem.getParent().getParent();
        ITmfTrace trace = traceElem.getTrace();
        if (trace == null) {
            /* That trace is not currently opened */
            return null;
        }

        /* Retrieve and initialize the analysis module, aka read the script's metadata */
        IOnDemandAnalysis ondemandAnalysis = analysisElem.getAnalysis();
        if (!(ondemandAnalysis instanceof LamiAnalysis)) {
            return null;
        }
        LamiAnalysis analysis = (LamiAnalysis) ondemandAnalysis;

        /* Retrieve the current time range, will be used as parameters to the analysis */
        TmfTraceManager tm = TmfTraceManager.getInstance();
        TmfTimeRange timeRange = tm.getCurrentTraceContext().getSelectionRange();
        if (timeRange.getStartTime().equals(timeRange.getEndTime())) {
            timeRange = null;
        }
        /* Job below needs a final reference... */
        final TmfTimeRange tr = timeRange;

        /* Pop the dialog to ask for extra parameters */
        String baseCommand = analysis.getFullCommandAsString(trace, tr);

        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        ParameterDialog dialog = new ParameterDialog(shell, Messages.ParameterDialog_ExternalParameters,
                Messages.ParameterDialog_ExternalParametersDescription,
                baseCommand, null);
        if (dialog.open() != Window.OK) {
            /* User clicked Cancel, don't run */
            return null;
        }
        String extraParams = nullToEmptyString(dialog.getValue());

        /* Execute the analysis and produce the reports */
        Job job = new Job(Messages.LamiAnalysis_MainTaskName) {
            @Override
            protected @Nullable IStatus run(@Nullable IProgressMonitor monitor) {
                IProgressMonitor mon = (monitor == null ? new NullProgressMonitor() : monitor);
                try {
                    List<LamiResultTable> results = analysis.execute(trace, tr, extraParams, mon);

                    String reportName = analysis.getName() +' ' + Messages.ParameterDialog_ReportNameSuffix;
                    LamiAnalysisReport report = new LamiAnalysisReport(reportName, results);
                    registerNewReport(analysisElem, report);

                    /* Automatically open the report for convenience */
                    Display.getDefault().syncExec(() -> {
                        try {
                            LamiReportViewFactory.createNewView(report);
                        } catch (PartInitException e) {
                        }
                    });
                    return Status.OK_STATUS;

                } catch (CoreException e) {
                    /*
                     * The analysis execution did not complete normally, we will
                     * report it to the user.
                     */
                    IStatus status = e.getStatus();

                    /* Don't display a dialog if it was simply cancelled by the user */
                    if (status.matches(IStatus.CANCEL)) {
                        return status;
                    }

                    String dialogTitle;
                    String dialogMessage;
                    if (status.matches(IStatus.ERROR)) {
                        dialogTitle = Messages.ErrorDialog_Error;
                        dialogMessage = Messages.ErrorDialog_ErrorMessage;
                    } else {
                        dialogTitle = Messages.ErrorDialog_Info;
                        dialogMessage = Messages.ErrorDialog_InfoMessage;
                    }

                    Display.getDefault().asyncExec(() -> {
                        ErrorDialog.openError(shell, dialogTitle, dialogMessage, status);
                    });

                    /*
                     * We showed our own error message, no need for the Job to
                     * show another one.
                     */
                    return Status.OK_STATUS;
                }
            }
        };
        job.schedule();

        return null;
    }

    /**
     * Register a new report
     *
     * @param analysisElem
     *            The analysis's project element
     * @param report
     *            The report to add
     */
    public void registerNewReport(TmfOnDemandAnalysisElement analysisElem, IOnDemandAnalysisReport report) {
        /* For now the TmfProjectReportsElement manages the reports. */
        TmfReportsElement reportsElement = analysisElem
                .getParent()
                .getParent()
                .getChildElementReports();

        reportsElement.addReport(report);
    }

}
