/*
 * Java-Gnome Bindings Library
 *
 * Copyright 1998-2004 the Java-Gnome Team, all rights reserved.
 *
 * The Java-Gnome bindings library is free software distributed under
 * the terms of the GNU Library General Public License version 2.
 */
package org.gnu.glib;

import java.util.LinkedList;
import org.gnu.gtk.Gtk;

/**
 * Provides methods for adding custom event sources. The main glib thread will
 * poll this ojbect as part of its main loop. If any events are pending, they
 * will be executed from within the main glib loop.
 *
 * <p>For multithreaded Gtk/Gnome applications, all gui modifications must be
 * done from within the main loop. The methods of this class allow you to do
 * this.
 *
 * <p>As events run on the main glib loop, only short methods should be executed
 * in it.
 *
 * @author Mark Howard &lt;mh@debian.org&gt;
 */
public final class CustomEvents extends GObject {

	private static Object lock = new Object();
	private static LinkedList toRunLater = new LinkedList();
	private static LinkedList toRunAndWait = new LinkedList();
	private static int count = 0;

	private CustomEvents(){ } // no initialization
	

	/**
	 * Adds a new event to the queue. <code>target.run()</code> will be called
	 * in the next iteration of the glib (gtk) main loop. This method will
	 * return immediately after adding the item to the queue.
	 */
	public static void addEvent( Runnable target ){
		synchronized( lock ){
			toRunLater.addLast( target );
			setPending( +1 );
		}
	}

	/**
	 * Adds a new event to the queue. <code>target.run()</code> will be called
	 * in the next iteration of the glib (gtk) main loop.  This method waits
	 * until the method call has completed before returning.
	 */
	public static void addEventAndWait( Runnable target ){
        if (Gtk.isGtkThread()) {
            target.run();
        } else {
		synchronized(lock){ 
			toRunAndWait.addLast( target );
			setPending( +1 );
			
			int c = count;
			while (!( count > c ||  (count < -15 && c > 16 ) )  ){
				try{
					lock.wait();			
				}catch( InterruptedException e ){
					// the loop will try again.
				}
			}
		}
        }
	}

	/** 
	 * Executes the pending events. This is called from within the gtk main
	 * thread.
	 */
	public static final void runEvents(){
	
		LinkedList listCopy = null;
		LinkedList newList = new LinkedList();
		int toRun = 0;
		synchronized( lock ){ 
			toRun = toRunLater.size();
			if (toRun >0){
			listCopy = toRunLater;
			toRunLater = newList;
			}
		}

		for( int i = 0; i < toRun; i++){
			((Runnable) listCopy.removeFirst()).run();
		}
		
		newList = new LinkedList();
		synchronized( lock ){			
			setPending( - toRun );
			toRun = toRunAndWait.size();
			if (toRun >0){
			listCopy = toRunAndWait;
			toRunAndWait = newList;
			}
		}
		
		for( int i = 0; i < toRun; i++){
			((Runnable) listCopy.removeFirst()).run();
		}
		
		synchronized( lock ){			
			setPending( - toRun );
			count++;
			lock.notifyAll();
		}
	}

	
	/**
     * Do not call this method; it's only purpose is to remove the event source
	 * once it is finished with.
     */
    protected final void finalize() throws Throwable {
		cleanup();
	}

	private static volatile int pending = 0;

	/**
	 * Let the native layer know if there are any events waiting to be called.
	 * If there aren't, it doesn't need to call runEvents.
	 */
	private static void setPending( int p ){
		pending = pending + p;
		if (pending == 0 && p != 0)
			setEventsPending( false );
		if (pending == p && p != 0)
			setEventsPending( true );
		
	}

    private static native void init();
	private static native void cleanup();
	private static native void setEventsPending( boolean pending );

    static {
		init();
    }
}
