/*
 * 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 {
        try {
            cleanup();
        } finally {
            super.finalize();
        }
    }

    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();
    }
}
