/*
 * 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"

static int
parse_gl_version(int argc, char** argv)
{
	int version;
	if (argc < 2) {
		piglit_loge("Usage: %s 15|30\n", argv[0]);
		exit(1);
	}
	version = atoi(argv[1]);
	if (version != 15 && version != 30) {
		piglit_loge("Usage: %s 15|30\n", argv[0]);
		exit(1);
	}
	return version;
}

static int gl_compat_version;

PIGLIT_GL_TEST_CONFIG_BEGIN

	gl_compat_version = parse_gl_version(argc, argv);
	config.supports_gl_compat_version = gl_compat_version;
	config.khr_no_error_support = PIGLIT_HAS_ERRORS;

PIGLIT_GL_TEST_CONFIG_END

static void
read_buffer(GLuint buffer, void* got, unsigned size)
{
	memset(got, 0, size);
	glBindBuffer(GL_ARRAY_BUFFER, buffer);
	glGetBufferSubData(GL_ARRAY_BUFFER, 0, size, got);
}

static enum piglit_result
test_NamedBufferDataEXT(void* d)
{
	bool pass = true;
	GLuint buffers[3];
	static const float data[4] = { 1, 2, 3, 4 };
	float got[4];

	glGenBuffers(ARRAY_SIZE(buffers), buffers);

	/* Test glNamedBufferDataEXT */
	glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
	glNamedBufferDataEXT(buffers[0], sizeof(data), data, GL_STATIC_DRAW);
	read_buffer(buffers[0], got, sizeof(got));
	pass = memcmp(data, got, sizeof(data)) == 0 && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * If the buffer object named by the buffer parameter has not been
	 * previously bound or has been deleted since the last binding, the
	 * GL first creates a new state vector, initialized with a zero-sized
	 * memory buffer and comprising the state values listed in table 2.6.
	 */
	/* Test glNamedBufferDataEXT without calling glBindBuffer first */
	glNamedBufferDataEXT(buffers[1], sizeof(data), data, GL_STATIC_DRAW);
	read_buffer(buffers[1], got, sizeof(got));
	pass = memcmp(data, got, sizeof(data)) == 0 && pass;

	/* Test glNamedBufferDataEXT on a deleted buffer */
	glDeleteBuffers(1, &buffers[2]);
	glNamedBufferDataEXT(buffers[2], sizeof(data), data, GL_STATIC_DRAW);
	read_buffer(buffers[2], got, sizeof(got));
	pass = memcmp(data, got, sizeof(data)) == 0 && pass;

	glDeleteBuffers(ARRAY_SIZE(buffers), buffers);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * There is no buffer corresponding to the name zero, these commands
	 * generate the INVALID_OPERATION error if the buffer parameter is zero.
	 */
	glNamedBufferDataEXT(0, sizeof(data), data, GL_STATIC_DRAW);
	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_NamedBufferSubDataEXT(void* d)
{
	bool pass = true;
	GLuint buffers[3];
	static const float data[4] = { 1, 2, 3, 4 };
	static const float expected[4] = { 1, 2, 1, 2 };
	float got[4];

	glGenBuffers(ARRAY_SIZE(buffers), buffers);

	/* Test glNamedBufferSubDataEXT */
	glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
	glNamedBufferDataEXT(buffers[0], sizeof(data), data, GL_STATIC_DRAW);
	glNamedBufferSubDataEXT(buffers[0], 2 * sizeof(float), 2 * sizeof(float), data);
	read_buffer(buffers[0], got, sizeof(got));
	pass = memcmp(expected, got, sizeof(expected)) == 0 && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * If the buffer object named by the buffer parameter has not been
	 * previously bound or has been deleted since the last binding, the
	 * GL first creates a new state vector, initialized with a zero-sized
	 * memory buffer and comprising the state values listed in table 2.6.
	 */
	/* Test glNamedBufferSubDataEXT without calling glBindBuffer first */
	glNamedBufferDataEXT(buffers[1], sizeof(data), data, GL_STATIC_DRAW);
	glNamedBufferSubDataEXT(buffers[1], 2 * sizeof(float), 2 * sizeof(float), data);
	read_buffer(buffers[1], got, sizeof(got));
	pass = memcmp(expected, got, sizeof(expected)) == 0 && pass;

	/* Test glNamedBufferDataEXT on a deleted buffer */
	glDeleteBuffers(1, &buffers[2]);
	glNamedBufferDataEXT(buffers[2], sizeof(data), data, GL_STATIC_DRAW);
	glNamedBufferSubDataEXT(buffers[2], 2 * sizeof(float), 2 * sizeof(float), data);
	read_buffer(buffers[2], got, sizeof(got));
	pass = memcmp(expected, got, sizeof(expected)) == 0 && pass;

	glDeleteBuffers(ARRAY_SIZE(buffers), buffers);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * There is no buffer corresponding to the name zero, these commands
	 * generate the INVALID_OPERATION error if the buffer parameter is zero.
	 */
	glNamedBufferSubDataEXT(0, 2 * sizeof(float), 2 * sizeof(float), data);
	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_MapNamedBufferEXT(void* d)
{
	bool pass = true;
	GLuint buffer;
	static const float data[4] = { 1, 2, 3, 4 };
	void* buf, *ptr;
	GLint value;

	if (gl_compat_version < 30) {
		return PIGLIT_SKIP;
	}

	glGenBuffers(1, &buffer);

	glNamedBufferDataEXT(buffer, sizeof(data), data, GL_STATIC_DRAW);

	/* Test glNamedBufferDataEXT */
	buf = glMapNamedBufferEXT(buffer, GL_READ_ONLY);
	if (!buf) {
		return PIGLIT_FAIL;
	}
	pass = memcmp(buf, data, sizeof(data)) == 0 && pass;
	glGetNamedBufferPointervEXT(buffer, GL_BUFFER_MAP_POINTER, &ptr);
	pass = buf == ptr && pass;
	glGetNamedBufferParameterivEXT(buffer, GL_BUFFER_ACCESS, &value);
	pass = value == GL_READ_ONLY && pass;
	glGetNamedBufferParameterivEXT(buffer, GL_BUFFER_MAPPED, &value);
	pass = value && pass;
	glUnmapNamedBufferEXT(buffer);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * If the buffer object named by the buffer parameter has not been
	 * previously bound or has been deleted since the last binding, the
	 * GL first creates a new state vector, initialized with a zero-sized
	 * memory buffer and comprising the state values listed in table 2.6.
	 */
	glDeleteBuffers(1, &buffer);

	glGetNamedBufferPointervEXT(buffer, GL_BUFFER_MAP_POINTER, &ptr);
	pass = ptr == NULL &&
	       piglit_check_gl_error(GL_NO_ERROR)
	       && pass;

	glDeleteBuffers(1, &buffer);

	/* The GL_EXT_direct_state_access says:
	 *
	 * There is no buffer corresponding to the name zero, these commands
	 * generate the INVALID_OPERATION error if the buffer parameter is zero.
	 */
	glMapNamedBufferEXT(0, GL_READ_ONLY);
	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_MapNamedBufferRangeEXT(void* d)
{
	bool pass = true;
	GLuint buffer;
	static const float data[4] = { 1, 2, 3, 4 };
	void* buf;

	if (gl_compat_version < 30) {
		return PIGLIT_SKIP;
	}

	glGenBuffers(1, &buffer);

	glNamedBufferDataEXT(buffer, sizeof(data), data, GL_STATIC_DRAW);

	/* Test MapNamedBufferRangeEXT */
	buf = glMapNamedBufferRangeEXT(buffer, 0, sizeof(data), GL_MAP_READ_BIT);
	if (!buf) {
		return PIGLIT_FAIL;
	}
	pass = memcmp(buf, data, sizeof(data)) == 0 && pass;

	glDeleteBuffers(1, &buffer);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* The GL_EXT_direct_state_access says:
	 *
	 * There is no buffer corresponding to the name zero, these commands
	 * generate the INVALID_OPERATION error if the buffer parameter is zero.
	 */
	glMapNamedBufferRangeEXT(0, 0, sizeof(data), GL_MAP_READ_BIT);
	pass = piglit_check_gl_error(GL_INVALID_OPERATION) && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_FlushMappedNamedBufferRangeEXT(void* d)
{
	bool pass = true;
	GLuint buffer;
	static const float data[4] = { 1, 2, 3, 4 };
	float* buf;

	if (gl_compat_version < 30) {
		return PIGLIT_SKIP;
	}

	glGenBuffers(1, &buffer);

	glNamedBufferDataEXT(buffer, sizeof(data), data, GL_STATIC_DRAW);

	/* Test MapNamedBufferRangeEXT */
	buf = (float*) glMapNamedBufferRangeEXT(buffer,
						0,
						sizeof(data),
						GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_WRITE_BIT);
	if (!buf) {
		return PIGLIT_FAIL;
	}

	buf[2] = 5;

	glFlushMappedNamedBufferRangeEXT(buffer, 3, 1);
	glUnmapNamedBufferEXT(buffer);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	/* Remap and check modified value */
	buf = (float*) glMapNamedBufferRangeEXT(buffer, 0, sizeof(data), GL_MAP_READ_BIT);

	pass = buf[2] == 5 && pass;
	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	glDeleteBuffers(1, &buffer);

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

static enum piglit_result
test_NamedCopyBufferSubDataEXT(void* d)
{
	bool pass = true;
	GLuint buffers[2];
	static const char data[] = {
		1, 2, 3, 4
	};
	char* buf;

	if (gl_compat_version < 30) {
		return PIGLIT_SKIP;
	}

	glGenBuffers(ARRAY_SIZE(buffers), buffers);

	glNamedBufferDataEXT(buffers[0], 4, data, GL_STATIC_DRAW);
	glNamedBufferDataEXT(buffers[1], 4, NULL, GL_STATIC_DRAW);

	/* copy buffers[0] in buffers[1] backward */
	for (int i = 0; i < ARRAY_SIZE(data); i++) {
		glNamedCopyBufferSubDataEXT(
			buffers[0], buffers[1],
			i, ARRAY_SIZE(data) - i - 1,
			1);
	}

	buf = (char*) glMapNamedBufferRangeEXT(buffers[1],
						0,
						ARRAY_SIZE(data),
						GL_MAP_READ_BIT);
	if (!buf) {
		return PIGLIT_FAIL;
	}

	for (int i = 0; i < ARRAY_SIZE(data); i++) {
		pass = buf[i] == data[ARRAY_SIZE(data) - i - 1] && pass;
	}

	glUnmapNamedBufferEXT(buffers[1]);
	glDeleteBuffers(ARRAY_SIZE(buffers), buffers);

	pass = piglit_check_gl_error(GL_NO_ERROR) && pass;

	return pass ? PIGLIT_PASS : PIGLIT_FAIL;
}

void
piglit_init(int argc, char **argv)
{
	piglit_require_extension("GL_EXT_direct_state_access");
	static const struct piglit_subtest tests[] = {
		{
			"NamedBufferDataEXT",
			NULL,
			test_NamedBufferDataEXT
		},
		{
			"NamedBufferSubDataEXT",
			NULL,
			test_NamedBufferSubDataEXT
		},
		{
			"MapNamedBufferEXT",
			NULL,
			test_MapNamedBufferEXT
		},
		{
			"MapNamedBufferRangeEXT",
			NULL,
			test_MapNamedBufferRangeEXT
		},
		{
			"FlushMappedNamedBufferRangeEXT",
			NULL,
			test_FlushMappedNamedBufferRangeEXT,
		},
		{
			"NamedCopyBufferSubDataEXT",
			NULL,
			test_NamedCopyBufferSubDataEXT,
		},
		{
			NULL
		}
	};

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

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

