package org.rubypeople.rdt.internal.debug.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.internal.ui.contexts.DebugContextManager;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.rubypeople.rdt.debug.core.model.IRubyStackFrame;
import org.rubypeople.rdt.debug.core.model.IRubyThread;
import org.rubypeople.rdt.debug.ui.IEvaluationContextManager;

public class EvaluationContextManager implements IDebugContextListener, IWindowListener, IEvaluationContextManager
{

	private static EvaluationContextManager fgManager;

	/**
	 * System property indicating a stack frame is selected in the debug view with an <code>IJavaStackFrame</code>
	 * adapter.
	 */
	private static final String DEBUGGER_ACTIVE = RdtDebugUiPlugin.getUniqueIdentifier() + ".debuggerActive"; //$NON-NLS-1$
	/**
	 * System property indicating an element is selected in the debug view that is an instanceof
	 * <code>RubyStackFrame</code> or <code>RubyThread</code>.
	 */
	private static final String INSTANCE_OF_IRUBY_STACK_FRAME = RdtDebugUiPlugin.getUniqueIdentifier()
			+ ".instanceof.RubyStackFrame"; //$NON-NLS-1$

	private Map<IWorkbenchPage, IRubyStackFrame> fContextsByPage = null;
	private IWorkbenchWindow fActiveWindow;

	public EvaluationContextManager()
	{
		DebugContextManager.getDefault().addDebugContextListener(this);
		fgManager = this;
	}

	public IRubyStackFrame getEvaluationContext(IWorkbenchPart part)
	{
		IWorkbenchPage page = part.getSite().getPage();
		IRubyStackFrame frame = getContext(page);
		if (frame == null)
		{
			return getEvaluationContext(page.getWorkbenchWindow());
		}
		return frame;
	}

	public IRubyStackFrame getEvaluationContext(IWorkbenchWindow window)
	{
		List<IWorkbenchWindow> alreadyVisited = new ArrayList<IWorkbenchWindow>();
		if (window == null)
		{
			window = fgManager.fActiveWindow;
		}
		return getEvaluationContext(window, alreadyVisited);
	}

	private static IRubyStackFrame getEvaluationContext(IWorkbenchWindow window, List<IWorkbenchWindow> alreadyVisited)
	{
		IWorkbenchPage activePage = window.getActivePage();
		IRubyStackFrame frame = null;
		if (activePage != null)
		{
			frame = getContext(activePage);
		}
		if (frame == null)
		{
			IWorkbenchPage[] pages = window.getPages();
			for (int i = 0; i < pages.length; i++)
			{
				if (activePage != pages[i])
				{
					frame = getContext(pages[i]);
					if (frame != null)
					{
						return frame;
					}
				}
			}

			alreadyVisited.add(window);

			IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
			for (int i = 0; i < windows.length; i++)
			{
				if (!alreadyVisited.contains(windows[i]))
				{
					frame = getEvaluationContext(windows[i], alreadyVisited);
					if (frame != null)
					{
						return frame;
					}
				}
			}
			return null;
		}
		return frame;
	}

	private static IRubyStackFrame getContext(IWorkbenchPage page)
	{
		if (fgManager != null)
		{
			if (fgManager.fContextsByPage != null)
			{
				return fgManager.fContextsByPage.get(page);
			}
		}
		return null;
	}

	public void contextActivated(ISelection selection, IWorkbenchPart part)
	{
		if (part != null)
		{
			IWorkbenchPage page = part.getSite().getPage();
			if (selection instanceof IStructuredSelection)
			{
				IStructuredSelection ss = (IStructuredSelection) selection;
				if (ss.size() == 1)
				{
					Object element = ss.getFirstElement();
					if (element instanceof IAdaptable)
					{
						IRubyStackFrame frame = (IRubyStackFrame) ((IAdaptable) element).getAdapter(IRubyStackFrame.class);
						boolean instOf = element instanceof IRubyStackFrame || element instanceof IRubyThread;
						if (frame != null)
						{
							// do not consider scrapbook frames
							// if (frame.getLaunch().getAttribute(ScrapbookLauncher.SCRAPBOOK_LAUNCH) == null) {
							setContext(page, frame, instOf);
							return;
							// }
						}
					}
				}
			}
			// no context in the given view
			removeContext(page);
		}
	}

	public void contextChanged(ISelection selection, IWorkbenchPart part)
	{
	}

	/**
	 * Sets the evaluation context for the given page, and notes that a valid execution context exists.
	 * 
	 * @param page
	 * @param frame
	 */
	private void setContext(IWorkbenchPage page, IRubyStackFrame frame, boolean instOf)
	{
		if (fContextsByPage == null)
		{
			fContextsByPage = new HashMap<IWorkbenchPage, IRubyStackFrame>();
		}
		fContextsByPage.put(page, frame);
		System.setProperty(DEBUGGER_ACTIVE, "true"); //$NON-NLS-1$
		if (instOf)
		{
			System.setProperty(INSTANCE_OF_IRUBY_STACK_FRAME, "true"); //$NON-NLS-1$
		}
		else
		{
			System.setProperty(INSTANCE_OF_IRUBY_STACK_FRAME, "false"); //$NON-NLS-1$
		}
	}

	/**
	 * Removes an evaluation context for the given page, and determines if any valid execution context remain.
	 * 
	 * @param page
	 */
	private void removeContext(IWorkbenchPage page)
	{
		if (fContextsByPage != null)
		{
			fContextsByPage.remove(page);
			if (fContextsByPage.isEmpty())
			{
				System.setProperty(DEBUGGER_ACTIVE, "false"); //$NON-NLS-1$
				System.setProperty(INSTANCE_OF_IRUBY_STACK_FRAME, "false"); //$NON-NLS-1$
			}
		}
	}

	public void startup()
	{
		Runnable r = new Runnable()
		{
			public void run()
			{
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
				for (int i = 0; i < windows.length; i++)
				{
					fgManager.windowOpened(windows[i]);
				}
				workbench.addWindowListener(fgManager);
				fgManager.fActiveWindow = workbench.getActiveWorkbenchWindow();
			}
		};
		RdtDebugUiPlugin.getStandardDisplay().asyncExec(r);
	}

	public static IEvaluationContextManager instance()
	{
		if (fgManager == null)
		{
			fgManager = new EvaluationContextManager();
		}
		return fgManager;
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.IWindowListener#windowActivated(org.eclipse.ui.IWorkbenchWindow)
	 */
	public void windowActivated(IWorkbenchWindow window)
	{
		fActiveWindow = window;
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.IWindowListener#windowClosed(org.eclipse.ui.IWorkbenchWindow)
	 */
	public void windowClosed(IWorkbenchWindow window)
	{
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.IWindowListener#windowDeactivated(org.eclipse.ui.IWorkbenchWindow)
	 */
	public void windowDeactivated(IWorkbenchWindow window)
	{
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.IWindowListener#windowOpened(org.eclipse.ui.IWorkbenchWindow)
	 */
	public void windowOpened(IWorkbenchWindow window)
	{
	}

	public void debugContextChanged(DebugContextEvent event)
	{
		if ((event.getFlags() & DebugContextEvent.ACTIVATED) > 0) {
			contextActivated(event.getContext(), event.getDebugContextProvider().getPart());
		}	
	}

}
