/*******************************************************************************
 * Copyright (c) 2012 Tilera Corporation 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:
 *     William R. Swanson (Tilera Corporation) - initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.dsf.gdb.multicorevisualizer.internal.ui.view;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.datamodel.IDMContext;
import org.eclipse.cdt.dsf.debug.service.IProcesses.IThreadDMContext;
import org.eclipse.cdt.dsf.debug.service.IStack.IFrameDMContext;
import org.eclipse.cdt.dsf.gdb.multicorevisualizer.internal.ui.model.VisualizerThread;
import org.eclipse.cdt.dsf.gdb.multicorevisualizer.internal.utils.DebugViewTreeWalker;
import org.eclipse.cdt.dsf.mi.service.IMIExecutionDMContext;
import org.eclipse.cdt.dsf.mi.service.IMIProcessDMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.visualizer.ui.util.SelectionUtils;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreePath;


/** 
* Debug view tree walker that finds elements to select
* based on selection obtained from the multicore visualizer.
*/
@SuppressWarnings("restriction") // allow access to internal interfaces
public class MulticoreVisualizerSelectionFinder
	extends DebugViewTreeWalker
{
	// --- members ---

	/** Selection item(s) we're currently looking for. */
	List<Object> m_selection = null;

	/** Result we're found, if any. */
	Set<Object> m_result = null;


	// --- constructors/destructors ---

	/** Constructor */
	public MulticoreVisualizerSelectionFinder()
	{
	}

	/** Dispose method */
	@Override
	public void dispose() {
		super.dispose();
	}

	// --- methods ---

	/** Finds and returns Debug View element for specified
	 *  Visualizer selection item.
	 *  (E.g. the IDMVMContext for a VisualizerThread.
	 *  Returns null if no match is found.
	 */
	public ISelection findSelection(ISelection selection)
	{
		m_selection = SelectionUtils.getSelectedObjects(selection);

		m_result = new HashSet<Object>();
		walk();
		ISelection found = SelectionUtils.toSelection(m_result);
		return found;
	}

	/** Processes an element of the tree view.
	 *  Returns true if children of this element should be processed,
	 *  and false if they can be skipped.
	 */
	@Override
	public boolean processElement(TreePath path)
	{
		boolean result = true;

		Object element = getElement(path);

		if (element instanceof IDMVMContext) {
			IDMContext context = ((IDMVMContext) element).getDMContext();
			int pid = getPID(context);
			int tid = getTID(context);

			if (isThreadContext(context))
			{
				for (Object o : m_selection) {
					if (o instanceof VisualizerThread) {
						VisualizerThread thread = (VisualizerThread) o;
						// The Debug view model uses the GDB thread, to we need to use that one from the Visualizer model
						if (thread.getPID() == pid && thread.getGDBTID() == tid)
						{
							m_result.add(element);
						}
					}
				}
			}
			else if (context instanceof IFrameDMContext)
			{
				// FIXME: if we have frame[0] under a selected thread,
				// select that stack frame instead of the thread
				if (isThreadFrameZero(context))
				{
					IDMVMContext threadContext = (IDMVMContext) path.getParentPath().getLastSegment();
					if (m_result.contains(threadContext))
					{
						m_result.remove(threadContext);
						m_result.add(element);
					}
				}
			}
		}

		return result;
	}
	
	/** Returns PID for specified debug context. */
	public static int getPID(IDMContext context)
	{
		IMIProcessDMContext processContext =
				DMContexts.getAncestorOfType(context, IMIProcessDMContext.class);
		int pid = (processContext == null) ? 0 : 
			Integer.parseInt(processContext.getProcId());
		return pid;
	}

	/** Returns TID for specified debug context. */
	public static int getTID(IDMContext context)
	{
		IMIExecutionDMContext execContext =
				DMContexts.getAncestorOfType(context, IMIExecutionDMContext.class);
		int tid = (execContext == null) ? 0 :
			execContext.getThreadId();
		return tid;
	}

	/** Returns true if specified context represents a thread. */
	public static boolean isThreadContext(IDMContext context)
	{
		// TODO: is there a more elegant way to express this?
		return
			context instanceof IMIExecutionDMContext &&
			context.getParents().length >= 2 &&
			(context.getParents()[0] instanceof IThreadDMContext ||
			 context.getParents()[1] instanceof IThreadDMContext);
	}
	
	/** Returns true if context represents the topmost (0th) frame under a thread. */
	public static boolean isThreadFrameZero(IDMContext context)
	{
		// TODO: is there a more elegant way to express this?
		String value = context.toString();
		return (value != null && value.endsWith(".frame[0]")); //$NON-NLS-1$
	}
}
