#include <gtk/gtk.h>
#include <unistd.h>

typedef struct
{
  GdkWindow *window;
  const char *title;
  int width, height;
  gboolean retitle, resize;
}
Window;

typedef struct
{
  GMainLoop *loop;
  GThread *thread;
}
BGThread;

static BGThread *bgthread = NULL;


BGThread *bgthread_new (void);
gboolean window_render (Window *w);


Window *window_new (const char *title)
{
  Window *w;

  w = g_new0 (Window, 1);
  w->title = title;
  w->width = 320;
  w->height = 240;

  g_timeout_add_full (0, 25, window_render, (gpointer)w, NULL);

  if (!bgthread)
    bgthread = bgthread_new ();

  return w;
}

void window_event_func (GdkEvent *e, gpointer data)
{
  Window *w = data;

  switch (e->type)
    {
      case GDK_EXPOSE:
          /* For *actual* rendering code */
          break;

      case GDK_CONFIGURE:
          /* Size, position, or stacking order has changed. */
          w->width = e->configure.width;
          w->height = e->configure.height;
          break;

      default:
          printf ("Test: Unprocessed event %d\n", e->type);
          break;
    }
}

gboolean window_render (Window *w)
{
  /* If the window hasn't been opened yet, do so
   * we need to wait for the EXPOSE event to render anything, so return now
   */
  if (!w->window)
    {
      GdkWindowAttr attr;

      attr.width = w->width;
      attr.height = w->height;
      attr.title = g_strdup (w->title);
      attr.window_type = GDK_WINDOW_TOPLEVEL;
      attr.event_mask = GDK_ALL_EVENTS_MASK;

      w->window = gdk_window_new (NULL, &attr, 0);

      /* Set a reference to self in the new Gdk.Window's GObject user data
       * this is for event routing from the BGThread class to the proper
       * Window.
       */
      gdk_window_set_user_data (w->window, w);
      gdk_window_set_title (w->window, w->title);
      gdk_window_show (w->window);
      return TRUE;
    }

  if (w->resize)
    {
      w->resize = FALSE;
      gdk_window_resize (w->window, w->width, w->height);
    }

  if (w->retitle)
    {
      w->retitle = FALSE;
      gdk_window_set_title (w->window, w->title);
    }

  /* this should trigger an EXPOSE event */
    {
      GdkRectangle rect = { 0, 0, w->width, w->height };
      gdk_window_invalidate_rect (w->window, &rect, FALSE);
    }

  return TRUE;
}

void window_set_title (Window *w, const char *title)
{
  w->retitle = TRUE;
  w->title = title;
}

void window_resize (Window *w, int width, int height)
{
  w->resize = TRUE;
  w->width = width;
  w->height = height;
}


void bgthread_events (GdkEvent *e, gpointer data)
{
  Window *target;

  if (!e->any.window)
    {
      printf ("Not a window!\n");
      return;
    }

  gdk_window_get_user_data (e->any.window, &target);
  if (target)
    window_event_func (e, target);
}

void *bgthread_run (BGThread *b)
{
  gdk_init (NULL, NULL);

  gdk_event_handler_set (bgthread_events, b, NULL);

  g_main_loop_run (b->loop);

  return NULL;
}

BGThread *bgthread_new (void)
{
  BGThread *b;

  b = g_new0 (BGThread, 1);

  b->loop = g_main_loop_new (NULL, TRUE);

  b->thread = g_thread_create (bgthread_run, b, FALSE, NULL);
  if (!b->thread)
    printf ("ThreadError\n");

  return b;
}


int main (int argc, char **argv)
{
  Window *window1, *window2, *window3;

  g_thread_init (NULL);
  g_type_init ();

  window1 = window_new ("Window Test 1");
  printf ("Opened %s  %dx%d\n",
          window1->title, window1->width, window1->height);
  usleep (3000000);


  window2 = window_new ("Window Test 2");
  printf ("Opened %s  %dx%d\n",
          window2->title, window2->width, window2->height);
  usleep (3000000);


  window_resize (window1, 640, 480);
  printf ("Resized %s  %dx%d\n",
          window1->title, window1->width, window1->height);
  usleep (3000000);


  window3 = window_new ("Window Test 3");
  printf ("Opened %s %dx%d\n",
          window3->title, window3->width, window3->height);
  usleep (3000000);


  window_set_title (window2, "Retitled Window 2");
  printf ("Opened %s %dx%d\n",
          window2->title, window2->width, window2->height);
  usleep (3000000);


  window_resize (window3, 800, 300);
  window_set_title (window3, "Reconfigured Window 3");
  printf ("%s %dx%d\n", window3->title, window3->width, window3->height);
  usleep (5000000);

  printf ("Done.\n");

  return 0;
}
