/*
 * Building:
 * gcc -o test-tfp test-tfp.c -g -Wall `pkg-config --cflags --libs clutter-1.0 clutter-x11-1.0 clutter-glx-1.0 cairo cairo-xlib`
 *
 * Running:
 * CLUTTER_VBLANK=none CLUTTER_DEFAULT_FPS=10000 ./test-tfp
 */

#include <X11/Xlib.h>
#include <math.h>
#include <glib.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <clutter/clutter.h>
#include <clutter/x11/clutter-x11.h>
#include <clutter/glx/clutter-glx.h>

static gint n_balls = 10;
static gint width;
static gint height;

static gint current_res = 0;
static gint *res[] = {
  (gint[]){ 512, 384 },
  (gint[]){ 640, 480 },
  (gint[]){ 720, 576 },
  (gint[]){ 800, 600 },
  (gint[]){ 1000, 768 },
  (gint[]){ 1024, 768 }
};

typedef struct {
  float x;
  float y;
  float r;
  float dir_x;
  float dir_y;
} Ball;

typedef struct {
  GC gc;
  Display *dpy;
  Pixmap buffer;
  Pixmap front_buffer;
  ClutterActor *stage;
  ClutterActor *tfp;
  cairo_surface_t *surface;
  Ball *balls;
  gint frames;
} Data;

static gboolean
animate_balls (Data *data)
{
  int i;
  cairo_t *cr;

  if (!data->tfp)
    return FALSE;

  cr = cairo_create (data->surface);
  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
  cairo_paint (cr);

  for (i = 0; i < n_balls; i++)
    {
      Ball *ball = &data->balls[i];

      /* Draw ball */
      cairo_arc (cr,
                 ball->x,
                 ball->y,
                 ball->r,
                 0.0,
                 2 * M_PI);

      cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.5);
      cairo_fill_preserve (cr);

      cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
      cairo_stroke (cr);

      /* Animate ball */
      ball->x += ball->dir_x;
      ball->y += ball->dir_y;

      if ((ball->x + ball->r >= width) ||
          (ball->x - ball->r < 0.0))
        ball->dir_x = -ball->dir_x;
      if ((ball->y + ball->r >= height) ||
          (ball->y - ball->r < 0.0))
        ball->dir_y = -ball->dir_y;
    }

  cairo_destroy (cr);

  /* Copy from back to front buffer */
  XCopyArea (data->dpy,
             data->buffer,
             data->front_buffer,
             data->gc,
             0, 0,
             width, height,
             0, 0);
  XSync (data->dpy, False);

  /* Schedule a redraw */
  clutter_actor_queue_redraw (data->stage);
  data->frames ++;

  return TRUE;
}

static void
create_pixmaps (Display          *dpy,
                Pixmap           *buffer,
                Pixmap           *front_buffer,
                cairo_surface_t **surface)
{
  int screen = DefaultScreen (dpy);
  *buffer = XCreatePixmap (dpy,
                           RootWindow (dpy, screen),
                           width, height,
                           DefaultDepth (dpy, screen));
  *front_buffer = XCreatePixmap (dpy,
                                 RootWindow (dpy, screen),
                                 width, height,
                                 DefaultDepth (dpy, screen));
  XSync (dpy, False);

  *surface = cairo_xlib_surface_create (dpy,
                                        *buffer,
                                        DefaultVisual (dpy, screen),
                                        width, height);
}

static gboolean
switch_res (Data *data)
{
  printf ("At %dx%d, rendered %d frames in 10 seconds\n",
          res[current_res][0], res[current_res][1], data->frames);

  clutter_container_remove_actor (CLUTTER_CONTAINER (data->stage),
                                  data->tfp);
  data->tfp = NULL;

  XFreePixmap (data->dpy, data->buffer);
  XFreePixmap (data->dpy, data->front_buffer);
  XFreeGC (data->dpy, data->gc);
  cairo_surface_destroy (data->surface);

  current_res ++;
  if (current_res >= G_N_ELEMENTS(res))
    {
      clutter_main_quit ();
      return FALSE;
    }

  width = res[current_res][0];
  height = res[current_res][1];

  clutter_actor_set_size (data->stage, width, height);
  create_pixmaps (data->dpy,
                  &data->buffer,
                  &data->front_buffer,
                  &data->surface);
  data->gc = XCreateGC (data->dpy, data->front_buffer, 0, NULL);

  data->tfp = clutter_glx_texture_pixmap_new_with_pixmap (data->front_buffer);
  clutter_x11_texture_pixmap_set_automatic (
    CLUTTER_X11_TEXTURE_PIXMAP (data->tfp), TRUE);
  clutter_container_add_actor (CLUTTER_CONTAINER (data->stage), data->tfp);
  clutter_actor_lower_bottom (data->tfp);

  data->frames = 0;

  return TRUE;
}

int
main (int argc, char **argv)
{
  int i;
  Data data;
  Display *dpy;
  cairo_surface_t *surface;
  Pixmap buffer, front_buffer;
  ClutterActor *stage, *tfp, *label;

  clutter_init (&argc, &argv);

  width = res[current_res][0];
  height = res[current_res][1];

  /* Create window */
  stage = clutter_stage_get_default ();
  clutter_actor_set_size (stage, width, height);

  /* Create pixmaps/cairo surface */
  dpy = XOpenDisplay (g_getenv ("DISPLAY"));
  create_pixmaps (dpy, &buffer, &front_buffer, &surface);

  /* Create tfp texture actor */
  tfp = clutter_glx_texture_pixmap_new_with_pixmap (front_buffer);
  clutter_x11_texture_pixmap_set_automatic (CLUTTER_X11_TEXTURE_PIXMAP (tfp),
                                            TRUE);

  /* Set it reactive, for testing purposes (will activate picking) */
  clutter_actor_set_reactive (tfp, TRUE);

  /* Create output label */
  label = clutter_text_new_with_text ("Sans 64", "");

  /* Parent and show everything */
  clutter_container_add (CLUTTER_CONTAINER (stage),
                         tfp,
                         label,
                         NULL);
  clutter_actor_show_all (stage);

  /* Setup drawing loop */
  data.stage = stage;
  data.tfp = tfp;
  data.surface = surface;
  data.buffer = buffer;
  data.front_buffer = front_buffer;
  data.dpy = dpy;
  data.gc = XCreateGC (dpy, front_buffer, 0, NULL);
  data.balls = g_new (Ball, n_balls);
  data.frames = 0;
  for (i = 0; i < n_balls; i++)
    {
      data.balls[i].r = g_random_double_range (10.0, 100.0);
      data.balls[i].x = g_random_double_range (data.balls[i].r,
                                               width - 1 - data.balls[i].r);
      data.balls[i].y = g_random_double_range (data.balls[i].r,
                                               height - 1 - data.balls[i].r);
      data.balls[i].dir_x = (float)((g_random_int_range (0, 2) * 2) - 1);
      data.balls[i].dir_y = (float)((g_random_int_range (0, 2) * 2) - 1);
    }
  clutter_threads_add_repaint_func ((GSourceFunc)animate_balls,
                                    &data,
                                    NULL);

  g_timeout_add_full (G_PRIORITY_HIGH,
                      10000,
                      (GSourceFunc)switch_res,
                      &data,
                      NULL);

  /* Start */
  clutter_main ();

  return 0;
}


