/*
 * Copyright (C) 1999  Allen Akin   All Rights Reserved.
 *
 * 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 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 ALLEN AKIN 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.
 *
 */

/**
 * @file tex-env.c:  Test the basic texture env modes
 * Author: Brian Paul (brianp@valinux.com)  April 2001
 *
 * Test procedure:
 *   Setup a texture with 81 columns of unique RGBA colors, 3 texels each.
 *   Draw a 81 uniquely-colored flat-shaded quads as wide horizontal bands,
 *   with the above texture.  This makes a matrix of 81*81 colored squares
 *   for which we test that the current texture environment mode and texture
 *   format produced the correct color.
 *   Finally, we blend over a gray background in order to verify that the
 *   post-texture alpha value is correct.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 13;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA;
	config.khr_no_error_support = PIGLIT_NO_ERRORS;
	config.window_width = 256;
	config.window_height = 256;

PIGLIT_GL_TEST_CONFIG_END

#define BLEND_WITH_BACKGROUND 1

#define COLORS (3 * 3 * 3 * 3)

static float colors[COLORS][4];

static float bg_color[4] = {0.5, 0.5, 0.5, 0.5};

static const GLenum format_enums[] = {
	GL_ALPHA,
	GL_LUMINANCE,
	GL_LUMINANCE_ALPHA,
	GL_INTENSITY,
	GL_RGB,
	GL_RGBA
};

static const GLenum env_mode_enums[] = {
	GL_REPLACE,
	GL_MODULATE,
	GL_DECAL,
	GL_BLEND,
	GL_ADD
};

/* Compute expected texenv result given the texture env mode, the texture
 * base format, texture color, fragment color, and texture env color.
 * This also blends the result with the background color if that option
 * is enabled (see above). */
static void
compute_expected_color(GLenum env_mode, GLenum tex_format,
		       const float tex_color[4], const float frag_color[4],
		       const float env_color[4], float result[4])
{
	switch (env_mode) {
	case GL_REPLACE:
		switch (tex_format) {
		case GL_ALPHA:
			result[0] = frag_color[0];
			result[1] = frag_color[1];
			result[2] = frag_color[2];
			result[3] = tex_color[3]; /* alpha */
			break;
		case GL_LUMINANCE:
			result[0] = tex_color[0]; /* lum */
			result[1] = tex_color[0];
			result[2] = tex_color[0];
			result[3] = frag_color[3];
			break;
		case GL_LUMINANCE_ALPHA:
			result[0] = tex_color[0]; /* lum */
			result[1] = tex_color[0];
			result[2] = tex_color[0];
			result[3] = tex_color[3]; /* alpha */
			break;
		case GL_INTENSITY:
			result[0] = tex_color[0]; /* intensity */
			result[1] = tex_color[0];
			result[2] = tex_color[0];
			result[3] = tex_color[0];
			break;
		case GL_RGB:
			result[0] = tex_color[0]; /* r */
			result[1] = tex_color[1]; /* g */
			result[2] = tex_color[2]; /* b */
			result[3] = frag_color[3];
			break;
		case GL_RGBA:
			result[0] = tex_color[0]; /* r */
			result[1] = tex_color[1]; /* g */
			result[2] = tex_color[2]; /* b */
			result[3] = tex_color[3]; /* a */
			break;
		default:
			abort(); /* implementation error */
		}
		break;
	case GL_MODULATE:
		switch (tex_format) {
		case GL_ALPHA:
			result[0] = frag_color[0];
			result[1] = frag_color[1];
			result[2] = frag_color[2];
			result[3] = frag_color[3] * tex_color[3];
			break;
		case GL_LUMINANCE:
			result[0] = frag_color[0] * tex_color[0];
			result[1] = frag_color[1] * tex_color[0];
			result[2] = frag_color[2] * tex_color[0];
			result[3] = frag_color[3];
			break;
		case GL_LUMINANCE_ALPHA:
			result[0] = frag_color[0] * tex_color[0];
			result[1] = frag_color[1] * tex_color[0];
			result[2] = frag_color[2] * tex_color[0];
			result[3] = frag_color[3] * tex_color[3];
			break;
		case GL_INTENSITY:
			result[0] = frag_color[0] * tex_color[0];
			result[1] = frag_color[1] * tex_color[0];
			result[2] = frag_color[2] * tex_color[0];
			result[3] = frag_color[3] * tex_color[0];
			break;
		case GL_RGB:
			result[0] = frag_color[0] * tex_color[0];
			result[1] = frag_color[1] * tex_color[1];
			result[2] = frag_color[2] * tex_color[2];
			result[3] = frag_color[3];
			break;
		case GL_RGBA:
			result[0] = frag_color[0] * tex_color[0];
			result[1] = frag_color[1] * tex_color[1];
			result[2] = frag_color[2] * tex_color[2];
			result[3] = frag_color[3] * tex_color[3];
			break;
		default:
			abort(); /* implementation error */
		}
		break;
	case GL_DECAL:
		switch (tex_format) {
		case GL_ALPHA:
			result[0] = 0; /* undefined */
			result[1] = 0;
			result[2] = 0;
			result[3] = 0;
			break;
		case GL_LUMINANCE:
			result[0] = 0; /* undefined */
			result[1] = 0;
			result[2] = 0;
			result[3] = 0;
			break;
		case GL_LUMINANCE_ALPHA:
			result[0] = 0; /* undefined */
			result[1] = 0;
			result[2] = 0;
			result[3] = 0;
			break;
		case GL_INTENSITY:
			result[0] = 0; /* undefined */
			result[1] = 0;
			result[2] = 0;
			result[3] = 0;
			break;
		case GL_RGB:
			result[0] = tex_color[0];
			result[1] = tex_color[1];
			result[2] = tex_color[2];
			result[3] = frag_color[3];
			break;
		case GL_RGBA: {
			const float a = tex_color[3];
			const float oma = 1.0 - a;
			result[0] = frag_color[0] * oma + tex_color[0] * a;
			result[1] = frag_color[1] * oma + tex_color[1] * a;
			result[2] = frag_color[2] * oma + tex_color[2] * a;
			result[3] = frag_color[3];
		} break;
		default:
			abort(); /* implementation error */
		}
		break;
	case GL_BLEND:
		switch (tex_format) {
		case GL_ALPHA:
			result[0] = frag_color[0];
			result[1] = frag_color[1];
			result[2] = frag_color[2];
			result[3] = frag_color[3] * tex_color[3];
			break;
		case GL_LUMINANCE: {
			const float l = tex_color[0];
			const float oml = 1.0 - l;
			result[0] = frag_color[0] * oml + env_color[0] * l;
			result[1] = frag_color[1] * oml + env_color[1] * l;
			result[2] = frag_color[2] * oml + env_color[2] * l;
			result[3] = frag_color[3];
		} break;
		case GL_LUMINANCE_ALPHA: {
			const float l = tex_color[0];
			const float oml = 1.0 - l;
			result[0] = frag_color[0] * oml + env_color[0] * l;
			result[1] = frag_color[1] * oml + env_color[1] * l;
			result[2] = frag_color[2] * oml + env_color[2] * l;
			result[3] = frag_color[3] * tex_color[3];
		} break;
		case GL_INTENSITY: {
			const float i = tex_color[0];
			const float omi = 1.0 - i;
			result[0] = frag_color[0] * omi + env_color[0] * i;
			result[1] = frag_color[1] * omi + env_color[1] * i;
			result[2] = frag_color[2] * omi + env_color[2] * i;
			result[3] = frag_color[3] * omi + env_color[3] * i;
		} break;
		case GL_RGB: {
			const float r = tex_color[0];
			const float omr = 1.0 - r;
			const float g = tex_color[1];
			const float omg = 1.0 - g;
			const float b = tex_color[2];
			const float omb = 1.0 - b;
			result[0] = frag_color[0] * omr + env_color[0] * r;
			result[1] = frag_color[1] * omg + env_color[1] * g;
			result[2] = frag_color[2] * omb + env_color[2] * b;
			result[3] = frag_color[3];
		} break;
		case GL_RGBA: {
			const float r = tex_color[0];
			const float omr = 1.0 - r;
			const float g = tex_color[1];
			const float omg = 1.0 - g;
			const float b = tex_color[2];
			const float omb = 1.0 - b;
			result[0] = frag_color[0] * omr + env_color[0] * r;
			result[1] = frag_color[1] * omg + env_color[1] * g;
			result[2] = frag_color[2] * omb + env_color[2] * b;
			result[3] = frag_color[3] * tex_color[3];
		} break;
		default:
			abort(); /* implementation error */
		}
		break;
	case GL_ADD:
		switch (tex_format) {
		case GL_ALPHA:
			result[0] = frag_color[0];
			result[1] = frag_color[1];
			result[2] = frag_color[2];
			result[3] = frag_color[3] * tex_color[3];
			break;
		case GL_LUMINANCE:
			result[0] = frag_color[0] + tex_color[0];
			result[1] = frag_color[1] + tex_color[0];
			result[2] = frag_color[2] + tex_color[0];
			result[3] = frag_color[3];
			break;
		case GL_LUMINANCE_ALPHA:
			result[0] = frag_color[0] + tex_color[0];
			result[1] = frag_color[1] + tex_color[0];
			result[2] = frag_color[2] + tex_color[0];
			result[3] = frag_color[3] * tex_color[3];
			break;
		case GL_INTENSITY:
			result[0] = frag_color[0] + tex_color[0];
			result[1] = frag_color[1] + tex_color[0];
			result[2] = frag_color[2] + tex_color[0];
			result[3] = frag_color[3] + tex_color[0];
			break;
		case GL_RGB:
			result[0] = frag_color[0] + tex_color[0];
			result[1] = frag_color[1] + tex_color[1];
			result[2] = frag_color[2] + tex_color[2];
			result[3] = frag_color[3];
			break;
		case GL_RGBA:
			result[0] = frag_color[0] + tex_color[0];
			result[1] = frag_color[1] + tex_color[1];
			result[2] = frag_color[2] + tex_color[2];
			result[3] = frag_color[3] * tex_color[3];
			break;
		default:
			abort(); /* implementation error */
		}
		/* clamping */
		if (result[0] > 1.0)
			result[0] = 1.0;
		if (result[1] > 1.0)
			result[1] = 1.0;
		if (result[2] > 1.0)
			result[2] = 1.0;
		if (result[3] > 1.0)
			result[3] = 1.0;
		break;
	default:
		abort(); /* implementation error */
	}

#if BLEND_WITH_BACKGROUND
	/* now blend result over a gray background */
	const float alpha = result[3];
	const float om_alpha = 1.0 - alpha;
	result[0] = result[0] * alpha + bg_color[0] * om_alpha;
	result[1] = result[1] * alpha + bg_color[1] * om_alpha;
	result[2] = result[2] * alpha + bg_color[2] * om_alpha;
	result[3] = result[3] * alpha + bg_color[3] * om_alpha;
#endif
}

/* Make a texture in which the colors vary along the length
 * according to the colors[] array.  For example, we use
 * 243 columns of the texture to store 81 colors, 3 texels each. */
static void
make_tex_image(GLenum base_format, int num_colors, const float colors[][4])
{
#define WIDTH 256
#define HEIGHT 4
	static float img[WIDTH * HEIGHT][4];

	assert(num_colors == 81);

	for (int i = 0; i < HEIGHT; i++) {
		for (int j = 0; j < WIDTH; j++) {
			int c = j / 3;
			if (c >= num_colors) {
				img[i * WIDTH + j][0] = 0.0;
				img[i * WIDTH + j][1] = 0.0;
				img[i * WIDTH + j][2] = 0.0;
				img[i * WIDTH + j][3] = 0.0;
			} else {
				img[i * WIDTH + j][0] = colors[c][0];
				img[i * WIDTH + j][1] = colors[c][1];
				img[i * WIDTH + j][2] = colors[c][2];
				img[i * WIDTH + j][3] = colors[c][3];
			}
		}
	}

	glTexImage2D(GL_TEXTURE_2D, 0, base_format, WIDTH, HEIGHT, 0, GL_RGBA,
		     GL_FLOAT, (void *)img);
}

/* Do num_colors * num_colors tests in one batch.
 * Setup a texture in which the colors vary by column.
 * Draw a quadstrip in which we draw horizontal bands of colors.
 * Drawing the textured quadstrips will fill the window with
 * num_colors * num_colors test squares.
 * Verify that they're all correct.
 * Return:  true = pass, false = fail */
static bool
matrix_test(GLenum env_mode, GLenum tex_format, int num_colors,
	    const float colors[][4], const float env_color[4])
{
	const char *format_name = piglit_get_gl_enum_name(tex_format);
	const char *env_name = piglit_get_gl_enum_name(env_mode);

	if (env_mode == GL_DECAL &&
	    (tex_format != GL_RGB && tex_format != GL_RGBA)) {
		/* undefined mode */
		return true;
	}

	glClear(GL_COLOR_BUFFER_BIT);

	/* The texture colors are the columns */
	make_tex_image(tex_format, num_colors, colors);

	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env_mode);
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, env_color);

	/* The fragment colors are the rows */
	float W = num_colors * 3;
	float S = (float)(num_colors * 3) / (float)256;
	glBegin(GL_QUAD_STRIP);
	glTexCoord2f(0, 0);  glVertex2f(0, 0);
	glTexCoord2f(S, 0);  glVertex2f(W, 0);
	for (int i = 0; i < num_colors; i++) {
		glColor4fv(colors[i]);
		float y = i * 3 + 3;
		float t = y / (num_colors * 3);
		glTexCoord2f(0, t);  glVertex2f(0, y);
		glTexCoord2f(S, t);  glVertex2f(W, y);
	}
	glEnd();

	/* Check results */
	float *pixels = piglit_read_pixels_float(0, 0, piglit_width, piglit_height, GL_RGBA, NULL);
	for (int row = 0; row < num_colors; row++) {
		for (int col = 0; col < num_colors; col++) {

			/* compute expected */
			float expected[4];
			compute_expected_color(env_mode, tex_format,
					       colors[col], colors[row],
					       env_color, expected);

			/* fetch actual pixel */
			int x = col * 3 + 1;
			int y = row * 3 + 1;

			/* compare */
			if (!piglit_compare_pixels_float(
				&pixels[4 * y * piglit_width + 4 * x],
				expected,
				piglit_tolerance, 4)) {
				/* Report the error */
				printf("GL_TEXTURE_ENV_MODE = %s\n"
				       "Texture Format = %s\n"
				       "Fragment Color = (%f, %f, %f, %f)\n"
				       "Texture Color = (%f, %f, %f, %f)\n"
				       "Tex Env Color = (%f, %f, %f, %f)\n",
				       env_name, format_name,
				       colors[row][0], colors[row][1],
				       colors[row][2], colors[row][3],
				       colors[col][0], colors[col][1],
				       colors[col][2], colors[col][3],
				       env_color[0], env_color[1],
				       env_color[2], env_color[3]);
#if BLEND_WITH_BACKGROUND
				printf("Blend over = (%f, %f, %f, %f)\n",
				       bg_color[0], bg_color[1],
				       bg_color[2], bg_color[3]);
#endif
				free(pixels);
				return false;
			}
		}
	}

	free(pixels);

	return true;
}

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

	for (int fmt = 0; fmt < 6; fmt++) {
		const GLenum format = format_enums[fmt];
		for (int mode = 0; mode < 5; mode++) {
			const GLenum env_mode = env_mode_enums[mode];
			if (env_mode == GL_BLEND && format != GL_ALPHA) {
				/* also vary texenv color, every 5th is OK. */
				for (int i = 0; i < COLORS; i += 5) {
					const float *env_color = colors[i];
					if (!matrix_test(
						    env_mode, format, COLORS,
						    (const float(*)[4])colors,
						    env_color)) {
						pass = false;
						break;
					}
				}
			} else {
				/* texenv color not significant */
				if (!matrix_test(env_mode, format, COLORS,
						 (const float(*)[4])colors,
						 colors[0])) {
					pass = false;
				}
			}
		}
	}

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

void
piglit_init(int argc, char **argv)
{
	GLuint texture;

	/* colors[] is an array of all possible RGBA colors with component
	 * values of 0, 0.5, and 1.0 */
	for (int i = 0; i < COLORS; i++) {
		int r = i % 3;
		int g = (i / 3) % 3;
		int b = (i / 9) % 3;
		int a = (i / 27) % 3;
		colors[i][0] = (float)r / 2.0;
		colors[i][1] = (float)g / 2.0;
		colors[i][2] = (float)b / 2.0;
		colors[i][3] = (float)a / 2.0;
	}

	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glEnable(GL_TEXTURE_2D);

#if BLEND_WITH_BACKGROUND
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
#endif

	glClearColor(bg_color[0], bg_color[1], bg_color[2], bg_color[3]);
	glShadeModel(GL_FLAT);

	piglit_ortho_projection(piglit_width, piglit_height, GL_FALSE);
}
