/* Copyright (C) 2000  Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

package java.awt;

import java.awt.event.*;
import java.util.EmptyStackException;
import java.lang.reflect.InvocationTargetException;

/* Written using on-line Java 2 Platform Standard Edition v1.3 API 
 * Specification, as well as "The Java Class Libraries", 2nd edition 
 * (Addison-Wesley, 1998).
 * Status:  Believed complete, but untested. Check FIXME's.
 */

/** @author Bryce McKinlay */

public class EventQueue
{
  private static final int INITIAL_QUEUE_DEPTH = 8;
  private AWTEvent[] queue = new AWTEvent[INITIAL_QUEUE_DEPTH];
  
  private int next_in = 0; // Index where next event will be added to queue
  private int next_out = 0; // Index of next event to be removed from queue

  private EventQueue next;
  private EventQueue prev;

  private EventDispatchThread dispatchThread = new EventDispatchThread(this);

  public EventQueue()
  {
  }
  
  public synchronized AWTEvent getNextEvent()
    throws InterruptedException
  {
    if (next != null)
      return next.getNextEvent();
      
    while (next_in == next_out)
      wait();
    
    AWTEvent res = queue[next_out];
    
    if (++next_out == queue.length)
      next_out = 0;
    return res;
  }
  
  /** @specnote Does not block. Returns null if there are no events on the 
   *            queue. 
   */ 
  public synchronized AWTEvent peekEvent()
  {
    if (next != null)
      return next.peekEvent();
      
    if (next_in != next_out)
      return queue[next_out];
    else return null;
  }
  
  /** @specnote Does not block. Returns null if there are no matching events 
   *            on the queue. 
   */ 
  public synchronized AWTEvent peekEvent(int id)
  {
    if (next != null)
      return next.peekEvent(id);

    int i = next_out;
    while (i != next_in)
      {
        AWTEvent qevt = queue[i];
        if (qevt.id == id)
	  return qevt;
      }
    return null;
  }
  
  public synchronized void postEvent(AWTEvent evt)
  {
    if (next != null)
      {
        next.postEvent(evt);
	return;
      }
    // FIXME: Security checks?
    
    /* Check for any events already on the queue with the same source 
       and ID. */	
    int i = next_out;
    while (i != next_in)
      {
        AWTEvent qevt = queue[i];
	Object src;
	if (qevt.id == evt.id
	    && (src = qevt.getSource()) == evt.getSource()
	    && src instanceof Component)
	  {
	    /* If there are, call coalesceEvents on the source component 
	       to see if they can be combined. */
	    Component srccmp = (Component) src;
	    AWTEvent coalesced_evt = srccmp.coalesceEvents(qevt, evt);
	    if (coalesced_evt != null)
	      {
	        /* Yes. Replace the existing event with the combined event. */
	        queue[i] = coalesced_evt;
		return;
	      }
            break;
	  }
	if (++i == queue.length)
	  i = 0;
      }

    queue[next_in] = evt;    
    if (++next_in == queue.length)
      next_in = 0;
      
    if (next_in == next_out)
      {
        /* Queue is full. Extend it. */
        AWTEvent[] oldQueue = queue;
	queue = new AWTEvent[queue.length * 2];
	
	int len = oldQueue.length - next_out;
	System.arraycopy(oldQueue, next_out, queue, 0, len);
	if (next_out != 0)
	  System.arraycopy(oldQueue, 0, queue, len, next_out);
	  
	next_out = 0;
	next_in = oldQueue.length;
      }
    notify();
  }
  
  /** @since JDK1.2 */
  public static void invokeAndWait(Runnable runnable)
    throws InterruptedException, InvocationTargetException
  {
    EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 
    Thread current = Thread.currentThread();
    if (current == eq.dispatchThread)
      throw new Error("Can't call invokeAndWait from event dispatch thread");
  
    InvocationEvent ie = 
      new InvocationEvent(eq, runnable, current, true);

    synchronized (current)
      {
	eq.postEvent(ie);
	current.wait();
      }

    Exception exception;
    
    if ((exception = ie.getException()) != null)
      throw new InvocationTargetException(exception);
  }
  
  /** @since JDK1.2 */
  static void invokeLater(Runnable runnable)
  {
    EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 

    InvocationEvent ie = 
      new InvocationEvent(eq, runnable, null, false);

    eq.postEvent(ie);
  }
  
  static boolean isDispatchThread()
  {
    EventQueue eq = Toolkit.getDefaultToolkit().getSystemEventQueue(); 
    return (Thread.currentThread() == eq.dispatchThread);
  }
  
  /** Allows a custom EventQueue implementation to replace this one. 
    * All pending events are transferred to the new queue. Calls to postEvent,
    * getNextEvent, and peekEvent are forwarded to the pushed queue until it
    * is removed with a pop().
    */
  public synchronized void push(EventQueue newEventQueue)
  {
    int i = next_out;
    while (i != next_in)
      {
        newEventQueue.postEvent(queue[i]);
	next_out = i;
	if (++i == queue.length)
	  i = 0;
      }

    next = newEventQueue;
    newEventQueue.prev = this;    
  }
  
  /** Transfer any pending events from this queue back to the parent queue that
    * was previously push()ed. Event dispatch from this queue is suspended. */
  protected void pop() throws EmptyStackException
  {
    if (prev == null)
      throw new EmptyStackException();
    
    // Don't synchronize both this and prev at the same time, or deadlock could
    // occur.
    synchronized (prev)
      {
	prev.next = null;
      }
      
    synchronized (this)
      {
	int i = next_out;
	while (i != next_in)
	  {
            prev.postEvent(queue[i]);
	    next_out = i;
	    if (++i == queue.length)
	      i = 0;
	  }
      }
  }
  
  protected void dispatchEvent(AWTEvent evt)
  {
    if (evt instanceof ActiveEvent)
      {
        ActiveEvent active_evt = (ActiveEvent) evt;
	active_evt.dispatch();
      }
    else
      {
	Object source = evt.getSource();

	if (source instanceof Component)
	  {
            Component srccmp = (Component) source;
	    srccmp.dispatchEvent(evt);
	  }
	else if (source instanceof MenuComponent)
	  {
	    MenuComponent srccmp = (MenuComponent) source;
	    srccmp.dispatchEvent(evt);
	  }
      }
  }
}