#include <gmodule.h>
#include <clutter/clutter.h>
#include <string.h>

#define DOT_SIZE 2
#define TEX_SIZE 64

typedef struct _CallbackData CallbackData;

struct _CallbackData
{
  ClutterActor *stage;
  ClutterActor *tex;
  ClutterActor *box;
  ClutterMotionEvent event;
  guint idle_source;
};

int
test_stage_read_pixels_main (int argc, char **argv);

const char *
test_stage_read_pixels_describe (void);

static ClutterActor *
make_label (void)
{
  ClutterActor *label;
  gchar *text;
  const char *argv[] = { "ls", "--help", NULL };

  label = clutter_text_new ();
  clutter_text_set_font_name (CLUTTER_TEXT (label), "Sans 10");

  if (g_spawn_sync (NULL, (char **) argv, NULL,
		    G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_SEARCH_PATH,
		    NULL, NULL, &text, NULL, NULL, NULL))
    {
      clutter_text_set_text (CLUTTER_TEXT (label), text);
      g_free (text);
    }

  return label;
}

static ClutterActor *
make_tex (void)
{
  ClutterActor *tex = clutter_texture_new ();

  clutter_actor_set_size (tex, TEX_SIZE * 2, TEX_SIZE * 2);

  return tex;
}

static ClutterActor *
make_box (void)
{
  ClutterActor *box;
  static const ClutterColor blue = { 0x00, 0x00, 0xff, 0xff };

  box = clutter_rectangle_new_with_color (&blue);
  clutter_actor_set_size (box, DOT_SIZE + 2, DOT_SIZE + 2);
  clutter_actor_hide (box);

  return box;
}

static gboolean
on_motion_idle (gpointer user_data)
{
  CallbackData *data = (CallbackData *) user_data;
  guchar *pixels, *p;
  gfloat stage_width, stage_height;
  gint x, y;

  data->idle_source = 0;

  clutter_actor_get_size (data->stage, &stage_width, &stage_height);

  x = CLAMP (data->event.x - TEX_SIZE / 2, 0, stage_width - TEX_SIZE);
  y = CLAMP (data->event.y - TEX_SIZE / 2, 0, stage_height - TEX_SIZE);

  clutter_actor_set_position (data->box,
                              x + TEX_SIZE / 2 - 1,
			      y + TEX_SIZE / 2 - 1);
  clutter_actor_show (data->box);

  /* Redraw so that the layouting will be done and the box will be
     drawn in the right position */
  clutter_stage_ensure_redraw (CLUTTER_STAGE (data->stage));

  pixels = clutter_stage_read_pixels (CLUTTER_STAGE (data->stage),
				      x, y,
                                      TEX_SIZE, TEX_SIZE);

  /* Make a red dot in the center */
  p = pixels + (TEX_SIZE / 2 - DOT_SIZE / 2) * TEX_SIZE * 4
    + (TEX_SIZE / 2 - DOT_SIZE / 2) * 4;
  for (y = 0; y < DOT_SIZE; y++)
    {
      for (x = 0; x < DOT_SIZE; x++)
	{
	  *(p++) = 255;
	  memset (p, 0, 3);
	  p += 3;
	}
      p += TEX_SIZE * 4 - DOT_SIZE * 4;
    }

  /* Set all of the alpa values to full */
  for (p = pixels + TEX_SIZE * TEX_SIZE * 4; p > pixels; p -= 4)
    *(p - 1) = 255;

  clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (data->tex),
				     pixels, TRUE,
				     TEX_SIZE, TEX_SIZE,
				     TEX_SIZE * 4, 4, 0, NULL);
  g_free (pixels);

  return FALSE;
}

static gboolean
on_motion (ClutterActor *stage, ClutterMotionEvent *event, CallbackData *data)
{
  /* Handle the motion event in an idle handler so that multiple
     events will be combined into one */
  if (data->idle_source == 0)
    data->idle_source = clutter_threads_add_idle (on_motion_idle, data);

  data->event = *event;

  return FALSE;
}

G_MODULE_EXPORT int
test_stage_read_pixels_main (int argc, char **argv)
{
  CallbackData data;

  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  data.idle_source = 0;
  data.stage = clutter_stage_new ();
  clutter_stage_set_title (CLUTTER_STAGE (data.stage), "Read Pixels");
  g_signal_connect (data.stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  data.tex = make_tex ();
  data.box = make_box ();
  clutter_actor_set_position (data.tex,
			      clutter_actor_get_width (data.stage)
			      - clutter_actor_get_width (data.tex),
			      clutter_actor_get_height (data.stage)
			      - clutter_actor_get_height (data.tex));

  clutter_container_add (CLUTTER_CONTAINER (data.stage),
			 make_label (), data.tex, data.box, NULL);

  g_signal_connect (data.stage, "motion-event", G_CALLBACK (on_motion), &data);

  clutter_actor_show (data.stage);

  clutter_main ();

  return 0;
}

G_MODULE_EXPORT const char *
test_stage_read_pixels_describe (void)
{
  return "Read back pixels from a Stage.";
}
