#include <glib.h>
#include <gmodule.h>
#include <stdlib.h>
#include <clutter/clutter.h>
#include <cogl/cogl.h>
#include <math.h>

/* Defines the size and resolution of the quad mesh we morph:
 */
#define MESH_WIDTH  100.0 /* number of quads along x axis */
#define MESH_HEIGHT 100.0 /* number of quads along y axis */
#define QUAD_WIDTH  5.0 /* width in pixels of a single quad */
#define QUAD_HEIGHT 5.0 /* height in pixels of a single quad */

/* Defines a sine wave that sweeps across the mesh:
 */
#define WAVE_DEPTH    ((MESH_WIDTH * QUAD_WIDTH) / 16.0) /* peak amplitude */
#define WAVE_PERIODS  4.0
#define WAVE_SPEED    10.0

/* Defines a rippling sine wave emitted from a point:
 */
#define RIPPLE_CENTER_X ((MESH_WIDTH / 2.0) * QUAD_WIDTH)
#define RIPPLE_CENTER_Y ((MESH_HEIGHT / 2.0) * QUAD_HEIGHT)
#define RIPPLE_RADIUS   (MESH_WIDTH * QUAD_WIDTH)
#define RIPPLE_DEPTH    ((MESH_WIDTH * QUAD_WIDTH) / 16.0) /* peak amplitude */
#define RIPPLE_PERIODS  4.0
#define RIPPLE_SPEED    -10.0

/* Defines the width of the gaussian bell used to fade out the alpha
 * towards the edges of the mesh (starting from the ripple center):
 */
#define GAUSSIAN_RADIUS ((MESH_WIDTH * QUAD_WIDTH) / 6.0)

/* Our hues lie in the range [0, 1], and this defines how we map amplitude
 * to hues (before scaling by {WAVE,RIPPLE}_DEPTH)
 * As we are interferring two sine waves together; amplitudes lie in the
 * range [-2, 2]
 */
#define HSL_OFFSET    0.5 /* the hue that we map an amplitude of 0 too */
#define HSL_SCALE     0.25

typedef struct _TestState
{
  ClutterActor    *dummy;
  CoglHandle       buffer;
  float           *quad_mesh_verts;
  guint8          *quad_mesh_colors;
  guint16         *static_indices;
  guint            n_static_indices;
  CoglHandle       indices;
  ClutterTimeline *timeline;
  guint            frame_id;
} TestState;

int
test_cogl_vertex_buffer_main (int argc, char *argv[]);

const char *
test_cogl_vertex_buffer_describe (void);

static void
frame_cb (ClutterTimeline *timeline,
          gint             elapsed_msecs,
          TestState       *state)
{
  guint x, y;
  float period_progress = clutter_timeline_get_progress (timeline);
  float period_progress_sin = sinf (period_progress);
  float wave_shift = period_progress * WAVE_SPEED;
  float ripple_shift = period_progress * RIPPLE_SPEED;

  for (y = 0; y <= MESH_HEIGHT; y++)
    for (x = 0; x <= MESH_WIDTH; x++)
      {
        guint    vert_index = (MESH_WIDTH + 1) * y + x;
        float   *vert = &state->quad_mesh_verts[3 * vert_index];

        float    real_x = x * QUAD_WIDTH;
        float    real_y = y * QUAD_HEIGHT;

        float    wave_offset = (float)x / (MESH_WIDTH + 1);
        float    wave_angle =
                    (WAVE_PERIODS * 2 * G_PI * wave_offset) + wave_shift;
        float    wave_sin = sinf (wave_angle);

        float    a_sqr = (RIPPLE_CENTER_X - real_x) * (RIPPLE_CENTER_X - real_x);
        float    b_sqr = (RIPPLE_CENTER_Y - real_y) * (RIPPLE_CENTER_Y - real_y);
        float    ripple_offset = sqrtf (a_sqr + b_sqr) / RIPPLE_RADIUS;
        float    ripple_angle =
                    (RIPPLE_PERIODS * 2 * G_PI * ripple_offset) + ripple_shift;
        float    ripple_sin = sinf (ripple_angle);

        float    h, s, l;
        guint8  *color;

        vert[2] = (wave_sin * WAVE_DEPTH) + (ripple_sin * RIPPLE_DEPTH);

        /* Burn some CPU time picking a pretty color... */
        h = (HSL_OFFSET
             + wave_sin
             + ripple_sin
             + period_progress_sin) * HSL_SCALE;
        s = 0.5;
        l = 0.25 + (period_progress_sin + 1.0) / 4.0;
        color = &state->quad_mesh_colors[4 * vert_index];
        /* A bit of a sneaky cast, but it seems safe to assume the ClutterColor
         * typedef is set in stone... */
        clutter_color_from_hls ((ClutterColor *)color, h * 360.0, l, s);

        color[0] = (color[0] * color[3] + 128) / 255;
        color[1] = (color[1] * color[3] + 128) / 255;
        color[2] = (color[2] * color[3] + 128) / 255;
      }

  cogl_vertex_buffer_add (state->buffer,
                          "gl_Vertex",
                          3, /* n components */
                          COGL_ATTRIBUTE_TYPE_FLOAT,
                          FALSE, /* normalized */
                          0, /* stride */
                          state->quad_mesh_verts);
  cogl_vertex_buffer_add (state->buffer,
                          "gl_Color",
                          4, /* n components */
                          COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE,
                          FALSE, /* normalized */
                          0, /* stride */
                          state->quad_mesh_colors);

  cogl_vertex_buffer_submit (state->buffer);

  clutter_actor_set_rotation (state->dummy,
                              CLUTTER_Z_AXIS,
                              360 * period_progress,
                              (MESH_WIDTH * QUAD_WIDTH) / 2,
                              (MESH_HEIGHT * QUAD_HEIGHT) / 2,
                              0);
  clutter_actor_set_rotation (state->dummy,
                              CLUTTER_X_AXIS,
                              360 * period_progress,
                              (MESH_WIDTH * QUAD_WIDTH) / 2,
                              (MESH_HEIGHT * QUAD_HEIGHT) / 2,
                              0);
}

static void
on_paint (ClutterActor *actor, TestState *state)
{
  cogl_set_source_color4ub (0xff, 0x00, 0x00, 0xff);
  cogl_vertex_buffer_draw_elements (state->buffer,
                                    COGL_VERTICES_MODE_TRIANGLE_STRIP,
                                    state->indices,
                                    0, /* min index */
                                    (MESH_WIDTH + 1) *
                                    (MESH_HEIGHT + 1) - 1, /* max index */
                                    0, /* indices offset */
                                    state->n_static_indices);
}

static void
init_static_index_arrays (TestState *state)
{
  guint     n_indices;
  int       x, y;
  guint16  *i;
  guint     dir;

  /* - Each row takes (2 + 2 * MESH_WIDTH indices)
   *    - Thats 2 to start the triangle strip then 2 indices to add 2 triangles
   *      per mesh quad.
   * - We have MESH_HEIGHT rows
   * - It takes one extra index for linking between rows (MESH_HEIGHT - 1)
   * - A 2 x 3 mesh == 20 indices... */
  n_indices = (2 + 2 * MESH_WIDTH) * MESH_HEIGHT + (MESH_HEIGHT - 1);
  state->static_indices = g_malloc (sizeof (guint16) * n_indices);
  state->n_static_indices = n_indices;

#define MESH_INDEX(X, Y) (Y) * (MESH_WIDTH + 1) + (X)

  i = state->static_indices;

  /* NB: front facing == anti-clockwise winding */

  i[0] = MESH_INDEX (0, 0);
  i[1] = MESH_INDEX (0, 1);
  i += 2;

#define LEFT  0
#define RIGHT 1

  dir = RIGHT;

  for (y = 0; y < MESH_HEIGHT; y++)
    {
      for (x = 0; x < MESH_WIDTH; x++)
        {
          /* Add 2 triangles per mesh quad... */
          if (dir == RIGHT)
            {
              i[0] = MESH_INDEX (x + 1, y);
              i[1] = MESH_INDEX (x + 1, y + 1);
            }
          else
            {
              i[0] = MESH_INDEX (MESH_WIDTH - x - 1, y);
              i[1] = MESH_INDEX (MESH_WIDTH - x - 1, y + 1);
            }
          i += 2;
        }

      /* Link rows... */

      if (y == (MESH_HEIGHT - 1))
        break;

      if (dir == RIGHT)
        {
          i[0] = MESH_INDEX (MESH_WIDTH, y + 1);
          i[1] = MESH_INDEX (MESH_WIDTH, y + 1);
          i[2] = MESH_INDEX (MESH_WIDTH, y + 2);
        }
      else
        {
          i[0] = MESH_INDEX (0, y + 1);
          i[1] = MESH_INDEX (0, y + 1);
          i[2] = MESH_INDEX (0, y + 2);
        }
      i += 3;
      dir = !dir;
    }

#undef MESH_INDEX

  state->indices =
    cogl_vertex_buffer_indices_new (COGL_INDICES_TYPE_UNSIGNED_SHORT,
                                    state->static_indices,
                                    state->n_static_indices);
}

static float
gaussian (float x, float y)
{
  /* Bell width */
  float c = GAUSSIAN_RADIUS;

  /* Peak amplitude */
  float a = 1.0;
  /* float a = 1.0 / (c * sqrtf (2.0 * G_PI)); */

  /* Center offset */
  float b = 0.0;

  float dist;
  x = x - RIPPLE_CENTER_X;
  y = y - RIPPLE_CENTER_Y;
  dist = sqrtf (x*x + y*y);

  return a * exp ((- ((dist - b) * (dist - b))) / (2.0 * c * c));
}

static void
init_quad_mesh (TestState *state)
{
  int x, y;
  float *vert;
  guint8 *color;

  /* Note: we maintain the minimum number of vertices possible. This minimizes
   * the work required when we come to morph the geometry.
   *
   * We use static indices into our mesh so that we can treat the data like a
   * single triangle list and drawing can be done in one operation (Note: We
   * are using degenerate triangles at the edges to link to the next row)
   */
  state->quad_mesh_verts =
    g_malloc0 (sizeof (float) * 3 * (MESH_WIDTH + 1) * (MESH_HEIGHT + 1));

  state->quad_mesh_colors =
    g_malloc0 (sizeof (guint8) * 4 * (MESH_WIDTH + 1) * (MESH_HEIGHT + 1));

  vert = state->quad_mesh_verts;
  color = state->quad_mesh_colors;
  for (y = 0; y <= MESH_HEIGHT; y++)
    for (x = 0; x <= MESH_WIDTH; x++)
      {
        vert[0] = x * QUAD_WIDTH;
        vert[1] = y * QUAD_HEIGHT;
        vert += 3;

        color[3] = gaussian (x * QUAD_WIDTH,
                             y * QUAD_HEIGHT) * 255.0;
        color += 4;
      }

  state->buffer = cogl_vertex_buffer_new ((MESH_WIDTH + 1)*(MESH_HEIGHT + 1));
  cogl_vertex_buffer_add (state->buffer,
                          "gl_Vertex",
                          3, /* n components */
                          COGL_ATTRIBUTE_TYPE_FLOAT,
                          FALSE, /* normalized */
                          0, /* stride */
                          state->quad_mesh_verts);

  cogl_vertex_buffer_add (state->buffer,
                          "gl_Color",
                          4, /* n components */
                          COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE,
                          FALSE, /* normalized */
                          0, /* stride */
                          state->quad_mesh_colors);

  cogl_vertex_buffer_submit (state->buffer);

  init_static_index_arrays (state);
}

/* This creates an actor that has a specific size but that does not result
 * in any drawing so we can do our own drawing using Cogl... */
static ClutterActor *
create_dummy_actor (guint width, guint height)
{
  ClutterActor *group, *rect;
  ClutterColor clr = { 0xff, 0xff, 0xff, 0xff};

  group = clutter_group_new ();
  rect = clutter_rectangle_new_with_color (&clr);
  clutter_actor_set_size (rect, width, height);
  clutter_actor_hide (rect);
  clutter_container_add_actor (CLUTTER_CONTAINER (group), rect);
  return group;
}

static void
stop_and_quit (ClutterActor *actor,
               TestState    *state)
{
  clutter_timeline_stop (state->timeline);
  clutter_main_quit ();
}

G_MODULE_EXPORT int
test_cogl_vertex_buffer_main (int argc, char *argv[])
{
  TestState       state;
  ClutterActor   *stage;
  gfloat          stage_w, stage_h;
  gint            dummy_width, dummy_height;

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

  stage = clutter_stage_new ();

  clutter_stage_set_title (CLUTTER_STAGE (stage), "Cogl Vertex Buffers");
  clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_Black);
  g_signal_connect (stage, "destroy", G_CALLBACK (stop_and_quit), &state);
  clutter_actor_get_size (stage, &stage_w, &stage_h);

  dummy_width = MESH_WIDTH * QUAD_WIDTH;
  dummy_height = MESH_HEIGHT * QUAD_HEIGHT;
  state.dummy = create_dummy_actor (dummy_width, dummy_height);
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), state.dummy);
  clutter_actor_set_position (state.dummy,
                              (stage_w / 2.0) - (dummy_width / 2.0),
                              (stage_h / 2.0) - (dummy_height / 2.0));

  state.timeline = clutter_timeline_new (1000);
  clutter_timeline_set_loop (state.timeline, TRUE);

  state.frame_id = g_signal_connect (state.timeline,
                                     "new-frame",
                                     G_CALLBACK (frame_cb),
                                     &state);

  g_signal_connect (state.dummy, "paint", G_CALLBACK (on_paint), &state);

  init_quad_mesh (&state);

  clutter_actor_show_all (stage);

  clutter_timeline_start (state.timeline);

  clutter_main ();

  cogl_handle_unref (state.buffer);
  cogl_handle_unref (state.indices);

  return 0;
}

G_MODULE_EXPORT const char *
test_cogl_vertex_buffer_describe (void)
{
  return "Vertex buffers support in Cogl.";
}
