/*
 * Example of advanced usage of GMainLoop.  Splits the loop between two
 * threads, a UI thread and a polling thread.
 *
 * Currently fails under Windows.  Maybe caused by buggy condition
 * variables?
 *
 * Author: Paolo Bonzini <bonzini@gnu.org>
 */

#include <glib.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
#include <assert.h>

/* Synchronization objects for the two threads.  */

static GMutex *mutex;
static GCond *cond;
static GCond *cond_dispatch;
static volatile gboolean queued;

/* A couple of wrappers around GLib functions, to ensure useful
   pre/postconditions.  */

static void
main_context_wait (GMainContext *context, GCond *cond, GMutex *mutex)
{
  while (!g_main_context_wait (context, cond, mutex));
  assert (g_main_context_is_owner (context));
}

static void
main_context_release (GMainContext *context)
{
  assert (g_main_context_is_owner (context));
  g_main_context_release (context);
}

/* This function runs in the "UI" thread.  */

static void
main_context_iterate (GMainLoop *loop)
{
  GMainContext *context = g_main_loop_get_context (loop);

  g_mutex_lock (mutex);
  while (g_main_loop_is_running (loop))
    {
      /* Wait for events to be there.  */
      while (!queued)
        g_cond_wait (cond_dispatch, mutex);

      /* Now acquire the context and dispatch.  No need to keep the mutex,
         except while we operate on the condition variables; even
         g_main_context_release locks the mutex for us.  */
      main_context_wait (context, cond, mutex);
      g_mutex_unlock (mutex);
      g_main_context_dispatch (context);
      main_context_release (context);

      /* Restart the polling thread.  */
      g_mutex_lock (mutex);
      queued = FALSE;
      g_cond_broadcast (cond_dispatch);
    }

  g_mutex_unlock (mutex);
}

/* This function takes care of the polling and wakes up the UI thread.  */
static gpointer
main_loop_thread (gpointer loop_ptr)
{
  static GPollFD *fds;
  static int allocated_nfds;
  GMainLoop *loop = loop_ptr;
  GMainContext *context = g_main_loop_get_context (loop);

  fds = g_new (GPollFD, 20);
  allocated_nfds = 20;

  /* Mostly based on g_main_context_iterate (a static function in gmain.c)
     except that we have to use our own mutex and that g_main_context_dispatch
     is replaced by waiting on cond_dispatch.  */

  g_mutex_lock (mutex);
  while (g_main_loop_is_running (loop))
    {
      int nfds, maxprio, timeout;

      main_context_wait (context, cond, mutex);
      g_main_context_prepare (context, &maxprio);
      while ((nfds = g_main_context_query (context, maxprio,
                                           &timeout, fds, allocated_nfds))
             > allocated_nfds)
        {
          g_free (fds);
          fds = g_new (GPollFD, nfds);
          allocated_nfds = nfds;
        }

      /* Release the context so that the other thread can dispatch while
         this one polls.  g_main_context_release locks the mutex for us, so
         always unlock it before calling it!  (We do the same below too.)  */
      g_mutex_unlock (mutex);
      main_context_release (context);

      g_poll (fds, nfds, timeout);

      g_mutex_lock (mutex);
      main_context_wait (context, cond, mutex);
      g_main_context_check (context, maxprio, fds, nfds);
      g_mutex_unlock (mutex);

      main_context_release (context);

      /* Dispatch on the other thread and wait for it to rendez-vous.  
         Reacquire the mutex to operate on the condition variable.  */
      g_mutex_lock (mutex);
      queued = TRUE;
      g_cond_broadcast (cond_dispatch);
      do
        g_cond_wait (cond_dispatch, mutex);
      while (queued);
    }

  g_main_loop_unref (loop);
  loop = NULL;
  g_mutex_unlock (mutex);
  return NULL;
}

static GMainLoop *
create_main_loop_thread (void)
{
  mutex = g_mutex_new ();
  cond = g_cond_new ();
  cond_dispatch = g_cond_new ();

  GMainLoop *loop = g_main_loop_new (NULL, TRUE);

  /* Add a second reference to be released when the thread exits.  The first
     one is returned.  */
  g_main_loop_ref (loop);
  GThread *thread = g_thread_create (main_loop_thread, loop, TRUE, NULL);
  if (!thread)
    {
      /* Destroy both references, since the thread won't have any occasion
         to release his.  */
      g_main_loop_unref (loop);
      g_main_loop_unref (loop);
      return NULL;
    }

  return loop;
}

/* Minimal example program.  */

static void
main_loop_quit (GtkObject *unused, GMainLoop *loop)
{
  g_main_loop_quit (loop);
}

void
main (int argc, char **argv)
{
  gtk_init (&argc, &argv);
  g_thread_init (NULL);
  gdk_threads_init ();

  GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_show (window);

  GMainLoop *loop = create_main_loop_thread ();
  gtk_signal_connect (G_OBJECT (window), "destroy",
                      G_CALLBACK (main_loop_quit), loop);

  main_context_iterate (loop);
  g_main_loop_unref (loop);
}
