/*******************************************************************************
 * Copyright (c) 2008, 2014 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.launch;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DefaultDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Sequence;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.debug.model.DsfLaunch;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.service.PDATerminatedEvent;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.Launch;
import org.eclipse.debug.core.model.ISourceLocator;

/**
 * The PDA launch object. In general, a DSF-based debugger has to override
 * the base launch class in order to supply its own content providers for the
 * debug view.  Additionally, the PDA launch is used to monitor the state of the
 * PDA debugger and to shutdown the DSF services and session belonging to the
 * launch.
 * <p>
 * The PDA launch class mostly contains methods and fields that can be accessed
 * on any thread.  However, some fields and methods used for managing the DSF
 * session need to be synchronized using the DSF executor.
 * </p>
 */
@ThreadSafe
public class PDALaunch extends DsfLaunch {
	// DSF executor and session.  Both are created and shutdown by the launch.
	private final DefaultDsfExecutor fExecutor;
	private final DsfSession fSession;

	// Objects used to track the status of the DSF session.
	private boolean fInitialized = false;
	private boolean fShutDown = false;

	@ConfinedToDsfExecutor("getSession().getExecutor()")
	private Sequence fInitializationSequence = null;

	/**
	 * Launch constructor creates the launch for given parameters.  The
	 * constructor also creates a DSF session and an executor, so that
	 * {@link #getSession()} returns a valid value, however no services
	 * are initialized yet.
	 *
	 * @see Launch
	 */
	public PDALaunch(ILaunchConfiguration launchConfiguration, String mode, ISourceLocator locator) {
		super(launchConfiguration, mode, locator);

		// Create the dispatch queue to be used by debugger control and services
		// that belong to this launch
		final DefaultDsfExecutor dsfExecutor = new DefaultDsfExecutor(PDAPlugin.ID_PDA_DEBUG_MODEL);
		dsfExecutor.prestartCoreThread();
		fExecutor = dsfExecutor;
		fSession = DsfSession.startSession(fExecutor, PDAPlugin.ID_PDA_DEBUG_MODEL);

		// Register the launch as an adapter This ensures that the launch,
		// and debug model ID will be associated with all DMContexts from this
		// session.
		fSession.registerModelAdapter(ILaunch.class, this);
	}

	/**
	 * Returns the DSF services session that belongs to this launch.  This
	 * method will always return a DsfSession object, however if the debugger
	 * is shut down, the session will no longer active.
	 */
	public DsfSession getSession() {
		return fSession;
	}

	/**
	 * Initializes the DSF services using the specified parameters.  This
	 * method has to be called on the executor thread in order to avoid
	 * synchronization issues.
	 */
	@ConfinedToDsfExecutor("getSession().getExecutor()")
	public void initializeServices(String program, final RequestMonitor rm) {
		// Double-check that we're being called in the correct thread.
		assert fExecutor.isInExecutorThread();

		// Check if shutdownServices() was called already, which would be
		// highly unusual, but if so we don't need to do anything except set
		// the initialized flag.
		synchronized (this) {
			if (fShutDown) {
				fInitialized = true;
				return;
			}
		}

		// Register the launch as listener for services events.
		fSession.addServiceEventListener(PDALaunch.this, null);

		// The initialization sequence is stored in a field to allow it to be
		// canceled if shutdownServices() is called before the sequence
		// completes.
		fInitializationSequence = new PDAServicesInitSequence(getSession(), this, program,
				new RequestMonitor(ImmediateExecutor.getInstance(), rm) {
					@Override
					protected void handleCompleted() {
						// Set the initialized flag and check whether the
						// shutdown flag is set.  Access the flags in a
						// synchronized section as these flags can be accessed
						// on any thread.
						boolean doShutdown = false;
						synchronized (this) {
							fInitialized = true;
							fInitializationSequence = null;
							if (fShutDown) {
								doShutdown = true;
							}
						}

						if (doShutdown) {
							// If shutdownServices() was already called, start the
							// shutdown sequence now.
							doShutdown(rm);
						} else {
							// If there was an error in the startup sequence,
							// report the error to the client.
							if (getStatus().getSeverity() == IStatus.ERROR) {
								rm.setStatus(getStatus());
							}
							rm.done();
						}
						fireChanged();
					}
				});

		// Finally, execute the sequence.
		getSession().getExecutor().execute(fInitializationSequence);
	}

	/**
	 * Event handler for a debugger terminated event.
	 */
	@DsfServiceEventHandler
	public void eventDispatched(PDATerminatedEvent event) {
		shutdownServices(new RequestMonitor(ImmediateExecutor.getInstance(), null));
	}

	/**
	 * Returns whether the DSF service initialization sequence has completed yet.
	 */
	public synchronized boolean isInitialized() {
		return fInitialized;
	}

	/**
	 * Returns whether the DSF services have been set to shut down.
	 * @return
	 */
	public synchronized boolean isShutDown() {
		return fShutDown;
	}

	@Override
	public boolean canTerminate() {
		return super.canTerminate() && isInitialized() && !isShutDown();
	}

	@Override
	public boolean isTerminated() {
		return super.isTerminated() || isShutDown();
	}

	@Override
	public void terminate() throws DebugException {
		if (isShutDown())
			return;
		super.terminate();
	}

	/**
	 * Shuts down the services, the session and the executor associated with
	 * this launch.
	 * <p>
	 * Note: The argument request monitor to this method should NOT use the
	 * executor that belongs to this launch.  By the time the shutdown is
	 * complete, this executor will not be dispatching anymore and the
	 * request monitor will never be invoked.  Instead callers should use
	 * the {@link ImmediateExecutor}.
	 * </p>
	 * @param rm The request monitor invoked when the shutdown is complete.
	 */
	@ConfinedToDsfExecutor("getSession().getExecutor()")
	public void shutdownServices(final RequestMonitor rm) {
		// Check initialize and shutdown flags to determine if the shutdown
		// sequence can be called yet.
		boolean doShutdown = false;
		synchronized (this) {
			if (!fInitialized && fInitializationSequence != null) {
				// Launch has not yet initialized, try to cancel the
				// shutdown sequence.
				fInitializationSequence.cancel(false);
			} else {
				doShutdown = !fShutDown && fInitialized;
			}
			fShutDown = true;
		}

		if (doShutdown) {
			doShutdown(rm);
		} else {
			rm.done();
		}
	}

	@ConfinedToDsfExecutor("getSession().getExecutor()")
	private void doShutdown(final RequestMonitor rm) {
		fExecutor.execute(new PDAServicesShutdownSequence(fExecutor, fSession.getId(),
				new RequestMonitor(fSession.getExecutor(), rm) {
					@Override
					public void handleCompleted() {
						fSession.removeServiceEventListener(PDALaunch.this);
						if (!isSuccess()) {
							PDAPlugin.getDefault().getLog().log(new MultiStatus(PDAPlugin.PLUGIN_ID, -1,
									new IStatus[] { getStatus() }, "Session shutdown failed", null)); //$NON-NLS-1$
						}
						// Last order of business, shutdown the dispatch queue.
						DsfSession.endSession(fSession);
						// endSession takes a full dispatch to distribute the
						// session-ended event, finish step only after the dispatch.
						fireTerminate();

						rm.setStatus(getStatus());
						rm.done();
					}
				}));
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object getAdapter(Class adapter) {
		// Force adapters to be loaded.  Otherwise the adapter manager may not find
		// the model proxy adapter for DSF-based debug elements.
		Platform.getAdapterManager().loadAdapter(this, adapter.getName());
		return super.getAdapter(adapter);
	}

	@Override
	public void launchRemoved(ILaunch launch) {
		if (this.equals(launch)) {
			fExecutor.shutdown();
		}
		super.launchRemoved(launch);
	}
}
