//----------------------------BEGIN LICENSE----------------------------
/* 
 * Willow : the Open Source WorkFlow Project
 * Distributable under GNU LGPL license by gun.org
 *
 * Copyright (C) 2004-2010 huihoo.org 
 * Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com> 
 * 
 * ====================================================================
 * Project Homepage : http://www.huihoo.org/willow
 * Source Forge     : http://sourceforge.net/projects/huihoo
 * Mailing list     : willow@lists.sourceforge.net 
 */
//----------------------------END  LICENSE----------------------------

package org.huihoo.workflow.event;

import java.lang.ref.WeakReference;
import java.security.AccessController;
import java.security.PrivilegedAction;

public class EventQueue
{
	// From Thread.java
	private static int threadInitNumber;
	private static synchronized int nextThreadNum()
	{
		return threadInitNumber++;
	}

	private static final int LOW_PRIORITY = 0;
	private static final int NORM_PRIORITY = 1;
	private static final int HIGH_PRIORITY = 2;

	private static final int NUM_PRIORITIES = HIGH_PRIORITY + 1;

	/*
	 * We maintain one Queue for each priority that the EventQueue supports.
	 * That is, the EventQueue object is actually implemented as
	 * NUM_PRIORITIES queues and all Events on a particular internal Queue
	 * have identical priority. Events are pulled off the EventQueue starting
	 * with the Queue of highest priority. We progress in decreasing order
	 * across all Queues.
	 */
	private Queue[] queues = new Queue[NUM_PRIORITIES];

	private EventDispatchThread dispatchThread;

	private final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
	private final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

	/*
	 * Debugging flag -- set true and recompile to enable checking.
	 */
	private final static boolean debug = false;

	/*
	 * The time stamp of the last dispatched InputEvent or ActionEvent.
	 */
	private long mostRecentEventTime = System.currentTimeMillis();

	/**
	 * The modifiers field of the current event, if the current event is an
	 * InputEvent or ActionEvent.
	 */
	private WeakReference currentEvent;

	/*
	 * Non-zero if a thread is waiting in getNextEvent(int) for an event of
	 * a particular ID to be posted to the queue.
	 */
	private int waitForID;

	private final String name = "WILLOW-EventQueue-" + nextThreadNum();

	public EventQueue()
	{
		for (int i = 0; i < NUM_PRIORITIES; i++)
		{
			queues[i] = new Queue();
		}
	}

	/**
	 * Posts an  event to the <code>EventQueue</code>. 
	 *
	 * @param theEvent an instance of <code>WorkflowEvent</code>,
	 *		or a subclass of it
	 * @throws NullPointerException if <code>theEvent</code> is <code>null</code>
	 */
	public void postEvent(WorkflowEvent theEvent)
	{
		postEventPrivate(theEvent);
	}

	/**
	 * Posts a 1.1-style event to the <code>EventQueue</code>.
	 * If there is an existing event on the queue with the same ID
	 * and event source, the source <code>Component</code>'s
	 * <code>coalesceEvents</code> method will be called.
	 *
	 * @param theEvent an instance of <code>WorkflowEvent</code>,
	 * 		or a subclass of it
	 */
	final void postEventPrivate(WorkflowEvent theEvent)
	{
		synchronized (this)
		{
			postEvent(theEvent, HIGH_PRIORITY);
		}
	}

	/**
	 * Posts the event to the internal Queue of specified priority,
	 * coalescing as appropriate.
	 *
	 * @param theEvent an instance of <code>WorkflowEvent</code>,
	 * 		or a subclass of it
	 * @param priority  the desired priority of the event
	 */
	private void postEvent(WorkflowEvent theEvent, int priority)
	{

		if (dispatchThread == null)
		{
			if (theEvent.getSource() == EventAutoShutdown.getInstance())
			{
				return;
			}
			else
			{
				initDispatchThread();
			}
		}

		EventQueueItem newItem = new EventQueueItem(theEvent);

		boolean notifyID = (theEvent.getID() == this.waitForID);

		if (queues[priority].head == null)
		{
			boolean shouldNotify = noEvents();
			queues[priority].head = queues[priority].tail = newItem;

			if (shouldNotify)
			{
				if (theEvent.getSource() != EventAutoShutdown.getInstance())
				{
					EventAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
				}
				notifyAll();
			}
			else if (notifyID)
			{
				notifyAll();
			}
		}
		else
		{
			// Insert it at the end of the appropriate Queue.
			queues[priority].tail.next = newItem;
			queues[priority].tail = newItem;
			if (notifyID)
			{
				notifyAll();
			}
		}
	}

	/**
	 * Returns whether an event is pending on any of the separate
	 * Queues.
	 * @return whether an event is pending on any of the separate Queues
	 */
	private boolean noEvents()
	{
		for (int i = 0; i < NUM_PRIORITIES; i++)
		{
			if (queues[i].head != null)
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Removes an event from the <code>EventQueue</code> and
	 * returns it.  This method will block until an event has
	 * been posted by another thread.
	 * @return the next <code>WorkflowEvent</code>
	 * @exception InterruptedException 
	 *            if another thread has interrupted this thread
	 */
	public WorkflowEvent getNextEvent() throws InterruptedException
	{
		do
		{
			synchronized (this)
			{
				for (int i = NUM_PRIORITIES - 1; i >= 0; i--)
				{
					if (queues[i].head != null)
					{
						EventQueueItem eqi = queues[i].head;
						queues[i].head = eqi.next;
						if (eqi.next == null)
						{
							queues[i].tail = null;
						}
						return eqi.event;
					}
				}
				EventAutoShutdown.getInstance().notifyThreadFree(dispatchThread);
				wait();
			}
		}
		while (true);
	}

	WorkflowEvent getNextEvent(int id) throws InterruptedException
	{
		do
		{
			synchronized (this)
			{
				for (int i = 0; i < NUM_PRIORITIES; i++)
				{
					for (EventQueueItem entry = queues[i].head, prev = null; entry != null; prev = entry, entry = entry.next)
					{
						if (entry.id == id)
						{
							if (prev == null)
							{
								queues[i].head = entry.next;
							}
							else
							{
								prev.next = entry.next;
							}
							if (queues[i].tail == entry)
							{
								queues[i].tail = prev;
							}
							return entry.event;
						}
					}
				}

				this.waitForID = id;

				wait();

				this.waitForID = 0;
			}
		}
		while (true);
	}

	/**
	 * Returns the first event on the <code>EventQueue</code>
	 * without removing it.
	 * @return the first event
	 */
	public synchronized WorkflowEvent peekEvent()
	{
		for (int i = NUM_PRIORITIES - 1; i >= 0; i--)
		{
			if (queues[i].head != null)
			{
				return queues[i].head.event;
			}
		}

		return null;
	}

	/**
	 * Returns the first event with the specified id, if any.
	 * @param id the id of the type of event desired
	 * @return the first event of the specified id or <code>null</code>
	 *    if there is no such event
	 */
	public synchronized WorkflowEvent peekEvent(int id)
	{
		for (int i = NUM_PRIORITIES - 1; i >= 0; i--)
		{
			EventQueueItem q = queues[i].head;
			for (; q != null; q = q.next)
			{
				if (q.id == id)
				{
					return q.event;
				}
			}
		}

		return null;
	}

	/**
	 * Dispatches an event. The manner in which the event is
	 * dispatched depends upon the type of the event and the
	 * type of the event's source object:
	 * <p> </p>
	 * <table border=1 summary="Event types, source types, and dispatch methods">
	 * <tr>
	 *     <th>Event Type</th>
	 *     <th>Source Type</th> 
	 *     <th>Dispatched To</th>
	 * </tr>
	 * <tr>
	 *     <td>ActiveEvent</td>
	 *     <td>Any</td>
	 *     <td>event.dispatch()</td>
	 * </tr>
	 * <tr>
	 *     <td>Other</td>
	 *     <td>WorkflowWork</td>
	 *     <td>event.dispatch()</td>
	 * </tr>
	 * <tr>
	 *     <td>Other</td>
	 *     <td>Other</td>
	 *     <td>No action (ignored)</td>
	 * </tr>
	 * </table>
	 * <p> </p>
	 * @param event an instance of <code>WorkflowEvent</code>,
	 * 		or a subclass of it
	 * @throws NullPointerException if <code>event</code> is <code>null</code>
	 */
	protected void dispatchEvent(WorkflowEvent event)
	{
		Object src = event.getSource();
		if (src instanceof EventAutoShutdown)
		{
			if (noEvents())
			{
				dispatchThread.stopDispatching();
			}
		}
		else if (event instanceof ActiveEvent)
		{
			// This could become the sole method of dispatching in time.
			setCurrentEventAndMostRecentTime(event);

			((ActiveEvent) event).dispatch();
		}
		else if (event instanceof JoinEvent)
		{
			((JoinEvent) event).dispatch();
		}
		else
		{
			System.err.println("unable to dispatch event: " + event);
		}
	}

	private synchronized long getMostRecentEventTime()
	{
		return (Thread.currentThread() == dispatchThread) ? mostRecentEventTime : System.currentTimeMillis();
	}

	private synchronized WorkflowEvent getCurrentEvent()
	{
		return (Thread.currentThread() == dispatchThread) ? ((WorkflowEvent) currentEvent.get()) : null;
	}

	final void initDispatchThread()
	{
		synchronized (this)
		{
			if (dispatchThread == null && !threadGroup.isDestroyed())
			{
				dispatchThread = (EventDispatchThread) AccessController.doPrivileged(new PrivilegedAction()
				{
					public Object run()
					{
						EventDispatchThread t = new EventDispatchThread(threadGroup, name, EventQueue.this);
						t.setContextClassLoader(classLoader);
						t.setPriority(Thread.NORM_PRIORITY + 1);
						t.setDaemon(false);
						return t;
					}
				});
				EventAutoShutdown.getInstance().notifyThreadBusy(dispatchThread);
				dispatchThread.start();
			}
		}
	}

	final void detachDispatchThread()
	{
		dispatchThread = null;
	}

	/*
	 * Gets the <code>EventDispatchThread</code> for this
	 * <code>EventQueue</code>.
	 * @return the event dispatch thread associated with this event queue
	 *         or <code>null</code> if this event queue doesn't have a
	 *	       working thread associated with it
	 * @see    EventQueue#initDispatchThread
	 * @see    EventQueue#detachDispatchThread
	 */
	final EventDispatchThread getDispatchThread()
	{
		return dispatchThread;
	}

	private synchronized void setCurrentEventAndMostRecentTime(WorkflowEvent e)
	{
		if (Thread.currentThread() != dispatchThread)
		{
			return;
		}

		currentEvent = new WeakReference(e);
		mostRecentEventTime = e.getWhen();
	}


}

/**
 * The Queue object holds pointers to the beginning and end of one internal
 * queue. An EventQueue object is composed of multiple internal Queues, one
 * for each priority supported by the EventQueue. All Events on a particular
 * internal Queue have identical priority.
 */
class Queue
{
	EventQueueItem head;
	EventQueueItem tail;
}

class EventQueueItem
{
	WorkflowEvent event;
	int id;
	EventQueueItem next;

	EventQueueItem(WorkflowEvent evt)
	{
		event = evt;
		id = evt.getID();
	}
}