/*
 * Copyright 2010 VMware, Inc.
 * Copyright © 2012 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/**
 * Test GL_NV_primitive_restart and/or GL 3.1 primitive restart.
 * Note that these two extensions/features use different enum values
 * and Enable/Disable functions!
 *
 * Authors:
 *    Brian Paul
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 10;

	config.window_width = 400;
	config.window_height = 300;
	config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static const char *TestName = "primitive-restart";

typedef enum {
   DISABLE_VBO,
   VBO_VERTEX_ONLY,
   VBO_INDEX_ONLY,
   VBO_SEPARATE_VERTEX_AND_INDEX,
   VBO_COMBINED_VERTEX_AND_INDEX,
   ALL_TESTS,
} VBO_CFG;

static char* vbo_cfg_names[] = {
   "DISABLE_VBO",
   "VBO_VERTEX_ONLY",
   "VBO_INDEX_ONLY",
   "VBO_SEPARATE_VERTEX_AND_INDEX",
   "VBO_COMBINED_VERTEX_AND_INDEX",
   "all",
};

static VBO_CFG vbo_init_cfg = DISABLE_VBO;

static const GLfloat red[4] = {1.0, 0.0, 0.0, 1.0};
static const GLfloat green[4] = {0.0, 1.0, 0.0, 0.0};
static const GLfloat black[4] = {0.0, 0.0, 0.0, 0.0};

static bool Have_NV;
static bool Have_31;
static bool TestGL31;


static void
enable_restart(GLuint restart_index);
static void
disable_restart(void);

static bool
check_rendering(void)
{
   const GLfloat x0 = 0.0, x1 = piglit_width - 10.0, dx = 20.0;
   const GLint iy = piglit_height / 2;
   bool draw = true;
   GLfloat x;

   if (!piglit_probe_pixel_rgb(0, 0, black)) {
      return false;
   }

   for (x = x0 + 0.5 * dx; x < x1; x += dx) {
      bool pass;
      const int ix = (int) x;

      if (draw) {
         /* there should be triangle drawing here */
         pass = piglit_probe_pixel_rgb(ix, iy, green);
      }
      else {
         /* there should not be triangle drawing here */
         pass = piglit_probe_pixel_rgb(ix, iy, black);
      }

      /* debug */
      if (0) {
         glWindowPos2i(ix, iy);
         glDrawPixels(1, 1, GL_RGBA, GL_FLOAT, red);
      }

      if (!pass) {
         return false;
      }

      draw = !draw;
   }

   return true;
}


/**
 * Test glBegin(GL_TRIANGLE/LINE_STRIP), glPrimitiveRestartNV(), glEnd().
 */
static bool
test_begin_end(GLenum primMode)
{
   const GLfloat x0 = 0.0, x1 = piglit_width - 10.0, dx = 20.0;
   const GLfloat y0 = 0.5 * piglit_height - 10.0, y1 = y0 + 20.0, dy = 20.0;
   GLfloat x, y;
   GLint vert;
   bool pass;

   piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);

   glClear(GL_COLOR_BUFFER_BIT);

   glColor4fv(green);

   if (primMode == GL_TRIANGLE_STRIP) {
      /* Draw a tri-strip across the window, using restart to actually render
       * a series of quads/boxes.
       */
      glBegin(GL_TRIANGLE_STRIP);
      vert = 0;
      for (x = x0; x <= x1; x += dx) {
         for (y = y0; y <= y1; y += dy) {
            glVertex2f(x, y);
         }

         vert++;
         if (vert % 2 == 0)
            glPrimitiveRestartNV();
      }
      glEnd();
   }
   else {
      /* Draw a line strip across the window, using restart to actually render
       * a series of disconnected lines.
       */
      glLineWidth(5.0);
      glBegin(GL_LINE_STRIP);
      vert = 0;
      for (x = x0; x <= x1; x += dx) {
         y = 0.5 * piglit_height;

         glVertex2f(x, y);

         vert++;
         if (vert % 2 == 0)
            glPrimitiveRestartNV();
      }
      glEnd();
   }

   glFinish();

   pass = check_rendering();
   if (!pass) {
      fprintf(stderr, "%s: failure drawing with glBegin(%s) / glEnd()\n",
              TestName, piglit_get_prim_name(primMode));
   }

   piglit_present_results();

   return pass;
}

static void
write_vec2_value(GLfloat * verts, GLuint vidx, GLfloat x, GLfloat y)
{
   verts[(vidx * 2) + 0] = x;
   verts[(vidx * 2) + 1] = y;
}
/* This test should draw green rectangle if everything is ok.
 * But in case of bug which makes us unable to disable
 * the primitive restart option it should draw just one triangle.
 * Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=109451
 */
static bool
test_shared_ib_restart()
{
   bool pass = true;
   GLfloat verts[256 * 2];
   memset(&verts, 0, sizeof(verts));
   //left-bottom
   write_vec2_value(verts, 0, 0.0f, 0.0f);
   //right-bottom
   write_vec2_value(verts, 1, piglit_width, 0.0f);
   //left-top
   write_vec2_value(verts, 2, 0.0f, piglit_height);
   //right-top
   write_vec2_value(verts, 255, piglit_width, piglit_height);

   const GLubyte indices[] = { 0, 1, 2, 255, 0 };
   const GLfloat expected[3] = { 0.0f, 1.0f, 0.0f };
   GLuint vbo1, vbo2;

   piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);

   glClear(GL_COLOR_BUFFER_BIT);

   glColor4fv(green);

   glGenBuffers(1, &vbo1);
   glBindBuffer(GL_ARRAY_BUFFER, vbo1);
   glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
   glVertexPointer(2, GL_FLOAT, 0, (void *)0);

   glGenBuffers(1, &vbo2);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
   glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
   glEnableClientState(GL_VERTEX_ARRAY);

   //We should draw something with an enabled restart option to check that
   //it could be correctly disabled
   enable_restart(0xff);
   glDrawElements(GL_LINE_STRIP, ARRAY_SIZE(indices), GL_UNSIGNED_BYTE, (void*)0);
   disable_restart();

   //Draw full screen rectangle
   glDrawElements(GL_TRIANGLE_STRIP, ARRAY_SIZE(indices) - 1, GL_UNSIGNED_BYTE, (void*)0);

   glDisableClientState(GL_VERTEX_ARRAY);
   glBindBuffer(GL_ARRAY_BUFFER, 0);
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
   glDeleteBuffers(1, &vbo1);
   glDeleteBuffers(1, &vbo2);
   glFinish();
   pass = (piglit_probe_rect_rgb(0, 0, piglit_width, piglit_height, expected) != 0) && pass;
   piglit_present_results();
   return pass;
}


static void
enable_restart(GLuint restart_index)
{
   if (TestGL31) {
      glEnable(GL_PRIMITIVE_RESTART);
      glPrimitiveRestartIndex(restart_index);
   }
   else {
      glEnableClientState(GL_PRIMITIVE_RESTART_NV);
      glPrimitiveRestartIndexNV(restart_index);
   }
}


static void
disable_restart(void)
{
   if (TestGL31) {
      glDisable(GL_PRIMITIVE_RESTART);
   }
   else {
      glDisableClientState(GL_PRIMITIVE_RESTART_NV);
   }
}


static GLuint type_size(GLenum type)
{
   switch (type) {
   case GL_UNSIGNED_BYTE:
      return sizeof(GLubyte);
   case GL_UNSIGNED_SHORT:
      return sizeof(GLushort);
   case GL_UNSIGNED_INT:
      return sizeof(GLuint);
   default:
      assert(0);
      return 0;
   }
}


static GLuint type_array_size(GLenum type, GLuint length)
{
   return length * type_size(type);
}


static GLuint read_index_value(const GLvoid *indices, GLenum type, GLuint index)
{
   switch (type) {
   case GL_UNSIGNED_BYTE:
      return (GLuint)((GLubyte*)indices)[index];
   case GL_UNSIGNED_SHORT:
      return (GLuint)((GLushort*)indices)[index];
   case GL_UNSIGNED_INT:
      return ((GLuint*)indices)[index];
   default:
      assert(0);
      return 0;
   }
}


static void write_index_value(const GLvoid *indices, GLenum type, GLuint index, GLuint value)
{
   switch (type) {
   case GL_UNSIGNED_BYTE:
      ((GLubyte*)indices)[index] = (GLubyte) value;
      break;
   case GL_UNSIGNED_SHORT:
      ((GLushort*)indices)[index] = (GLushort) value;
      break;
   case GL_UNSIGNED_INT:
      ((GLuint*)indices)[index] = value;
      break;
   default:
      assert(0);
   }
}


static void do_ArrayElement(GLenum mode, GLsizei count,
                             GLenum type, const GLvoid *indices)
{
   GLuint index;

   glBegin(mode);
   for (index = 0; index < count; index++) {
      glArrayElement(read_index_value(indices, type, index));
   }
   glEnd();
}


/**
 * Test glDrawElements() with glPrimitiveRestartIndexNV().
 */
static bool
test_draw_by_index(VBO_CFG vbo_cfg, bool one_by_one, GLenum primMode, GLenum indexType)
{
#define NUM_VERTS 48
#define NUM_ELEMS (NUM_VERTS * 5 / 4)
   GLfloat verts[NUM_VERTS+2][2];
   GLubyte indices[sizeof(GLuint) * NUM_ELEMS];
   GLfloat x, dx;
   GLuint restart_index;
   GLuint num_elems;
   bool pass = true;
   GLuint vbo1, vbo2;
   bool create_vbo1 = false;
   bool create_vbo2 = false;
   uintptr_t index_offset = 0;
   uintptr_t vbo_data_size = sizeof(verts) + sizeof(indices);
   GLuint i, j;

   if ((vbo_cfg != DISABLE_VBO) && (vbo_cfg != VBO_INDEX_ONLY)) {
      create_vbo1 = true;
   }

   if ((vbo_cfg == VBO_INDEX_ONLY) || (vbo_cfg == VBO_SEPARATE_VERTEX_AND_INDEX)) {
      create_vbo2 = true;
   }

   if ((vbo_cfg == DISABLE_VBO) || (vbo_cfg == VBO_VERTEX_ONLY)) {
      index_offset = (uintptr_t) indices;
   } else if (vbo_cfg == VBO_COMBINED_VERTEX_AND_INDEX) {
      index_offset = sizeof(verts);
   } else {
      index_offset = 0;
   }

   switch (indexType) {
   case GL_UNSIGNED_BYTE:
      restart_index = 255;
      break;
   case GL_UNSIGNED_SHORT:
      restart_index = 1000;
      break;
   case GL_UNSIGNED_INT:
      restart_index = 1000 * 1000;
      break;
   default:
      assert(0);
      restart_index = 0;
   }

   x = 0.0;
   dx = 20.0;

   if (primMode == GL_TRIANGLE_STRIP) {
      const GLfloat y = 0.5 * piglit_height - 10.0, dy = 20.0;
      for (i = 0; i < NUM_VERTS / 2; i++) {
         verts[i*2+0][0] = x;
         verts[i*2+0][1] = y;
         verts[i*2+1][0] = x;
         verts[i*2+1][1] = y + dy;
         x += dx;
      }

      /* setup elements to draw series of squares w/ tri strip */
      for (i = j = 0; i < NUM_VERTS; i++) {
         write_index_value(indices, indexType, j, i);
         j++;
         if (i > 0 && i % 4 == 3) {
            write_index_value(indices, indexType, j, restart_index);
            j++;
         }
      }

      num_elems = j;
   }
   else {
      const GLfloat y = 0.5 * piglit_height;

      assert(primMode == GL_LINE_STRIP);

      glLineWidth(5.0);

      for (i = 0; i < NUM_VERTS; i++) {
         verts[i][0] = x;
         verts[i][1] = y;
         x += dx;
      }

      /* setup elements to draw series of disjoint lines w/ line strip */
      for (i = j = 0; i < NUM_VERTS / 2; i++) {
         write_index_value(indices, indexType, j, i);
         j++;
         if (i > 0 && i % 2 == 1) {
            write_index_value(indices, indexType, j, restart_index);
            j++;
         }
      }

      num_elems = j;
   }

   assert(num_elems <= NUM_ELEMS);

   /* debug */
   if (0) {
      for (i = 0; i < num_elems; i++)
         printf("%2d: %d\n", i, read_index_value(indices, indexType, i));
   }

   piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);

   glClear(GL_COLOR_BUFFER_BIT);

   glColor4fv(green);

   if (create_vbo1) {
      glGenBuffers(1, &vbo1);
      glBindBuffer(GL_ARRAY_BUFFER, vbo1);
      glBufferData(GL_ARRAY_BUFFER, vbo_data_size, NULL, GL_STATIC_DRAW);
   }

   if (create_vbo2) {
      glGenBuffers(1, &vbo2);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
      glBufferData(GL_ELEMENT_ARRAY_BUFFER, vbo_data_size, NULL, GL_STATIC_DRAW);
   } else {
      vbo2 = vbo1;
   }

   if (create_vbo1) {
      /* Load vertex data into VBO */
      glBindBuffer(GL_ARRAY_BUFFER, vbo1);
      glBufferSubData(GL_ARRAY_BUFFER,
                      0, sizeof(verts),
                      verts);
      glVertexPointer(2, GL_FLOAT, 0, (void *)0);
   } else {
      glVertexPointer(2, GL_FLOAT, 0, (void *)verts);
   }

   if ((vbo_cfg != DISABLE_VBO) && (vbo_cfg != VBO_VERTEX_ONLY)) {
      /* Load index data into VBO */
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo2);
      glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
                      index_offset, type_array_size(indexType, num_elems),
                      indices);
   }

   glEnableClientState(GL_VERTEX_ARRAY);
   pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

   enable_restart(restart_index);

   /* Draw */
   if (one_by_one) {
      do_ArrayElement(primMode, num_elems, indexType, indices);
   } else {
      glDrawElements(primMode, num_elems, indexType, (void*) index_offset);
   }

   disable_restart();

   glDisableClientState(GL_VERTEX_ARRAY);

   if (vbo_cfg != DISABLE_VBO) {
      glBindBuffer(GL_ARRAY_BUFFER, 0);
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
   }

   if (create_vbo1) {
      glDeleteBuffers(1, &vbo1);
   }

   if (create_vbo2) {
      glDeleteBuffers(1, &vbo2);
   }

   if (!check_rendering()) {
      fprintf(stderr, "%s: failure drawing with %s(%s, %s), %s\n",
              TestName,
              one_by_one ? "glArrayElement" : "glDrawElements",
              piglit_get_prim_name(primMode),
              piglit_get_gl_enum_name(indexType),
              vbo_cfg_names[vbo_cfg]);
      pass = false;
   }

   piglit_present_results();

   return pass;
#undef NUM_VERTS
}


/**
 * Test glDrawElements() with glPrimitiveRestartIndexNV().
 */
static bool
test_draw_elements(VBO_CFG vbo_cfg, GLenum primMode, GLenum indexType)
{
   return test_draw_by_index(vbo_cfg, false, primMode, indexType);
}


/**
 * Test glArrayElement() with glPrimitiveRestartIndexNV().
 */
static bool
test_array_element(VBO_CFG vbo_cfg, GLenum primMode, GLenum indexType)
{
   return test_draw_by_index(vbo_cfg, true, primMode, indexType);
}


bool
primitive_restart_test(VBO_CFG vbo_cfg)
{
   bool pass = true;

   if (Have_NV) {
      TestGL31 = false;
      pass = test_shared_ib_restart() && pass;
      pass = test_begin_end(GL_TRIANGLE_STRIP) && pass;
      pass = test_begin_end(GL_LINE_STRIP) && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_INT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_INT) && pass;
      pass = test_array_element(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_array_element(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_array_element(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_INT) && pass;
      pass = test_array_element(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_array_element(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_array_element(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_INT) && pass;
   }

   if (Have_31) {
      TestGL31 = true;
      pass = test_shared_ib_restart() && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_TRIANGLE_STRIP, GL_UNSIGNED_INT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_BYTE) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_SHORT) && pass;
      pass = test_draw_elements(vbo_cfg, GL_LINE_STRIP, GL_UNSIGNED_INT) && pass;
   }

   return pass;
}


enum piglit_result
piglit_display(void)
{
   if (vbo_init_cfg == ALL_TESTS) {
      VBO_CFG vbo_cfg;
      for (vbo_cfg = 0; vbo_cfg < ARRAY_SIZE(vbo_cfg_names); vbo_cfg++) {
         if ((vbo_cfg != ALL_TESTS) && !primitive_restart_test(vbo_cfg)) {
            return PIGLIT_FAIL;
         }
      }
      return PIGLIT_PASS;
   } else {
      return primitive_restart_test(vbo_init_cfg) ? PIGLIT_PASS : PIGLIT_FAIL;
   }
}


void
piglit_init(int argc, char **argv)
{
   Have_NV = piglit_is_extension_supported("GL_NV_primitive_restart");
   Have_31 = piglit_get_gl_version() >= 31;

   if (argc >= 2) {
      VBO_CFG vbo_cfg;
      for (vbo_cfg = 0; vbo_cfg < ARRAY_SIZE(vbo_cfg_names); vbo_cfg++) {
         if (strcmp(argv[1], vbo_cfg_names[vbo_cfg]) == 0) {
            vbo_init_cfg = vbo_cfg;
            break;
         }
      }
   }

   /* Debug */
   if (0) {
      printf("Have NV: %d\n", Have_NV);
      printf("Have 31: %d\n", Have_31);
   }

   if (!Have_NV && !Have_31) {
      piglit_report_result(PIGLIT_SKIP);
      exit(1);
   }

   glClearColor(0, 0, 0, 0);
}
