/*******************************************************************************
 * Copyright (c) 2010 Ericsson and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Ericsson - initial API and implementation
 *     Andy Jin (QNX) - Not output thread osId as a string when it is null (Bug 397039)
 *******************************************************************************/
package org.eclipse.cdt.dsf.gdb.service;

import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateDataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.ImmediateRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.Immutable;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerResumedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IContainerSuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IExitedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IResumedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.IStartedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.command.BufferedCommandControl;
import org.eclipse.cdt.dsf.debug.service.command.CommandCache;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.gdb.internal.GdbPlugin;
import org.eclipse.cdt.dsf.gdb.service.command.IGDBControl;
import org.eclipse.cdt.dsf.mi.service.IMICommandControl;
import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext;
import org.eclipse.cdt.dsf.mi.service.command.CommandFactory;
import org.eclipse.cdt.dsf.mi.service.command.output.MIListThreadGroupsInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIListThreadGroupsInfo.IThreadGroupInfo;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThread;
import org.eclipse.cdt.dsf.mi.service.command.output.MIThreadInfoInfo;
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;

/**
 * This class implements the IProcesses interface for GDB 7.1
 * which provides new information about cores for threads and processes.
 * 
 * @since 4.0
 */
public class GDBProcesses_7_1 extends GDBProcesses_7_0 {

	@Immutable
	protected static class MIThreadDMData_7_1 extends MIThreadDMData implements IGdbThreadDMData {
		final String[] fCores;

		public MIThreadDMData_7_1(String name, String id, String[] cores) {
			super(name, id);
			fCores = cores;
		}

		@Override
		public String[] getCores() { return fCores; }

		@Override
		public String getOwner() { return null; }
	}

    private CommandFactory fCommandFactory;
    // This cache is used when we send command to get the cores.
    // The value of the cores can change at any time, but we provide
    // an updated value whenever there is a suspended event.
    private CommandCache fCommandForCoresCache;
    private IGDBControl fCommandControl;

	public GDBProcesses_7_1(DsfSession session) {
		super(session);
	}

	@Override
	public void initialize(final RequestMonitor requestMonitor) {
		super.initialize(new ImmediateRequestMonitor(requestMonitor) {
			@Override
			protected void handleSuccess() {
				doInitialize(requestMonitor);
			}
		});
	}

	/**
	 * This method initializes this service after our superclass's initialize()
	 * method succeeds.
	 * 
	 * @param requestMonitor
	 *            The call-back object to notify when this service's
	 *            initialization is done.
	 */
	private void doInitialize(RequestMonitor requestMonitor) {
		fCommandControl = getServicesTracker().getService(IGDBControl.class);

		// This caches stores the result of a command when received; also, this cache
		// is manipulated when receiving events.  Currently, events are received after
		// three scheduling of the executor, while command results after only one.  This
		// can cause problems because command results might be processed before an event
		// that actually arrived before the command result.
		// To solve this, we use a bufferedCommandControl that will delay the command
		// result by two scheduling of the executor.
		// See bug 280461
        fCommandForCoresCache = new CommandCache(getSession(), 
        		new BufferedCommandControl(fCommandControl, getExecutor(), 2));
        fCommandForCoresCache.setContextAvailable(fCommandControl.getContext(), true);

        fCommandFactory = getServicesTracker().getService(IMICommandControl.class).getCommandFactory();
        getSession().addServiceEventListener(this, null);

        requestMonitor.done();
	}

	@Override
	public void shutdown(RequestMonitor requestMonitor) {
        getSession().removeServiceEventListener(this);

		super.shutdown(requestMonitor);
	}

	@Override
	public void getExecutionData(final IThreadDMContext dmc, final DataRequestMonitor<IThreadDMData> rm) {
		if (dmc instanceof IMIProcessDMContext) {
			// Starting with GDB 7.1, we can obtain the list of cores a process is currently
			// running on (each core that has a thread of that process).
			// We have to use -list-thread-groups to obtain that information
			// Note that -list-thread-groups does not show the 'user' field
			super.getExecutionData(dmc, new ImmediateDataRequestMonitor<IThreadDMData>(rm) {
				@Override
				protected void handleSuccess() {
					final IThreadDMData firstLevelData = getData();
					
					ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
					final String groupId = getGroupFromPid(((IMIProcessDMContext)dmc).getProcId());

					fCommandForCoresCache.execute(
							fCommandFactory.createMIListThreadGroups(controlDmc),
							new ImmediateDataRequestMonitor<MIListThreadGroupsInfo>(rm) {
								@Override
								protected void handleCompleted() {
									String[] cores = null;
									if (isSuccess()) {
										IThreadGroupInfo[] groups = getData().getGroupList();
										if (groups != null) {
											for (IThreadGroupInfo group : groups) {
												if (group.getGroupId().equals(groupId)) {
													cores = group.getCores();
													break;
												}
											}
										}
									}
									rm.setData(new MIThreadDMData_7_1(firstLevelData.getName(),
				                                                      firstLevelData.getId(),
				                                                      cores));
									rm.done();	
								}
							});
				} 
			});
		} else if (dmc instanceof MIThreadDMC) {
			// Starting with GDB 7.1, we can obtain the core on which a thread
			// is currently located.  The info is a new field in -thread-info
			final MIThreadDMC threadDmc = (MIThreadDMC)dmc;

			ICommandControlDMContext controlDmc = DMContexts.getAncestorOfType(dmc, ICommandControlDMContext.class);
			fCommandForCoresCache.execute(fCommandFactory.createMIThreadInfo(controlDmc, threadDmc.getId()),
					new ImmediateDataRequestMonitor<MIThreadInfoInfo>(rm) {
				        @Override
			          	protected void handleSuccess() {
				        	IThreadDMData threadData = null;
				        	if (getData().getThreadList().length != 0) {
				        		MIThread thread = getData().getThreadList()[0];
				        		if (thread.getThreadId().equals(threadDmc.getId())) {
				        			String id = ""; //$NON-NLS-1$
				        			if (thread.getOsId() != null) {
				        				id = thread.getOsId();
				        			}
        	        				// append thread details (if any) to the thread ID
        	        				// as for GDB 6.x with CLIInfoThreadsInfo#getOsId()
        	        				final String details = thread.getDetails();
        	        				if (details != null && details.length() > 0) {
        	        					if (!id.isEmpty()) id += " "; //$NON-NLS-1$
        	        					id += "(" + details + ")"; //$NON-NLS-1$ //$NON-NLS-2$
        	        				}
        	        				// We must indicate and empty id by using null
				        			if (id.isEmpty()) id = null;
				        			
				        			String core = thread.getCore();
				        			threadData = new MIThreadDMData_7_1("", id, //$NON-NLS-1$
				        					                            core == null ? null : new String[] { core });
				        		}
				        	}

				        	if (threadData != null) {
				        		rm.setData(threadData);        	        			
				        	} else {
				        		rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Could not get thread info", null)); //$NON-NLS-1$        	        			
				        	}
				        	rm.done();
				        }
			});
		} else {
			rm.setStatus(new Status(IStatus.ERROR, GdbPlugin.PLUGIN_ID, INVALID_HANDLE, "Invalid DMC type", null)); //$NON-NLS-1$
			rm.done();
		}
	}
	
	@DsfServiceEventHandler
	public void eventDispatched_7_1(IResumedDMEvent e) {
		if (e instanceof IContainerResumedDMEvent) {
			// This will happen in all-stop mode
			fCommandForCoresCache.setContextAvailable(e.getDMContext(), false);
		} else {
			// This will happen in non-stop mode
			// Keep target available for Container commands
		}
	}

	// Something has suspended, core allocation could have changed
	// during the time it was running.
    @DsfServiceEventHandler
    public void eventDispatched_7_1(ISuspendedDMEvent e) {
       	if (e instanceof IContainerSuspendedDMEvent) {
    		// This will happen in all-stop mode
       		fCommandForCoresCache.setContextAvailable(fCommandControl.getContext(), true);
       	} else {
       		// This will happen in non-stop mode
       	}
       	
       	fCommandForCoresCache.reset();
    }
    
    // Event handler when a thread or threadGroup starts, core allocation 
    // could have changed
    @DsfServiceEventHandler
    public void eventDispatched_7_1(IStartedDMEvent e) {
       	fCommandForCoresCache.reset();
	}
    
    // Event handler when a thread or a threadGroup exits, core allocation
    // could have changed
    @DsfServiceEventHandler
    public void eventDispatched_7_1(IExitedDMEvent e) {
       	fCommandForCoresCache.reset();
    }
    
	@Override
	public void flushCache(IDMContext context) {
		fCommandForCoresCache.reset(context);
		super.flushCache(context);
	}
}
