/*
 * Copyright © 2019 Advanced Micro Devices, Inc.
 *
 * 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.
 */

#include "piglit-util-gl.h"

PIGLIT_GL_TEST_CONFIG_BEGIN

	config.supports_gl_compat_version = 30;
	config.window_visual = PIGLIT_GL_VISUAL_RGBA |
		PIGLIT_GL_VISUAL_DOUBLE;
	config.khr_no_error_support = PIGLIT_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static enum piglit_result
test_FramebufferDrawBufferEXT(void* d)
{
	int max_color_attachments, max_draw_buffers;
	GLuint fbs[3];
	int i, j, got;
	GLenum* attachments;

	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);
	glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);

	attachments = (GLenum*) malloc((max_color_attachments + 1) * sizeof(GLenum));
	for (i = 0; i < max_color_attachments; i++) {
		attachments[i] = GL_COLOR_ATTACHMENT0 + i;
	}
	attachments[max_color_attachments] = GL_NONE;
	max_color_attachments += 1;

	glGenFramebuffers(ARRAY_SIZE(fbs), fbs);

	glBindFramebuffer(GL_FRAMEBUFFER, fbs[0]);
	glFramebufferDrawBufferEXT(fbs[1], attachments[1]);

	for (i = 0; i < max_color_attachments; i++) {
		const int buffer_count = MIN2(max_draw_buffers, max_color_attachments - i);

		glFramebufferDrawBufferEXT(fbs[1], attachments[i]);
		glFramebufferDrawBuffersEXT(fbs[2],
					    buffer_count,
					    &attachments[i]);
		glBindFramebuffer(GL_FRAMEBUFFER, fbs[1]);
		glGetIntegerv(GL_DRAW_BUFFER, &got);

		if (got != attachments[i]) {
			piglit_loge("glFramebufferDrawBufferEXT(..., %s) failed. Got %s\n",
				    piglit_get_gl_enum_name(attachments[i]),
				    piglit_get_gl_enum_name(got));
			free(attachments);
			return PIGLIT_FAIL;
		}

		glBindFramebuffer(GL_FRAMEBUFFER, fbs[2]);
		for (j = 0; j < buffer_count; j++) {
			glGetIntegerv(GL_DRAW_BUFFER0 + j, &got);
			if (got != attachments[i + j]) {
				piglit_loge("glFramebuffersDrawBufferEXT(..., %d, ...) failed.\n"
					    "Buffer %d: expected %s but got %s\n",
					    buffer_count,
					    j,
					    piglit_get_gl_enum_name(attachments[i + j]),
					    piglit_get_gl_enum_name(got));
				free(attachments);
				return PIGLIT_FAIL;
			}
		}
	}

	glDeleteFramebuffers(ARRAY_SIZE(fbs), fbs);

	free(attachments);
	return piglit_check_gl_error(GL_NO_ERROR) ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_FramebufferReadDrawBufferEXTDefault(void* d)
{
	GLuint fb;
	int i, got;
	static const GLenum attachments[] = {
		GL_NONE,
		GL_FRONT, GL_BACK,
		GL_LEFT, GL_FRONT_AND_BACK,
		GL_FRONT_LEFT, GL_BACK_LEFT
	};
	glGenFramebuffers(1, &fb);
	glFramebufferDrawBufferEXT(0, attachments[1]);
	glFramebufferReadBufferEXT(0, attachments[1]);

	for (i = 0; i < ARRAY_SIZE(attachments); i++) {
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glFramebufferDrawBufferEXT(0, attachments[i]);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glGetIntegerv(GL_DRAW_BUFFER, &got);

		if (!piglit_check_gl_error(GL_NO_ERROR) || got != attachments[i]) {
			piglit_loge("glFramebufferDrawBufferEXT(0, %s) failed. Got %s\n",
				    piglit_get_gl_enum_name(attachments[i]),
				    piglit_get_gl_enum_name(got));
			return PIGLIT_FAIL;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
	}

	for (i = 0; i < ARRAY_SIZE(attachments); i++) {
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glFramebufferReadBufferEXT(0, attachments[i]);
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glGetIntegerv(GL_READ_BUFFER, &got);

		if (!piglit_check_gl_error(GL_NO_ERROR) || got != attachments[i]) {
			piglit_loge("glFramebufferReadBufferEXT(0, %s) failed. Got %s\n",
				    piglit_get_gl_enum_name(attachments[i]),
				    piglit_get_gl_enum_name(got));
			return PIGLIT_FAIL;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
	}

	glDeleteFramebuffers(1, &fb);

	return piglit_check_gl_error(GL_NO_ERROR) ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_FramebufferReadBufferEXT(void* d)
{
	int max_color_attachments;
	GLuint fb;
	int i, got;
	GLenum* attachments;

	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &max_color_attachments);

	attachments = (GLenum*) malloc((max_color_attachments + 1) * sizeof(GLenum));
	for (i = 0; i < max_color_attachments; i++) {
		attachments[i] = GL_COLOR_ATTACHMENT0 + i;
	}
	attachments[max_color_attachments] = GL_NONE;
	max_color_attachments += 1;

	glGenFramebuffers(1, &fb);
	glFramebufferReadBufferEXT(fb, attachments[1]);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	for (i = 0; i < max_color_attachments; i++) {
		glFramebufferReadBufferEXT(fb, attachments[i]);
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glGetIntegerv(GL_READ_BUFFER, &got);

		if (got != attachments[i]) {
			piglit_loge("glFramebufferReadBufferEXT(..., %s) failed. Got %s\n",
				    piglit_get_gl_enum_name(attachments[i]),
				    piglit_get_gl_enum_name(got));
			free(attachments);
			return PIGLIT_FAIL;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}

	glDeleteFramebuffers(1, &fb);

	free(attachments);
	return piglit_check_gl_error(GL_NO_ERROR) ? PIGLIT_PASS : PIGLIT_FAIL;
}

enum piglit_result
test_GetFramebufferParameterivEXT(void* data)
{
	int i;
	GLuint fb;
	int got, expected;
	int max_draw_buffers;
	static GLenum pnames[2 + 16];
	pnames[0] = GL_DRAW_BUFFER;
	pnames[1] = GL_READ_BUFFER;

	glGetIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers);
	for (i = 0; i < max_draw_buffers; i++) {
		pnames[2 + i] = GL_DRAW_BUFFER0 + i;
	}

	/* The GL_EXT_direct_state_access says:
	 *
	 * The query returns the same value in param that GetIntegerv would
	 * return if called with pname and param as if the framebuffer specified
	 * by the framebuffer parameter had been bound with BindFramebuffer
	 */
	glGenFramebuffers(1, &fb);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	for (i = 0; i < 2 + max_draw_buffers; i++) {
		glGetFramebufferParameterivEXT(fb, pnames[i], &got);

		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		glGetIntegerv(pnames[i], &expected);

		if (got != expected) {
			piglit_loge("glGetFramebufferParameterivEXT(..., %s, ...) failed.\n"
				    "Expected %s but got %s\n",
				    piglit_get_gl_enum_name(pnames[i]),
				    piglit_get_gl_enum_name(expected),
				    piglit_get_gl_enum_name(got));
			return PIGLIT_FAIL;
		}

		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
	glDeleteFramebuffers(1, &fb);
	return piglit_check_gl_error(GL_NO_ERROR) ? PIGLIT_PASS : PIGLIT_FAIL;
}

static GLenum
dimension_to_target(int n)
{
	assert(n == 1 || n == 2 || n == 3);
	switch (n) {
		case 1: return GL_TEXTURE_1D;
		case 2: return GL_TEXTURE_2D;
		case 3:
		default:
			return GL_TEXTURE_3D;
	}
}

static enum piglit_result
test_NamedFramebufferTextureNDEXT(void* data)
{
	const int n = (int)(intptr_t) data;
	const GLenum target = dimension_to_target(n);
	GLuint color_texture;
	GLuint framebuffer;
	int got;

	glGenTextures(1, &color_texture);
	glBindTexture(target, color_texture);
	glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
	glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
	glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
	if (target == GL_TEXTURE_1D) {
		glTexImage1D(target, 0, GL_RGBA, piglit_width,
			     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	} else if (target == GL_TEXTURE_2D) {
		glTexImage2D(target, 0, GL_RGBA, piglit_width, piglit_height,
			     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	} else {
		glTexImage3D(target, 0, GL_RGBA, piglit_width, piglit_height, 1,
			     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	}
	glBindTexture(target, 0);

	glGenFramebuffers(1, &framebuffer);

	if (target == GL_TEXTURE_1D) {
		glNamedFramebufferTexture1DEXT(framebuffer, GL_COLOR_ATTACHMENT0,
					       target, color_texture, 0);
	} else if (target == GL_TEXTURE_2D) {
		glNamedFramebufferTexture2DEXT(framebuffer, GL_COLOR_ATTACHMENT0,
					       target, color_texture, 0);
	} else if (target == GL_TEXTURE_3D) {
		glNamedFramebufferTexture3DEXT(framebuffer, GL_COLOR_ATTACHMENT0,
					       target, color_texture, 0, 0);
	}

	if (!piglit_check_gl_error(GL_NO_ERROR)) {
		return PIGLIT_FAIL;
	}
	if (glCheckNamedFramebufferStatusEXT(framebuffer, GL_FRAMEBUFFER)
	    != GL_FRAMEBUFFER_COMPLETE) {
		return PIGLIT_FAIL;
	}

	glGetNamedFramebufferAttachmentParameteriv(framebuffer,
						   GL_COLOR_ATTACHMENT0,
						   GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
						   &got);
	if (got != GL_TEXTURE) {
		return PIGLIT_FAIL;
	}

	glGetNamedFramebufferAttachmentParameteriv(framebuffer,
						   GL_COLOR_ATTACHMENT0,
						   GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
						   &got);
	if (got != color_texture) {
		return PIGLIT_FAIL;
	}

	glDeleteFramebuffers(1, &framebuffer);

	return PIGLIT_PASS;
}

void
piglit_init(int argc, char **argv)
{
	piglit_require_extension("GL_EXT_direct_state_access");
	struct piglit_subtest tests[] = {
		{
			"FramebufferDrawBufferEXT",
			NULL,
			test_FramebufferDrawBufferEXT
		},
		{
			"FramebufferReadDrawBufferEXT (default framebuffer)",
			NULL,
			test_FramebufferReadDrawBufferEXTDefault
		},
		{
			"FramebufferReadBufferEXT",
			NULL,
			test_FramebufferReadBufferEXT,
		},
		{
			"GetFramebufferParameterivEXT",
			NULL,
			test_GetFramebufferParameterivEXT,
		},
		{
			"NamedFramebufferTexture1DEXT",
			NULL,
			test_NamedFramebufferTextureNDEXT,
			(void*) 1
		},
		{
			"NamedFramebufferTexture2DEXT",
			NULL,
			test_NamedFramebufferTextureNDEXT,
			(void*) 2
		},
		{
			"NamedFramebufferTexture3DEXT",
			NULL,
			test_NamedFramebufferTextureNDEXT,
			(void*) 3
		},
		{
			NULL
		}
	};

	piglit_report_result(piglit_run_selected_subtests(tests, NULL, 0, PIGLIT_PASS));
}

enum piglit_result
piglit_display(void)
{
	/* UNREACHABLE */
	return PIGLIT_FAIL;
}

