/*
 * Copyright © 2009, 2013 VMware, Inc.
 * Copyright © 2015 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 that the correct provoking vertex is used when a tri/quad/polygon
 * is clipped for glShadeModel(GL_FLAT).
 *
 * This is based on the glean clipFlat test.
 *
 * Test with glDrawArrays and glBegin/End.  Test GL_CCW and GL_CW winding.
 * Back-face polygon culling is enabled so if the winding order of any
 * primitive is incorrect, nothing may be drawn.
 *
 * XXX We should also test with two-sided lighting.
 *
 * If GL_ARB/EXT_provoking_vertex is supported, that feature is tested as well.
 *
 * Author: Brian Paul
 */


#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN
	config.supports_gl_compat_version = 10;
	config.window_visual = PIGLIT_GL_VISUAL_RGB | PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;
PIGLIT_GL_TEST_CONFIG_END


/* Note: all correctly rendered tris/quad/polygons will be green.
 * Any other color indicates that the wrong vertex color was used.
 */

// GL_TRIANGLES: provoking vertex = last of tri
static const GLfloat TriVerts[6][5] =
	{
		// R  G  B     X   Y
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 0, 1, 0,    1,  1 }, // PV

		{ 0, 0, 1,    1,  1 },
		{ 1, 0, 0,   -1,  1 },
		{ 0, 1, 0,   -1, -1 } // PV
	};

// GL_TRIANGLES: first provoking vertex
static const GLfloat TriVertsFirstPV[6][5] =
	{
		{ 0, 1, 0,    1,  1 }, // PV
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },

		{ 0, 1, 0,   -1, -1 }, // PV
		{ 0, 0, 1,    1,  1 },
		{ 1, 0, 0,   -1,  1 }
	};


// GL_TRIANGLE_STRIP: provoking vertex = last of tri
static const GLfloat TriStripVerts[6][5] =
	{
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 0, 1, 0,   -1,  0 }, // PV
		{ 0, 1, 0,    1,  0 }, // PV
		{ 0, 1, 0,   -1,  1 }, // PV
		{ 0, 1, 0,    1,  1 }  // PV
	};

// GL_TRIANGLE_STRIP: first provoking vertex
static const GLfloat TriStripVertsFirstPV[6][5] =
	{
		{ 0, 1, 0,   -1, -1 }, // PV
		{ 0, 1, 0,    1, -1 }, // PV
		{ 0, 1, 0,   -1,  0 }, // PV
		{ 0, 1, 0,    1,  0 }, // PV
		{ 1, 0, 0,   -1,  1 },
		{ 0, 0, 1,    1,  1 }
	};


// GL_TRIANGLE_FAN: provoking vertex = last of tri
static const GLfloat TriFanVerts[4][5] =
	{
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 0, 1, 0,    1,  1 }, // PV
		{ 0, 1, 0,   -1,  1 }  // PV
	};

// GL_TRIANGLE_FAN: first provoking vertex
static const GLfloat TriFanVertsFirstPV[4][5] =
	{
		{ 0, 0, 1,    1, -1 },
		{ 0, 1, 0,    1,  1 }, // PV
		{ 0, 1, 0,   -1,  1 }, // PV
		{ 1, 0, 0,   -1, -1 }
	};


// GL_QUADS: provoking vertex = last of quad
static const GLfloat QuadVerts[4][5] =
	{
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 1, 1, 0,    1,  1 },
		{ 0, 1, 0,   -1,  1 }  // PV
	};

// GL_QUADS: first provoking vertex
static const GLfloat QuadVertsFirstPV[4][5] =
	{
		{ 0, 1, 0,   -1,  1 }, // PV
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 1, 1, 0,    1,  1 }
	};


// GL_QUAD_STRIP: provoking vertex = last of quad
static const GLfloat QuadStripVerts[6][5] =
	{
		{ 1, 0, 0,   -1, -1 },
		{ 0, 0, 1,    1, -1 },
		{ 1, 1, 0,   -1,  0 },
		{ 0, 1, 0,    1,  0 }, // PV
		{ 1, 1, 0,   -1,  1 },
		{ 0, 1, 0,    1,  1 }  // PV
	};

// GL_QUAD_STRIP: first provoking vertex
static const GLfloat QuadStripVertsFirstPV[6][5] =
	{
		{ 0, 1, 0,   -1, -1 }, // PV
		{ 1, 1, 0,    1, -1 },
		{ 0, 1, 0,   -1,  0 }, // PV
		{ 1, 0, 0,    1,  0 },
		{ 0, 0, 1,   -1,  1 },
		{ 1, 0, 0,    1,  1 }
	};


// GL_POLYGON: provoking vertex = first vertex
static const GLfloat PolygonVerts[4][5] =
	{
		{ 0, 1, 0,   -1, -1 }, // PV
		{ 1, 0, 0,    1, -1 },
		{ 0, 0, 1,    1,  1 },
		{ 1, 1, 0,   -1,  1 }
	};


enum draw_mode {
	BEGIN_END,
	DRAW_ARRAYS,
	DRAW_ELEMENTS,
	NUM_DRAW_MODES
};


static bool provoking_vertex_first;
static bool quads_follows_pv_convention;
static bool testing_first_pv;


void
piglit_init(int argc, char **argv)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1.25, 1.25, -1.25, 1.25, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glShadeModel(GL_FLAT);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	glFrontFace(GL_CW);
	glCullFace(GL_FRONT);
	glEnable(GL_CULL_FACE);

	if (piglit_is_extension_supported("GL_ARB_provoking_vertex")) {
		provoking_vertex_first = true;
	}
	else if (piglit_is_extension_supported("GL_EXT_provoking_vertex")) {
		provoking_vertex_first = true;
	}

	printf("Have GL_ARB/EXT_provoking_vertex: %s\n",
	       provoking_vertex_first ? "yes" : "no");

	if (provoking_vertex_first) {
		GLboolean k;
		glGetBooleanv(GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT, &k);
		quads_follows_pv_convention = k;

		printf("Quads follow provoking vertex convention: %s\n",
		       k ? "yes" : "no");
	}
}


// Draw with glDrawArrays()
static void
drawArrays(GLenum mode, const GLfloat *verts, GLuint count)
{
	glColorPointer(3, GL_FLOAT, 5 * sizeof(GLfloat), verts + 0);
	glVertexPointer(2, GL_FLOAT, 5 * sizeof(GLfloat), verts + 3);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);

	glDrawArrays(mode, 0, count);

	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
}


// Draw with glDrawElements()
static void
drawElements(GLenum mode, const GLfloat *verts, GLuint count)
{
	static const GLuint elements[6] = { 0, 1, 2, 3, 4, 5 };
	glColorPointer(3, GL_FLOAT, 5 * sizeof(GLfloat), verts + 0);
	glVertexPointer(2, GL_FLOAT, 5 * sizeof(GLfloat), verts + 3);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);

	assert(count <= ARRAY_SIZE(elements));

	glDrawElements(mode, count, GL_UNSIGNED_INT, elements);

	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
}


// Draw with glBegin/End()
static void
drawBeginEnd(GLenum mode, const GLfloat *verts, GLuint count)
{
	GLuint i;

	glBegin(mode);
	for (i = 0; i < count; i++) {
		glColor3fv(verts + i * 5);
		glVertex2fv(verts + i * 5 + 3);
	}
	glEnd();
}


// Read pixels and check pixels.  All pixels should be green or black.
// Any other color indicates a failure.
static bool
checkResult(GLfloat badColor[3])
{
	GLubyte *image;
	GLuint i, j;
	bool anyGreen = false;

	image = malloc(piglit_width * piglit_height * 3);

	badColor[0] = badColor[1] = badColor[2] = 0.0f;

	glReadPixels(0, 0, piglit_width, piglit_height,
		     GL_RGB, GL_UNSIGNED_BYTE, image);

	if (!piglit_automatic)
		piglit_present_results();

	for (i = 0; i < piglit_height; i++) {
		for (j = 0; j < piglit_width; j++) {
			GLuint k = (i * piglit_width + j) * 3;

			if (image[k + 0] == 0 &&
			    image[k + 1] == 0 &&
			    image[k + 2] == 0) {
				// black - OK
			}
			else if (image[k + 0] == 0 &&
				 image[k + 1] >= 254 &&
				 image[k + 2] == 0) {
				// green - OK
				anyGreen = true;
			}
			else {
				// any other color = failure
				badColor[0] = image[k + 0] / 255.0;
				badColor[1] = image[k + 1] / 255.0;
				badColor[2] = image[k + 2] / 255.0;
				free(image);
				return false;
			}
		}
	}

	free(image);

	return anyGreen;
}


char *
calcQuadrant(GLfloat x, GLfloat y)
{
	const char *strx, *stry;
	char *ret;
	int _x, _y;
	_x = (int) x;
	_y = (int) y;

	switch (_x) {
	case -1:
		strx = "left";
		break;
	case 0:
		strx = "center";
		break;
	case 1:
		strx = "right";
		break;
	default:
		assert(0);
	}

	switch (_y) {
	case 1:
		stry = "top";
		break;
	case 0:
		stry = "middle";
		break;
	case -1:
		stry = "bottom";
		break;
	default:
		assert(0);
	}

	(void)!asprintf(&ret, "%s %s", strx, stry);
	return ret;
}


static void
reportSubtest(GLenum mode, int drawMode,
              const GLenum order, const GLenum poly_mode,
              const GLfloat badColor[3], GLfloat x, GLfloat y,
              bool pass)
{
	const char *m, *d, *f, *p;
	char *q;

	m = piglit_get_prim_name(mode);

	switch (drawMode) {
	case BEGIN_END:
		d = "glBegin/End";
		break;
	case DRAW_ARRAYS:
		d = "glDrawArrays";
		break;
	case DRAW_ELEMENTS:
		d = "glDrawElements";
		break;
	default:
		assert(0);
		d = "???";
	}

	f = piglit_get_gl_enum_name(order);

	p = piglit_get_gl_enum_name(poly_mode);

	q = calcQuadrant(x, y);

	if (!pass) {
		printf("clipflat: Failure for %s(%s), glFrontFace(%s), "
		       "glPolygonMode(%s), quadrant: %s\n",
		       d, m, f, p, q);

		if (testing_first_pv) {
			printf("\tGL_EXT_provoking_vertex test: "
			       "GL_FIRST_VERTEX_CONVENTION_EXT mode\n");
		}

		printf("Expected color (0, 1, 0) but found (%g, %g, %g)\n",
		       badColor[0], badColor[1], badColor[2]);
		printf("\n");
	}

	piglit_report_subtest_result(pass ? PIGLIT_PASS : PIGLIT_FAIL,
				     "%s(%s), glFrontFace(%s), glPolygonMode(%s), "
				     "quadrant: %s PV: %s",
				     d, m, f, p, q, testing_first_pv ? "FIRST" : "LAST");

	free(q);
}


// Test a particular primitive mode for one drawing mode, filled/unfilled
// state and CW/CCW winding.
static bool
testPrimCombo(GLenum mode, const GLfloat *verts, GLuint count,
			  bool fill, enum draw_mode drawMode, GLuint facing)
{
	GLfloat x, y;
	bool pass = true;
	const GLenum poly_mode = fill ? GL_LINE : GL_FILL;
	const GLenum order = facing ? GL_CW : GL_CCW;
	const GLenum cull_face = facing ? GL_FRONT : GL_BACK;

	glPolygonMode(GL_FRONT_AND_BACK, poly_mode);

	glFrontFace(order);
	glCullFace(cull_face);

	// Position the geometry at 9 different locations to test
	// clipping against the left, right, bottom and top edges of
	// the window.
	// Only the center location will be unclipped.
	for (y = -1.0; y <= 1.0; y += 1.0) {
		for (x = -1.0; x <= 1.0; x += 1.0) {
			bool quad_pass;
			GLfloat badColor[3];

			glPushMatrix();
			glTranslatef(x, y, 0.0);

			glClear(GL_COLOR_BUFFER_BIT);

			switch (drawMode) {
			case BEGIN_END:
				drawBeginEnd(mode, verts, count);
				break;
			case DRAW_ARRAYS:
				drawArrays(mode, verts, count);
				break;
			case DRAW_ELEMENTS:
				drawElements(mode, verts, count);
				break;
			default:
				assert(0);
			}

			glPopMatrix();

			quad_pass = checkResult(badColor);
			pass = pass && quad_pass;
			reportSubtest(mode, drawMode, order, poly_mode,
						  badColor, x, y, quad_pass);
		}
	}

	return pass;
}


// Test a particular primitive mode for all drawing modes, filled/unfilled
// and CW/CCW winding.
static bool
testPrim(GLenum mode, const GLfloat *verts, GLuint count)
{
	GLuint facing, fill;
	int drawMode;
	bool pass = true;

	// Loop over polygon mode: filled vs. outline
	for (fill = 0; fill < 2; fill++) {

		// Loop over drawing mode: glBegin/End vs glDrawArrays vs glDrawElements
		for (drawMode = 0; drawMode < NUM_DRAW_MODES; drawMode++) {

			// Loop over CW vs. CCW winding (should make no difference)
			for (facing = 0; facing < 2; facing++) {
				pass = testPrimCombo(mode, verts, count,
									 fill, drawMode, facing) && pass;
			}
		}
	}
	return pass;
}


enum piglit_result
piglit_display(void)
{
	bool pass = true;

	testing_first_pv = false;

	pass = testPrim(GL_TRIANGLES,
			(GLfloat *) TriVerts,
			ARRAY_SIZE(TriVerts)) && pass;

	pass = testPrim(GL_TRIANGLE_STRIP,
			(GLfloat *) TriStripVerts,
			ARRAY_SIZE(TriStripVerts)) && pass;

	pass = testPrim(GL_TRIANGLE_FAN,
			(GLfloat *) TriFanVerts,
			ARRAY_SIZE(TriFanVerts)) && pass;

	pass = testPrim(GL_QUADS,
			(GLfloat *) QuadVerts,
			ARRAY_SIZE(QuadVerts)) && pass;

	pass = testPrim(GL_QUAD_STRIP,
			(GLfloat *) QuadStripVerts,
			ARRAY_SIZE(QuadStripVerts)) && pass;

	pass = testPrim(GL_POLYGON,
			(GLfloat *) PolygonVerts,
			ARRAY_SIZE(PolygonVerts)) && pass;

	if (provoking_vertex_first) {
		glProvokingVertex(GL_FIRST_VERTEX_CONVENTION_EXT);
		testing_first_pv = true;

		pass = testPrim(GL_TRIANGLES,
				(GLfloat *) TriVertsFirstPV,
				ARRAY_SIZE(TriVertsFirstPV)) && pass;

		pass = testPrim(GL_TRIANGLE_STRIP,
				(GLfloat *) TriStripVertsFirstPV,
				ARRAY_SIZE(TriStripVertsFirstPV)) && pass;

		pass = testPrim(GL_TRIANGLE_FAN,
				(GLfloat *) TriFanVertsFirstPV,
				ARRAY_SIZE(TriFanVertsFirstPV)) && pass;

		if (quads_follows_pv_convention)
			pass = testPrim(GL_QUADS,
					(GLfloat *) QuadVertsFirstPV,
					ARRAY_SIZE(QuadVertsFirstPV)) && pass;
		else
			pass = testPrim(GL_QUADS,
					(GLfloat *) QuadVerts,
					ARRAY_SIZE(QuadVerts)) && pass;

		if (quads_follows_pv_convention)
			pass = testPrim(GL_QUAD_STRIP,
					(GLfloat *) QuadStripVertsFirstPV,
					ARRAY_SIZE(QuadStripVertsFirstPV)) && pass;
		else
			pass = testPrim(GL_QUAD_STRIP,
					(GLfloat *) QuadStripVerts,
					ARRAY_SIZE(QuadStripVerts)) && pass;

		pass  = testPrim(GL_POLYGON,
				 (GLfloat *) PolygonVerts,
				 ARRAY_SIZE(PolygonVerts)) && pass;
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}
