/*******************************************************************************
 * Copyright (c) 2010, 2011 IBM 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.debug.internal.core.model;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.eclipse.debug.core.DebugException;
import org.eclipse.wst.jsdt.debug.core.jsdi.VirtualMachine;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.Event;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.EventQueue;
import org.eclipse.wst.jsdt.debug.core.jsdi.event.EventSet;
import org.eclipse.wst.jsdt.debug.core.jsdi.request.EventRequest;
import org.eclipse.wst.jsdt.debug.internal.core.JavaScriptDebugPlugin;

/**
 * Event dispatcher that notifies registered model elements
 * 
 * @see IJavaScriptEventListener
 * @since 1.0
 */
public final class EventDispatcher implements Runnable {

	/**
	 * Custom debug event kind for when a script is loaded
	 */
	public static final int EVENT_SCRIPT_LOADED = 0x0001;
	
	private HashMap /*<EventRequest, IJSDIEventListener>*/ listeners = null;
	private boolean shutdown = false;
	private JavaScriptDebugTarget target = null;

	/**
	 * Constructor
	 * 
	 * @param target
	 */
	public EventDispatcher(JavaScriptDebugTarget target) {
		this.target = target;
	}

	/**
	 * Registers the given listener for the specified request.
	 * 
	 * @param listener
	 * @param request
	 */
	public synchronized void addEventListener(IJavaScriptEventListener listener, EventRequest request) {
		if(this.listeners == null) {
			this.listeners = new HashMap(4);
		}
		this.listeners.put(request, listener);
	}

	/**
	 * Removes the given listener for the given request and returns the status of the removal
	 * as per the contract of {@link Collection#remove(Object)}
	 * 
	 * @param listener
	 * @param request
	 */
	public synchronized boolean removeEventListener(IJavaScriptEventListener listener, EventRequest request) {
		if(this.listeners != null) {
			return this.listeners.remove(request) != null;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		if (shutdown) {
			return; // no-op
		}
		VirtualMachine vm = target.getVM();
		if (vm != null) {
			EventQueue queue = vm.eventQueue();
			EventSet eventset = null;
			while (!shutdown) {
				try {
					eventset = queue.remove();
					if (eventset != null) {
						dispatch(eventset);
					}
				}
				catch(RuntimeException rte) {
					try {
						this.target.terminate();
					} catch (DebugException e) {
						JavaScriptDebugPlugin.log(e);
					}
					//JavaScriptDebugPlugin.log(rte);
				}
			}
		}
	}

	/**
	 * performs the re-firing of JSDI events up to any model elements listening
	 * 
	 * @param eventset
	 */
	void dispatch(EventSet eventSet) {
		Event event = null;
		boolean resume = true;
		for (Iterator iter = eventSet.iterator(); iter.hasNext();) {
			event = (Event) iter.next();
			if (event == null) {
				continue;
			}
			IJavaScriptEventListener listener = (IJavaScriptEventListener) this.listeners.get(event.request());
			if(listener != null) {
				resume &= listener.handleEvent(event, target, false, eventSet);
			}
		}

		for (Iterator iter = eventSet.iterator(); iter.hasNext();) {
			event = (Event) iter.next();
			if (event == null) {
				continue;
			}
			IJavaScriptEventListener listener = (IJavaScriptEventListener) this.listeners.get(event.request());
			if(listener != null) {
				listener.eventSetComplete(event, target, !resume, eventSet);
			}
		}
		if (resume) {
			eventSet.resume();
		}
	}

	/**
	 * Shut down and clean up the dispatcher
	 */
	public void shutdown() {
		shutdown = true;
		listeners.clear();
	}
}
