#include <cogl/cogl.h>

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

/* This test assumes the GL driver supports point sizes up to 16
   pixels. Cogl should probably have some way of querying the size so
   we start from that instead */
#define MAX_POINT_SIZE 16
#define MIN_POINT_SIZE 4
#define N_POINTS (MAX_POINT_SIZE - MIN_POINT_SIZE + 1)
/* The size of the area that we'll paint each point in */
#define POINT_BOX_SIZE (MAX_POINT_SIZE * 2)

typedef struct
{
  float x, y;
  float point_size;
} PointVertex;

static int
calc_coord_offset (int pos, int pos_index, int point_size)
{
  switch (pos_index)
    {
    case 0: return pos - point_size / 2 - 2;
    case 1: return pos - point_size / 2 + 2;
    case 2: return pos + point_size / 2 - 2;
    case 3: return pos + point_size / 2 + 2;
    }

  g_assert_not_reached ();
  return 0;
}

static void
verify_point_size (CoglFramebuffer *test_fb,
                   int x_pos,
                   int y_pos,
                   int point_size)
{
  int y, x;

  for (y = 0; y < 4; y++)
    for (x = 0; x < 4; x++)
      {
        gboolean in_point = x >= 1 && x <= 2 && y >= 1 && y <= 2;
        uint32_t expected_pixel = in_point ? 0x00ff00ff : 0xff0000ff;

        test_utils_check_pixel (test_fb,
                                calc_coord_offset (x_pos, x, point_size),
                                calc_coord_offset (y_pos, y, point_size),
                                expected_pixel);
      }
}

static CoglPrimitive *
create_primitive (const char *attribute_name)
{
  PointVertex vertices[N_POINTS];
  CoglAttributeBuffer *buffer;
  CoglAttribute *attributes[2];
  CoglPrimitive *prim;
  int i;

  for (i = 0; i < N_POINTS; i++)
    {
      vertices[i].x = i * POINT_BOX_SIZE + POINT_BOX_SIZE / 2;
      vertices[i].y = POINT_BOX_SIZE / 2;
      vertices[i].point_size = MAX_POINT_SIZE - i;
    }

  buffer = cogl_attribute_buffer_new (test_ctx,
                                      sizeof (vertices),
                                      vertices);

  attributes[0] = cogl_attribute_new (buffer,
                                      "cogl_position_in",
                                      sizeof (PointVertex),
                                      G_STRUCT_OFFSET (PointVertex, x),
                                      2, /* n_components */
                                      COGL_ATTRIBUTE_TYPE_FLOAT);
  attributes[1] = cogl_attribute_new (buffer,
                                      attribute_name,
                                      sizeof (PointVertex),
                                      G_STRUCT_OFFSET (PointVertex, point_size),
                                      1, /* n_components */
                                      COGL_ATTRIBUTE_TYPE_FLOAT);

  prim = cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_POINTS,
                                             N_POINTS,
                                             attributes,
                                             2 /* n_attributes */);

  for (i = 0; i < 2; i++)
    cogl_object_unref (attributes[i]);

  return prim;
}

static void
do_test (const char *attribute_name,
         void (* pipeline_setup_func) (CoglPipeline *pipeline))
{
  int fb_width = cogl_framebuffer_get_width (test_fb);
  int fb_height = cogl_framebuffer_get_height (test_fb);
  CoglPrimitive *primitive;
  CoglPipeline *pipeline;
  int i;

  cogl_framebuffer_orthographic (test_fb,
                                 0, 0, /* x_1, y_1 */
                                 fb_width, /* x_2 */
                                 fb_height /* y_2 */,
                                 -1, 100 /* near/far */);

  cogl_framebuffer_clear4f (test_fb,
                            COGL_BUFFER_BIT_COLOR,
                            1.0f, 0.0f, 0.0f, 1.0f);

  primitive = create_primitive (attribute_name);
  pipeline = cogl_pipeline_new (test_ctx);
  cogl_pipeline_set_color4ub (pipeline, 0x00, 0xff, 0x00, 0xff);
  cogl_pipeline_set_per_vertex_point_size (pipeline, TRUE, NULL);
  if (pipeline_setup_func)
    pipeline_setup_func (pipeline);
  cogl_primitive_draw (primitive, test_fb, pipeline);
  cogl_object_unref (pipeline);
  cogl_object_unref (primitive);

  /* Verify all of the points where drawn at the right size */
  for (i = 0; i < N_POINTS; i++)
    verify_point_size (test_fb,
                       i * POINT_BOX_SIZE + POINT_BOX_SIZE / 2, /* x */
                       POINT_BOX_SIZE / 2, /* y */
                       MAX_POINT_SIZE - i /* point size */);

  if (cogl_test_verbose ())
    g_print ("OK\n");
}

static void
test_point_size_attribute (void)
{
  do_test ("cogl_point_size_in", NULL);
}

static void
setup_snippet (CoglPipeline *pipeline)
{
  CoglSnippet *snippet;

  snippet = cogl_snippet_new (COGL_SNIPPET_HOOK_POINT_SIZE,
                              "attribute float "
                              "my_super_duper_point_size_attrib;\n",
                              NULL);
  cogl_snippet_set_replace (snippet,
                            "cogl_point_size_out = "
                            "my_super_duper_point_size_attrib;\n");
  cogl_pipeline_add_snippet (pipeline, snippet);
  cogl_object_unref (snippet);
}

static void
test_point_size_attribute_snippet (void)
{
  do_test ("my_super_duper_point_size_attrib", setup_snippet);
}

COGL_TEST_SUITE (
  g_test_add_func ("/point-size/attribute", test_point_size_attribute);
  g_test_add_func ("/point-size/attribute-snippet", test_point_size_attribute_snippet);
)
