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

#include "test-conform-common.h"

static const ClutterColor stage_color = { 0x0, 0x0, 0x0, 0xff };

static TestConformGLFunctions gl_functions;

typedef struct _TestState
{
  ClutterActor *stage;
} TestState;

#ifndef GL_EXTENSIONS
#define GL_EXTENSIONS 0x1F03
#endif
#ifndef GL_TEXTURE_RECTANGLE_ARB
#define GL_TEXTURE_RECTANGLE_ARB 0x84F5
#endif
#ifndef GL_UNPACK_ROW_LENGTH
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#endif
#ifndef GL_UNPACK_ALIGNMENT
#define GL_UNPACK_ALIGNMENT 0x0CF5
#endif
#ifndef GL_UNPACK_SKIP_ROWS
#define GL_UNPACK_SKIP_ROWS 0x0CF3
#endif
#ifndef GL_UNPACK_SKIP_PIXELS
#define GL_UNPACK_SKIP_PIXELS 0x0CF4
#endif
#ifndef GL_RGBA
#define GL_RGBA 0x1908
#endif
#ifndef GL_UNSIGNED_BYTE
#define GL_UNSIGNED_BYTE 0x1401
#endif
#ifndef GL_NO_ERROR
#define GL_NO_ERROR 0x0
#endif
#ifndef GL_TEXTURE_BINDING_RECTANGLE_ARB
#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6
#endif

static CoglHandle
create_source_rect (void)
{
  int x, y;
  int prev_unpack_row_length;
  int prev_unpack_alignment;
  int prev_unpack_skip_rows;
  int prev_unpack_skip_pixles;
  int prev_rectangle_binding;
  guint8 *data = g_malloc (256 * 256 * 4), *p = data;
  CoglHandle tex;
  guint gl_tex;

  for (y = 0; y < 256; y++)
    for (x = 0; x < 256; x++)
      {
        *(p++) = x;
        *(p++) = y;
        *(p++) = 0;
        *(p++) = 255;
      }

  /* We are about to use OpenGL directly to create a TEXTURE_RECTANGLE
   * texture so we need to save the state that we modify so we can
   * restore it afterwards and be sure not to interfere with any state
   * caching that Cogl may do internally.
   */
  gl_functions.glGetIntegerv (GL_UNPACK_ROW_LENGTH, &prev_unpack_row_length);
  gl_functions.glGetIntegerv (GL_UNPACK_ALIGNMENT, &prev_unpack_alignment);
  gl_functions.glGetIntegerv (GL_UNPACK_SKIP_ROWS, &prev_unpack_skip_rows);
  gl_functions.glGetIntegerv (GL_UNPACK_SKIP_PIXELS, &prev_unpack_skip_pixles);
  gl_functions.glGetIntegerv (GL_TEXTURE_BINDING_RECTANGLE_ARB,
                              &prev_rectangle_binding);

  gl_functions.glPixelStorei (GL_UNPACK_ROW_LENGTH, 256);
  gl_functions.glPixelStorei (GL_UNPACK_ALIGNMENT, 8);
  gl_functions.glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
  gl_functions.glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);

  gl_functions.glGenTextures (1, &gl_tex);
  gl_functions.glBindTexture (GL_TEXTURE_RECTANGLE_ARB, gl_tex);
  gl_functions.glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0,
                             GL_RGBA, 256, 256, 0,
                             GL_RGBA,
                             GL_UNSIGNED_BYTE,
                             data);

  /* Now restore the original GL state as Cogl had left it */
  gl_functions.glPixelStorei (GL_UNPACK_ROW_LENGTH, prev_unpack_row_length);
  gl_functions.glPixelStorei (GL_UNPACK_ALIGNMENT, prev_unpack_alignment);
  gl_functions.glPixelStorei (GL_UNPACK_SKIP_ROWS, prev_unpack_skip_rows);
  gl_functions.glPixelStorei (GL_UNPACK_SKIP_PIXELS, prev_unpack_skip_pixles);
  gl_functions.glBindTexture (GL_TEXTURE_RECTANGLE_ARB, prev_rectangle_binding);

  g_assert (gl_functions.glGetError () == GL_NO_ERROR);

  g_free (data);

  tex = cogl_texture_new_from_foreign (gl_tex,
                                       GL_TEXTURE_RECTANGLE_ARB,
                                       256, 256, 0, 0,
                                       COGL_PIXEL_FORMAT_RGBA_8888);

  return tex;
}

static CoglHandle
create_source_2d (void)
{
  int x, y;
  guint8 *data = g_malloc (256 * 256 * 4), *p = data;
  CoglHandle tex;

  for (y = 0; y < 256; y++)
    for (x = 0; x < 256; x++)
      {
        *(p++) = 0;
        *(p++) = x;
        *(p++) = y;
        *(p++) = 255;
      }

  tex = cogl_texture_new_from_data (256, 256, COGL_TEXTURE_NONE,
                                    COGL_PIXEL_FORMAT_RGBA_8888_PRE,
                                    COGL_PIXEL_FORMAT_ANY,
                                    256 * 4,
                                    data);

  g_free (data);

  return tex;
}

static void
draw_frame (TestState *state)
{
  guint gl_tex;
  CoglHandle tex_rect = create_source_rect ();
  CoglHandle material_rect = cogl_material_new ();
  CoglHandle tex_2d = create_source_2d ();
  CoglHandle material_2d = cogl_material_new ();

  g_assert (tex_rect != COGL_INVALID_HANDLE);

  cogl_material_set_layer (material_rect, 0, tex_rect);
  cogl_material_set_layer_filters (material_rect, 0,
                                   COGL_MATERIAL_FILTER_NEAREST,
                                   COGL_MATERIAL_FILTER_NEAREST);

  cogl_material_set_layer (material_2d, 0, tex_2d);
  cogl_material_set_layer_filters (material_2d, 0,
                                   COGL_MATERIAL_FILTER_NEAREST,
                                   COGL_MATERIAL_FILTER_NEAREST);

  cogl_set_source (material_rect);

  /* Render the texture repeated horizontally twice */
  cogl_rectangle_with_texture_coords (0.0f, 0.0f, 512.0f, 256.0f,
                                      0.0f, 0.0f, 2.0f, 1.0f);
  /* Render the top half of the texture to test without repeating */
  cogl_rectangle_with_texture_coords (0.0f, 256.0f, 256.0f, 384.0f,
                                      0.0f, 0.0f, 1.0f, 0.5f);

  cogl_set_source (material_2d);

  /* Render the top half of a regular 2D texture */
  cogl_rectangle_with_texture_coords (256.0f, 256.0f, 512.0f, 384.0f,
                                      0.0f, 0.0f, 1.0f, 0.5f);

  /* Flush the rendering now so we can safely delete the texture */
  cogl_flush ();

  cogl_handle_unref (material_rect);

  /* Cogl doesn't destroy foreign textures so we have to do it manually */
  cogl_texture_get_gl_texture (tex_rect, &gl_tex, NULL);
  gl_functions.glDeleteTextures (1, &gl_tex);
  cogl_handle_unref (tex_rect);
}

static void
validate_result (TestState *state)
{
  guint8 *data, *p;
  int x, y;

  p = data = g_malloc (512 * 384 * 4);

  cogl_read_pixels (0, 0, 512, 384,
                    COGL_READ_PIXELS_COLOR_BUFFER,
                    COGL_PIXEL_FORMAT_RGBA_8888,
                    data);

  for (y = 0; y < 384; y++)
    for (x = 0; x < 512; x++)
      {
        if (x >= 256 && y >= 256)
          {
            g_assert_cmpint (p[0], ==, 0);
            g_assert_cmpint (p[1], ==, x & 0xff);
            g_assert_cmpint (p[2], ==, y & 0xff);
          }
        else
          {
            g_assert_cmpint (p[0], ==, x & 0xff);
            g_assert_cmpint (p[1], ==, y & 0xff);
            g_assert_cmpint (p[2], ==, 0);
          }
        p += 4;
      }

  g_free (data);

  /* Comment this out to see what the test paints */
  clutter_main_quit ();
}

static void
on_paint (ClutterActor *actor, TestState *state)
{
  draw_frame (state);

  validate_result (state);
}

static gboolean
queue_redraw (gpointer stage)
{
  clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));

  return TRUE;
}

static gboolean
check_rectangle_extension (void)
{
  static const char rect_extension[] = "GL_ARB_texture_rectangle";
  const char *extensions =
    (const char *) gl_functions.glGetString (GL_EXTENSIONS);
  const char *extensions_end;

  extensions_end = extensions + strlen (extensions);

  while (extensions < extensions_end)
    {
      const char *end = strchr (extensions, ' ');

      if (end == NULL)
        end = extensions_end;

      if (end - extensions == sizeof (rect_extension) - 1 &&
          !memcmp (extensions, rect_extension, sizeof (rect_extension) - 1))
        return TRUE;

      extensions = end + 1;
    }

  return FALSE;
}

void
test_cogl_texture_rectangle (TestConformSimpleFixture *fixture,
                             gconstpointer data)
{
  TestState state;
  guint idle_source;
  guint paint_handler;

  state.stage = clutter_stage_new ();

  test_conform_get_gl_functions (&gl_functions);

  /* Check whether GL supports the rectangle extension. If not we'll
     just assume the test passes */
  if (check_rectangle_extension ())
    {
      clutter_stage_set_color (CLUTTER_STAGE (state.stage), &stage_color);

      /* We force continuous redrawing of the stage, since we need to skip
       * the first few frames, and we wont be doing anything else that
       * will trigger redrawing. */
      idle_source = g_idle_add (queue_redraw, state.stage);

      paint_handler = g_signal_connect_after (state.stage, "paint",
                                              G_CALLBACK (on_paint), &state);

      clutter_actor_show_all (state.stage);

      clutter_main ();

      g_source_remove (idle_source);
      g_signal_handler_disconnect (state.stage, paint_handler);

      clutter_actor_destroy (state.stage);

      if (g_test_verbose ())
        g_print ("OK\n");
    }
  else if (g_test_verbose ())
    g_print ("Skipping\n");
}

