#include <clutter/clutter.h>

#include "tests/clutter-test-utils.h"

/****************************************************************
 Old style shader effect
 This uses clutter_shader_effect_set_source
 ****************************************************************/

static const gchar
old_shader_effect_source[] =
  "uniform vec3 override_color;\n"
  "\n"
  "void\n"
  "main ()\n"
  "{\n"
  "  cogl_color_out = vec4 (override_color, 1.0);\n"
  "}";

typedef struct _FooOldShaderEffectClass
{
  ClutterShaderEffectClass parent_class;
} FooOldShaderEffectClass;

typedef struct _FooOldShaderEffect
{
  ClutterShaderEffect parent;
} FooOldShaderEffect;

GType foo_old_shader_effect_get_type (void);

G_DEFINE_TYPE (FooOldShaderEffect,
               foo_old_shader_effect,
               CLUTTER_TYPE_SHADER_EFFECT);

static void
foo_old_shader_effect_paint_target (ClutterOffscreenEffect *effect,
                                    ClutterPaintNode       *node,
                                    ClutterPaintContext    *paint_context)
{
  clutter_shader_effect_set_shader_source (CLUTTER_SHADER_EFFECT (effect),
                                           old_shader_effect_source);
  clutter_shader_effect_set_uniform (CLUTTER_SHADER_EFFECT (effect),
                                     "override_color",
                                     G_TYPE_FLOAT, 3,
                                     1.0f, 0.0f, 0.0f);

  CLUTTER_OFFSCREEN_EFFECT_CLASS (foo_old_shader_effect_parent_class)->
    paint_target (effect, node, paint_context);
}

static void
foo_old_shader_effect_class_init (FooOldShaderEffectClass *klass)
{
  ClutterOffscreenEffectClass *offscreen_effect_class =
    CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);

  offscreen_effect_class->paint_target = foo_old_shader_effect_paint_target;
}

static void
foo_old_shader_effect_init (FooOldShaderEffect *self)
{
}

/****************************************************************
 New style shader effect
 This overrides get_static_shader_source()
 ****************************************************************/

static const gchar
new_shader_effect_source[] =
  "uniform vec3 override_color;\n"
  "\n"
  "void\n"
  "main ()\n"
  "{\n"
  "  cogl_color_out = (vec4 (override_color, 1.0) +\n"
  "                    vec4 (0.0, 0.0, 1.0, 0.0));\n"
  "}";

typedef struct _FooNewShaderEffectClass
{
  ClutterShaderEffectClass parent_class;
} FooNewShaderEffectClass;

typedef struct _FooNewShaderEffect
{
  ClutterShaderEffect parent;
} FooNewShaderEffect;

GType foo_new_shader_effect_get_type (void);

G_DEFINE_TYPE (FooNewShaderEffect,
               foo_new_shader_effect,
               CLUTTER_TYPE_SHADER_EFFECT);

static gchar *
foo_new_shader_effect_get_static_source (ClutterShaderEffect *effect)
{
  static gboolean already_called = FALSE;

  /* This should only be called once even though we have two actors
     using this effect */
  g_assert (!already_called);

  already_called = TRUE;

  return g_strdup (new_shader_effect_source);
}

static void
foo_new_shader_effect_paint_target (ClutterOffscreenEffect *effect,
                                    ClutterPaintNode       *node,
                                    ClutterPaintContext    *paint_context)
{
  clutter_shader_effect_set_uniform (CLUTTER_SHADER_EFFECT (effect),
                                     "override_color",
                                     G_TYPE_FLOAT, 3,
                                     0.0f, 1.0f, 0.0f);

  CLUTTER_OFFSCREEN_EFFECT_CLASS (foo_new_shader_effect_parent_class)->
    paint_target (effect, node, paint_context);
}

static void
foo_new_shader_effect_class_init (FooNewShaderEffectClass *klass)
{
  ClutterOffscreenEffectClass *offscreen_effect_class =
    CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
  ClutterShaderEffectClass *shader_effect_class =
    CLUTTER_SHADER_EFFECT_CLASS (klass);

  offscreen_effect_class->paint_target = foo_new_shader_effect_paint_target;

  shader_effect_class->get_static_shader_source =
    foo_new_shader_effect_get_static_source;
}

static void
foo_new_shader_effect_init (FooNewShaderEffect *self)
{
}

/****************************************************************
 Another new style shader effect
 This is the same but with a different shader. This is just
 sanity check that each class gets its own copy of the private
 data
 ****************************************************************/

static const gchar
another_new_shader_effect_source[] =
  "\n"
  "void\n"
  "main ()\n"
  "{\n"
  "  cogl_color_out = vec4 (1.0, 0.0, 1.0, 1.0);\n"
  "}";

typedef struct _FooAnotherNewShaderEffectClass
{
  ClutterShaderEffectClass parent_class;
} FooAnotherNewShaderEffectClass;

typedef struct _FooAnotherNewShaderEffect
{
  ClutterShaderEffect parent;
} FooAnotherNewShaderEffect;

GType foo_another_new_shader_effect_get_type (void);

G_DEFINE_TYPE (FooAnotherNewShaderEffect,
               foo_another_new_shader_effect,
               CLUTTER_TYPE_SHADER_EFFECT);

static gchar *
foo_another_new_shader_effect_get_static_source (ClutterShaderEffect *effect)
{
  return g_strdup (another_new_shader_effect_source);
}

static void
foo_another_new_shader_effect_class_init (FooAnotherNewShaderEffectClass *klass)
{
  ClutterShaderEffectClass *shader_effect_class =
    CLUTTER_SHADER_EFFECT_CLASS (klass);

  shader_effect_class->get_static_shader_source =
    foo_another_new_shader_effect_get_static_source;
}

static void
foo_another_new_shader_effect_init (FooAnotherNewShaderEffect *self)
{
}

/****************************************************************/

static ClutterActor *
make_actor (GType shader_type)
{
  ClutterActor *rect;
  const ClutterColor white = { 0xff, 0xff, 0xff, 0xff };

  rect = clutter_actor_new ();
  clutter_actor_set_background_color (rect, &white);
  clutter_actor_set_size (rect, 50, 50);

  clutter_actor_add_effect (rect, g_object_new (shader_type, NULL));

  return rect;
}

static guint32
get_pixel (CoglFramebuffer *fb,
           int              x,
           int              y)
{
  guint8 data[4];

  cogl_framebuffer_read_pixels (fb,
                                x, y, 1, 1,
                                COGL_PIXEL_FORMAT_RGBA_8888_PRE,
                                data);

  return (((guint32) data[0] << 16) |
          ((guint32) data[1] << 8) |
          data[2]);
}

static void
view_painted_cb (ClutterStage     *stage,
                 ClutterStageView *view,
                 MtkRegion        *redraw_clip,
                 ClutterFrame     *frame,
                 gpointer          data)
{
  CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
  gboolean *was_painted = data;

  /* old shader effect */
  g_assert_cmpint (get_pixel (fb, 0, 25), ==, 0xff0000);
  /* new shader effect */
  g_assert_cmpint (get_pixel (fb, 100, 25), ==, 0x00ffff);
  /* another new shader effect */
  g_assert_cmpint (get_pixel (fb, 200, 25), ==, 0xff00ff);
  /* new shader effect */
  g_assert_cmpint (get_pixel (fb, 300, 25), ==, 0x00ffff);

  *was_painted = TRUE;
}

static void
actor_shader_effect (void)
{
  ClutterActor *stage;
  ClutterActor *rect;
  gboolean was_painted;
  GList *actors = NULL;

  stage = clutter_test_get_stage ();

  rect = make_actor (foo_old_shader_effect_get_type ());
  clutter_actor_add_child (stage, rect);
  actors = g_list_prepend (actors, rect);

  rect = make_actor (foo_new_shader_effect_get_type ());
  clutter_actor_set_x (rect, 100);
  clutter_actor_add_child (stage, rect);
  actors = g_list_prepend (actors, rect);

  rect = make_actor (foo_another_new_shader_effect_get_type ());
  clutter_actor_set_x (rect, 200);
  clutter_actor_add_child (stage, rect);
  actors = g_list_prepend (actors, rect);

  rect = make_actor (foo_new_shader_effect_get_type ());
  clutter_actor_set_x (rect, 300);
  clutter_actor_add_child (stage, rect);
  actors = g_list_prepend (actors, rect);

  clutter_actor_show (stage);

  was_painted = FALSE;
  g_signal_connect_after (stage, "paint-view",
                          G_CALLBACK (view_painted_cb),
                          &was_painted);

  while (!was_painted)
    g_main_context_iteration (NULL, FALSE);

  g_list_free_full (actors, (GDestroyNotify) clutter_actor_destroy);
}

CLUTTER_TEST_SUITE (
  CLUTTER_TEST_UNIT ("/actor/shader-effect", actor_shader_effect)
)
