/*******************************************************************************
 * Copyright (c) 2007, 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 Implementation
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf;

import org.eclipse.cdt.dsf.service.DsfServiceEventHandler;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.swt.widgets.Display;

/*
 * This class provides a way to wait for an asynchronous ServerEvent
 * to occur.  The user of this class specifies which event is of
 * interest using the proper constructor or the registerForEvent() method.
 * waitForEvent() can then be called to block until the event occurs or
 * the timeout elapses.
 * 
 * Note that if the event occurs after regsiterForEvent() is called but
 * before waitForEvent() is called, waitForEvent() will return immediatly
 * since it will know the event has already occured.
 */

public class ServiceEventWaitor<V> {
	/*
	 *  Indicates we will wait forever. Otherwise the time specified
	 *  is in milliseconds.
	 */
	public final static int WAIT_FOREVER = 0 ;

	/* The type of event to wait for */
	private Class<V> fEventTypeClass;
	private final DsfSession fSession;
    private V fEvent;
    private final Display fDisplay;
	

	/* Empty contructor.  registerForEvent() should be called when
	 * this constructor is used.
	 */
	public ServiceEventWaitor(DsfSession session) {
	    fDisplay = Display.getDefault();
		fSession = session;
	}
	
	/* Contructor that takes the eventClass as parameter.  This is a shortcut
	 * that avoids calling registerForEvent()
	 */
	public ServiceEventWaitor(DsfSession session, Class<V> eventClass)	{
		this(session);
		registerForEvent(eventClass);
	}
	
	/* Specify which event to wait for, and add ourselves as
	 * a listener with the session 
	 */
	public void registerForEvent(Class<V> eventClass) {
		fEventTypeClass = eventClass;
		fEvent = null;
		fSession.addServiceEventListener(this, null);
	}

	public DsfSession getSession() {
	    return fSession;
	}
	
	public void dispose() {
        if (fEventTypeClass != null) fSession.removeServiceEventListener(this);
	}

	/* Block until 'timeout' or the previously specified event has been
	 * received.  The reason we don's specify the event as a parameter
	 * is that we must be ready for the event to occur event before
	 * this method is called.
	 */
	public synchronized V waitForEvent(int timeout) throws Exception {
		if (fEventTypeClass == null) {
			throw new Exception("Event to wait for has not been specified!");
		}
		// The event might have already been received
		if (fEvent != null) return fEvent;
		
		long timeoutTime = System.currentTimeMillis() + timeout;
		while (timeoutTime > System.currentTimeMillis()) {
		    if (fEvent != null) {
		        break;
		    }
		    if (!fDisplay.readAndDispatch()) {
		        Thread.sleep(0);
		    }
		}
		
		if (fEvent == null) {
			throw new Exception("Timed out waiting for ServiceEvent: " + fEventTypeClass.getName());
		}
		return fEvent;
	}

	/*
	 * Listen to all possible events by having the base class be the parameter.
	 * and then igure out if that event is the one we were waiting for.
	 */
	@DsfServiceEventHandler 
	public void eventDispatched(V event) {
		if (fEventTypeClass.isAssignableFrom(event.getClass())) {
			synchronized(this) {
				fEvent = event;
				notifyAll();
			}
		}
	}
}
