/*******************************************************************************
 * Copyright (c) 2007, 2011 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.dsf.mi.service.command;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.mi.service.IMIBackend;
import org.eclipse.cdt.dsf.mi.service.IMIBackend.BackendStateChangedEvent;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

/**
 * CLI Process object implementation which uses the {@link IMIBackend} service
 * to monitor and control the underlying process.
 *
 * Note that starting with GDB 7.12, as long as a PTY is available,
 * this process is no longer used.  Instead, the real GDB process,
 * along with its console will be used directly.  A second PTY
 * will be used to communicate using MI.
 *
 * @since 1.1
 */
public class MIBackendCLIProcess extends AbstractCLIProcess {

	private IMIBackend fMIBackend;
	private AtomicInteger fExitCode = new AtomicInteger(-1);
	private BackedExitedEventListener fExitedEventListener;

	@ConfinedToDsfExecutor("getSession()#getExecutor")
	public MIBackendCLIProcess(ICommandControlService commandControl, IMIBackend backend) throws IOException {
		super(commandControl);
		fMIBackend = backend;
		if (fMIBackend.getState() == IMIBackend.State.TERMINATED) {
			fExitCode.set(fMIBackend.getExitCode());
		}
		fExitedEventListener = new BackedExitedEventListener();
		getSession().addServiceEventListener(fExitedEventListener, null);
	}

	public class BackedExitedEventListener {
		private final List<RequestMonitor> fWaitForRMs = new ArrayList<>();

		@DsfServiceEventHandler
		public void eventDispatched(BackendStateChangedEvent event) {
			if (event.getState() == IMIBackend.State.TERMINATED && event.getBackendId().equals(fMIBackend.getId())) {
				fExitCode.set(fMIBackend.getExitCode());
				for (RequestMonitor rm : fWaitForRMs) {
					rm.done();
				}
				fWaitForRMs.clear();
			}
		}

		void dispose() {
			for (RequestMonitor rm : fWaitForRMs) {
				rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED,
						"Backend terminate event never received", null)); //$NON-NLS-1$
				rm.done();
			}
			fWaitForRMs.clear();
		}
	}

	/**
	 * @see java.lang.Process#waitFor()
	 */
	@Override
	public int waitFor() throws InterruptedException {
		if (!DsfSession.isSessionActive(getSession().getId())) {
			return fExitCode.get();
		}

		try {
			Query<Object> query = new Query<Object>() {
				@Override
				protected void execute(final DataRequestMonitor<Object> rm) {
					if (!DsfSession.isSessionActive(getSession().getId()) || isDisposed()
							|| fMIBackend.getState() == IMIBackend.State.TERMINATED) {
						rm.setData(new Object());
						rm.done();
					} else {
						fExitedEventListener.fWaitForRMs.add(new ImmediateRequestMonitor(rm) {
							@Override
							protected void handleSuccess() {
								rm.setData(new Object());
								rm.done();
							}
						});
					}
				}
			};
			getSession().getExecutor().execute(query);
			query.get();
		} catch (RejectedExecutionException e) {
		} catch (ExecutionException e) {
		}
		return fExitCode.get();
	}

	/**
	 * @see java.lang.Process#exitValue()
	 */
	@Override
	public int exitValue() {
		if (!DsfSession.isSessionActive(getSession().getId())) {
			return fExitCode.get();
		}
		try {
			getSession().getExecutor().submit(new Callable<Object>() {
				@Override
				public Object call() throws Exception {
					if (fMIBackend.getState() != IMIBackend.State.TERMINATED) {
						throw new IllegalThreadStateException("Backend Process has not exited"); //$NON-NLS-1$
					}
					return null;
				}
			}).get();
		} catch (RejectedExecutionException e) {
		} catch (InterruptedException e) {
		} catch (ExecutionException e) {
			if (e.getCause() instanceof RuntimeException) {
				throw (RuntimeException) e.getCause();
			}
		}
		return fExitCode.get();
	}

	/**
	 * @see java.lang.Process#destroy()
	 */
	@Override
	public void destroy() {
		try {
			getSession().getExecutor().execute(new DsfRunnable() {
				@Override
				public void run() {
					if (!DsfSession.isSessionActive(getSession().getId()))
						return;
					if (isDisposed())
						return;

					fMIBackend.destroy();
				}
			});
		} catch (RejectedExecutionException e) {
			// Session disposed.
		}
	}

	@Override
	public void dispose() {
		fExitedEventListener.dispose();
		getSession().removeServiceEventListener(fExitedEventListener);
		super.dispose();
	}
}
