/*******************************************************************************
 * Copyright (c) 2006, 2009 Wind River Systems and others.
 *
 * 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:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.ui.actions;

import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.examples.dsf.pda.service.PDACommandControl;
import org.eclipse.cdt.examples.dsf.pda.service.PDAVirtualMachineDMContext;
import org.eclipse.cdt.examples.dsf.pda.ui.PDAUIPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.commands.IDebugCommandRequest;
import org.eclipse.debug.core.commands.IEnabledStateRequest;
import org.eclipse.debug.core.commands.ITerminateHandler;

/**
 * The terminate command is specialized for the PDA debugger.  Currently there
 * is no standard interface for terminating a debug session in DSF, because the
 * details of initiating and shutting down a debug session vary greatly in
 * different debuggers.
 */
public class PDATerminateCommand implements ITerminateHandler {
	// The executor and the services tracker, both initialized from a DSF session.
	private final DsfSession fSession;
	private final DsfServicesTracker fTracker;

	public PDATerminateCommand(DsfSession session) {
		fSession = session;
		fTracker = new DsfServicesTracker(PDAUIPlugin.getBundleContext(), session.getId());
	}

	public void dispose() {
		// DSF services tracker always has to be disposed, because the OSGi services
		// use reference counting.
		fTracker.dispose();
	}

	// Run control may not be available after a connection is terminated and shut down.
	@Override
	public void canExecute(final IEnabledStateRequest request) {
		// Terminate can only operate on a single element.
		if (request.getElements().length != 1 || !(request.getElements()[0] instanceof IDMVMContext)) {
			request.setEnabled(false);
			request.done();
			return;
		}

		// Find the PDA program context in the selected element.  If one is not found,
		// the action should be disabled.
		IDMVMContext vmc = (IDMVMContext) request.getElements()[0];
		final PDAVirtualMachineDMContext pdaProgramCtx = DMContexts.getAncestorOfType(vmc.getDMContext(),
				PDAVirtualMachineDMContext.class);
		if (pdaProgramCtx == null) {
			request.setEnabled(false);
			request.done();
			return;
		}

		try {
			fSession.getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					// Get the processes service and the exec context.
					PDACommandControl commandControl = fTracker.getService(PDACommandControl.class);
					if (commandControl == null || pdaProgramCtx == null) {
						// Context or service already invalid.
						request.setEnabled(false);
						request.done();
					} else {
						// Check whether the control is terminated.
						request.setEnabled(!commandControl.isTerminated());
						request.done();
					}
				}
			});
		} catch (RejectedExecutionException e) {
			// The DSF session for this context is no longer active.  It's possible to check
			// for this condition before calling fSession.getExecutor().execute(), but
			// since this method is executing in a different thread than the session control,
			// there would still be a chance for a race condition leading to this exception.
			request.setEnabled(false);
			request.done();
		}
	}

	@Override
	public boolean execute(final IDebugCommandRequest request) {
		// Skip the checks and assume that this method is called only if the action
		// was enabled.

		try {
			fSession.getExecutor().submit(new DsfRunnable() {
				@Override
				public void run() {
					// If the command control service is available, attempt to terminate the program.
					PDACommandControl commandControl = fTracker.getService(PDACommandControl.class);
					if (commandControl != null) {

						commandControl.terminate(new RequestMonitor(ImmediateExecutor.getInstance(), null) {
							@Override
							protected void handleCompleted() {
								request.setStatus(getStatus());
								request.done();
							}
						});
					}
				}
			});
		} catch (RejectedExecutionException e) {
			request.setStatus(new Status(IStatus.ERROR, PDAUIPlugin.PLUGIN_ID, "PDA debug session is shut down."));
			request.done();
		}
		return false;
	}

}
