/*******************************************************************************
 * Copyright (c) 2011, 2012 Mentor Graphics 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:
 *     Vladimir Prus (Mentor Graphics) - initial API and implementation
 *******************************************************************************/

package org.eclipse.cdt.dsf.gdb.internal.ui.osview;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfExecutor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
import org.eclipse.cdt.dsf.datamodel.DataModelInitializedEvent;
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.IResumedDMEvent;
import org.eclipse.cdt.dsf.debug.service.IRunControl.ISuspendedDMEvent;
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService.ICommandControlDMContext;
import org.eclipse.cdt.dsf.gdb.internal.ui.GdbUIPlugin;
import org.eclipse.cdt.dsf.gdb.service.IGDBHardwareAndOS2;
import org.eclipse.cdt.dsf.gdb.service.IGDBHardwareAndOS2.IResourceClass;
import org.eclipse.cdt.dsf.gdb.service.IGDBHardwareAndOS2.IResourcesInformation;
import org.eclipse.cdt.dsf.mi.service.IMIRunControl;
import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfServicesTracker;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.BundleContext;

/** Responsible for fetching and storing OS awareness data for a
 * specific DSF session.
 *
 * @since 2.4
 */
public class SessionOSData {

	private DsfSession fSession;
	private DsfServicesTracker fTracker;
	private IGDBHardwareAndOS2 fHardwareOs;
	private ICommandControlDMContext fContext;

	private IResourceClass[] fResourceClasses = new IResourceClass[0];
	private Map<String, OSData> fExistingData = new HashMap<String, OSData>();
	private Map<String, Date> fTimestamp = new HashMap<String, Date>();

	private Listener fUIListener;
	private Control fUIControl;

	private boolean fWaitingForSession = true;
	private boolean fSupported = true;
	private boolean fAcceptingCommands = false;
	private boolean fFetchingClasses = false;
	private boolean fFetchingContent = false;
	

	public SessionOSData(DsfSession session, final ICommandControlDMContext executionContext)
	{
		fSession = session;
		BundleContext c = GdbUIPlugin.getDefault().getBundle().getBundleContext();
		fTracker = new DsfServicesTracker(c, fSession.getId());
		fContext = executionContext;

		final DsfExecutor executor = fSession.getExecutor();
		executor.submit(new DsfRunnable() {

			@Override
			public void run() {
				
				IMIRunControl runControl = fTracker.getService(IMIRunControl.class);
				fAcceptingCommands = runControl.isTargetAcceptingCommands();

				fSession.addServiceEventListener(SessionOSData.this, null);

				fHardwareOs = fTracker.getService(IGDBHardwareAndOS2.class);

				if (fHardwareOs == null) {
					fSupported = false;
					notifyUI();
					return;
				}
				
				if (fHardwareOs.isAvailable()) {
					fetchClasses();	
				}
			}
		});
	}
	
	@ConfinedToDsfExecutor("")
	private void fetchClasses() 
	{	
		fWaitingForSession = false;
		fFetchingClasses = true;
		fHardwareOs.getResourceClasses(fContext, new DataRequestMonitor<IResourceClass[]>(fSession.getExecutor(), null) {
			@Override
			@ConfinedToDsfExecutor("fExecutor")
			protected void handleCompleted() {

				if (isSuccess())
				{
					fResourceClasses = getData();
					if (fResourceClasses.length == 0)
						fSupported = false;
				}
				else
				{
					fSupported = false;
				}
				fFetchingClasses = false;
				notifyUI();
			}
		});
	}
	
	@DsfServiceEventHandler 
	public void eventDispatched(DataModelInitializedEvent e) {
		// If we see this event, it necessary means that by the time we've set event listener,
		// isAvailable() was returning false, so we need to fetch classes now.
		if (fHardwareOs != null)
			fetchClasses();
	}

	public boolean waitingForSessionInitialization()
	{
		return fWaitingForSession;
	}

	public boolean osResourcesSupported()
	{
		return fSupported;
	}

	public void dispose()
	{
		fSession.removeServiceEventListener(SessionOSData.this);
		fTracker.dispose();
	}

	public IResourceClass[] getResourceClasses()
	{
		return fResourceClasses;
	}

	/** Returns OS awareness data for given resource class that
	 * was previously fetched, or null if none was ever fetched.
	 */
	public OSData existingData(String resourceClass)
	{
		return fExistingData.get(resourceClass);
	}

	/** Returns the timestamp at which data for 'resourceClass' have
	 * been obtained.
	 * @pre existingData(resourceClass) != null
	 */
	public Date timestamp(String resourceClass)
	{
		return fTimestamp.get(resourceClass);
	}

	/** Returns true if fresh data can be fetched at this time.
	 * Generally, it's possible if we're not fetching data already,
	 * and if GDB is accepting commands right now.
	 *
	 */
	public boolean canFetchData()
	{
		return fAcceptingCommands && !fFetchingContent;
	}

	public boolean fetchingClasses()
	{
		return fFetchingClasses;
	}

	/** Returns true if we're presently fetching data. This can
	 * be used to provide some feedback to the user.
	 */
	public boolean fetchingContent()
	{
		return fFetchingContent;
	}

	/** Fetches up-to-date data for resourceClass.  Listeners will be
	 * informed when the new data is available.  */
	public void fetchData(final String resourceClass)
	{
		fFetchingContent = true;
		notifyUI();

		final DsfExecutor executor = fSession.getExecutor();
		executor.submit(new DsfRunnable() {

			@Override
			public void run() {
				fHardwareOs.getResourcesInformation(fContext, resourceClass, new DataRequestMonitor<IResourcesInformation>(executor, null) {

					@Override
					@ConfinedToDsfExecutor("fExecutor")
					protected void handleCompleted() {

						fFetchingContent = false;

						if (isSuccess())
						{
							OSData data = new OSData(resourceClass, getData());
							fExistingData.put(resourceClass, data);
							fTimestamp.put(resourceClass, new Date());
						}
						else
						{
							StatusManager.getManager().handle(getStatus(), StatusManager.BLOCK);
						}
						notifyUI();
					}
				});
			}
		});


	}

	public interface Listener
	{
		void update();
	}

	/** Setup the listener that will be notified whenever externally
	 * visible state changes. The listener will always be invoked
	 * in the UI thread. 'control' is the control associated with
	 * the listener. The listener will not be called if the control
	 * is disposed.
	 */
	public void setUIListener(Listener listener, Control control)
	{
		fUIListener = listener;
		fUIControl = control;
	}

	private void notifyUI()
	{
		
		final Control c = fUIControl;
		if (c != null && !c.isDisposed())
			// There be dragons: if you try to use c.getDisplay() below, then this Runnable will not
			// run until resource view is actually visible. And it will also block other interesting
			//  async/job runnables, like perspective switch runnable using during debug launch,
			//   causing launch to be stuck at random point.
			// 
			Display.getDefault().asyncExec(new Runnable() {

				@Override
				public void run() {
					
					if (!c.isDisposed())
						fUIListener.update();
				}
			});
			
	}

    @DsfServiceEventHandler
    public void eventDispatched(IResumedDMEvent e) {
    	if (e instanceof IContainerResumedDMEvent) {
    		// This event is raised only in all-stop. It does not
    		// seem to be possible to issue -info-os in all-stop,
    		// regardless of whether target-async is in effect, and
    		// according to DSF folks, all-stop+target-async will
    		// not work anyway. So, we assume that no commands
    		// can be issued right now.
    		fAcceptingCommands = false;
    		notifyUI();
    	}
    }

    @DsfServiceEventHandler
    public void eventDispatched(ISuspendedDMEvent e) {
    	if (e instanceof IContainerSuspendedDMEvent) {
    		fAcceptingCommands = true;
    		notifyUI();
    	}
    }
}
