/*-------------------------------------------------------------------------
 * OpenGL Conformance Test Suite
 * -----------------------------
 *
 * Copyright (c) 2015-2016 The Khronos Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */ /*!
 * \file
 * \brief
 */ /*-------------------------------------------------------------------*/

/**
 * \file  gl4cSyncTests.cpp
 * \brief Declares test classes for synchronization functionality.
 */ /*-------------------------------------------------------------------*/

#include "gl4cIncompleteTextureAccessTests.hpp"

#include "deSharedPtr.hpp"

#include "gluContextInfo.hpp"
#include "gluDefs.hpp"
#include "gluPixelTransfer.hpp"
#include "gluStrUtil.hpp"

#include "tcuFuzzyImageCompare.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"

#include "glw.h"
#include "glwFunctions.hpp"

namespace gl4cts
{
namespace IncompleteTextureAccess
{
/****************************************** Incomplete Texture Access Tests Group ***********************************************/

/** @brief Incomplete Texture Access Tests Group constructor.
 *
 *  @param [in] context     OpenGL context.
 */
Tests::Tests(deqp::Context& context)
	: TestCaseGroup(context, "incomplete_texture_access", "Incomplete Texture Access Tests Suite")
{
}

/** @brief Incomplete Texture Access Tests initializer. */
void Tests::init()
{
	addChild(new IncompleteTextureAccess::SamplerTest(m_context));
}

/*************************************** Sampler Incomplete Texture Access Test Test *******************************************/

/** @brief Sampler Incomplete Texture Access Test constructor.
 *
 *  @param [in] context     OpenGL context.
 */
SamplerTest::SamplerTest(deqp::Context& context)
	: deqp::TestCase(context, "sampler", "Fetch using sampler test"), m_po(0), m_to(0), m_fbo(0), m_rbo(0), m_vao(0)
{
	/* Intentionally left blank. */
}

/** @brief Iterate Incomplete Texture Access Test cases.
 *
 *  @return Iteration result.
 */
tcu::TestNode::IterateResult SamplerTest::iterate()
{
	/* Get context setup. */
	bool is_at_least_gl_45 = (glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)));

	if (!is_at_least_gl_45)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported");

		return STOP;
	}

	/* Running tests. */
	bool is_ok	= true;
	bool is_error = false;

	try
	{
		PrepareFramebuffer();
		PrepareVertexArrays();

		for (glw::GLuint i = 0; i < s_configurations_count; ++i)
		{
			PrepareProgram(s_configurations[i]);
			PrepareTexture(s_configurations[i]);

			Draw();

			if (!Check(s_configurations[i]))
			{
				m_context.getTestContext().getLog()
					<< tcu::TestLog::Message << "Incomplete texture sampler access test failed with sampler "
					<< s_configurations[i].sampler_template << "." << tcu::TestLog::EndMessage;

				is_ok = false;
			}

			CleanCase();
		}
	}
	catch (...)
	{
		is_ok	= false;
		is_error = true;
	}

	/* Cleanup. */
	CleanCase();
	CleanTest();

	/* Result's setup. */
	if (is_ok)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	}
	else
	{
		if (is_error)
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_INTERNAL_ERROR, "Error");
		}
		else
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
		}
	}

	return STOP;
}

void SamplerTest::PrepareProgram(Configuration configuration)
{
	/* Shortcut for GL functionality */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	struct Shader
	{
		glw::GLchar const* source[5];
		glw::GLsizei const count;
		glw::GLenum const  type;
		glw::GLuint		   id;
	} shader[] = { { { s_vertex_shader, NULL, NULL, NULL, NULL }, 1, GL_VERTEX_SHADER, 0 },
				   { { s_fragment_shader_head, configuration.sampler_template, s_fragment_shader_body,
					   configuration.fetch_template, s_fragment_shader_tail },
					 5,
					 GL_FRAGMENT_SHADER,
					 0 } };

	glw::GLuint const shader_count = sizeof(shader) / sizeof(shader[0]);

	try
	{
		/* Create program. */
		m_po = gl.createProgram();
		GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateProgram call failed.");

		/* Shader compilation. */

		for (glw::GLuint i = 0; i < shader_count; ++i)
		{
			{
				shader[i].id = gl.createShader(shader[i].type);

				GLU_EXPECT_NO_ERROR(gl.getError(), "glCreateShader call failed.");

				gl.attachShader(m_po, shader[i].id);

				GLU_EXPECT_NO_ERROR(gl.getError(), "glAttachShader call failed.");

				gl.shaderSource(shader[i].id, shader[i].count, shader[i].source, NULL);

				GLU_EXPECT_NO_ERROR(gl.getError(), "glShaderSource call failed.");

				gl.compileShader(shader[i].id);

				GLU_EXPECT_NO_ERROR(gl.getError(), "glCompileShader call failed.");

				glw::GLint status = GL_FALSE;

				gl.getShaderiv(shader[i].id, GL_COMPILE_STATUS, &status);
				GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv call failed.");

				if (GL_FALSE == status)
				{
					glw::GLint log_size = 0;
					gl.getShaderiv(shader[i].id, GL_INFO_LOG_LENGTH, &log_size);
					GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv call failed.");

					glw::GLchar* log_text = new glw::GLchar[log_size];

					gl.getShaderInfoLog(shader[i].id, log_size, NULL, &log_text[0]);

					m_context.getTestContext().getLog()
						<< tcu::TestLog::Message << "Shader compilation has failed.\n"
						<< "Shader type: " << glu::getShaderTypeStr(shader[i].type) << "\n"
						<< "Shader compilation error log:\n"
						<< log_text << "\n"
						<< "Shader source code:\n"
						<< shader[i].source[0] << (shader[i].source[1] ? shader[i].source[1] : "")
						<< (shader[i].source[2] ? shader[i].source[2] : "")
						<< (shader[i].source[3] ? shader[i].source[3] : "")
						<< (shader[i].source[4] ? shader[i].source[4] : "") << "\n"
						<< tcu::TestLog::EndMessage;

					delete[] log_text;

					GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderInfoLog call failed.");

					throw 0;
				}
			}
		}

		/* Link. */
		gl.linkProgram(m_po);

		GLU_EXPECT_NO_ERROR(gl.getError(), "glTransformFeedbackVaryings call failed.");

		glw::GLint status = GL_FALSE;

		gl.getProgramiv(m_po, GL_LINK_STATUS, &status);

		if (GL_TRUE == status)
		{
			for (glw::GLuint i = 0; i < shader_count; ++i)
			{
				if (shader[i].id)
				{
					gl.detachShader(m_po, shader[i].id);

					GLU_EXPECT_NO_ERROR(gl.getError(), "glDetachShader call failed.");
				}
			}
		}
		else
		{
			glw::GLint log_size = 0;

			gl.getProgramiv(m_po, GL_INFO_LOG_LENGTH, &log_size);

			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramiv call failed.");

			glw::GLchar* log_text = new glw::GLchar[log_size];

			gl.getProgramInfoLog(m_po, log_size, NULL, &log_text[0]);

			m_context.getTestContext().getLog() << tcu::TestLog::Message << "Program linkage has failed due to:\n"
												<< log_text << "\n"
												<< tcu::TestLog::EndMessage;

			delete[] log_text;

			GLU_EXPECT_NO_ERROR(gl.getError(), "glGetProgramInfoLog call failed.");

			throw 0;
		}
	}
	catch (...)
	{
		if (m_po)
		{
			gl.deleteProgram(m_po);

			m_po = 0;
		}
	}

	for (glw::GLuint i = 0; i < shader_count; ++i)
	{
		if (0 != shader[i].id)
		{
			gl.deleteShader(shader[i].id);

			shader[i].id = 0;
		}
	}

	if (0 == m_po)
	{
		throw 0;
	}
	else
	{
		gl.useProgram(m_po);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glUsePrograms has failed");
	}
}

void SamplerTest::PrepareTexture(Configuration configuration)
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_to)
	{
		throw 0;
	}

	gl.genTextures(1, &m_to);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenTextures has failed");

	gl.bindTexture(configuration.texture_target, m_to);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindTexture has failed");
}

void SamplerTest::PrepareFramebuffer()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Sanity checks. */
	if (m_fbo || m_rbo)
	{
		throw 0;
	}

	/* Framebuffer creation. */
	gl.genFramebuffers(1, &m_fbo);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenFramebuffers has failed");

	gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer has failed");

	gl.genRenderbuffers(1, &m_rbo);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glGenRenderbuffers has failed");

	gl.bindRenderbuffer(GL_RENDERBUFFER, m_rbo);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindRenderbuffer has failed");

	gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA32F, 1 /* x size */, 1 /* y size */);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glRenderbufferStorage has failed");

	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glFramebufferRenderbuffer has failed");

	/* Sanity checks. */
	if (GL_FRAMEBUFFER_COMPLETE != gl.checkFramebufferStatus(GL_FRAMEBUFFER))
	{
		throw 0;
	}
}

void SamplerTest::PrepareVertexArrays()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Sanity checks.*/
	if (m_vao)
	{
		throw 0;
	}

	/* Empty vao creation. */
	gl.genVertexArrays(1, &m_vao);
	GLU_EXPECT_NO_ERROR(gl.getError(), "gGenVertexArrays has failed");

	gl.bindVertexArray(m_vao);
	GLU_EXPECT_NO_ERROR(gl.getError(), "gBindVertexArrays has failed");
}

void SamplerTest::Draw()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Draw setup. */
	gl.activeTexture(GL_TEXTURE0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glActiveTexture has failed");

	gl.uniform1i(gl.getUniformLocation(m_po, "texture_input"), 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glUniform1i has failed");

	/* Draw. */
	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawArrays has failed");
}

bool SamplerTest::Check(Configuration configuration)
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Return storage. */
	glw::GLfloat result[4] = { 7.f, 7.f, 7.f, 7.f };

	/* Fetch. */
	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_FLOAT, result);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels has failed");

	/* Comparison. */
	for (glw::GLuint i = 0; i < 4 /* # components */; ++i)
	{
		if (de::abs(configuration.expected_result[i] - result[i]) > 0.0125 /* precision */)
		{
			/* Fail.*/
			return false;
		}
	}

	/* Comparsion passed.*/
	return true;
}

void SamplerTest::CleanCase()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Program cleanup. */
	if (m_po)
	{
		gl.deleteProgram(m_po);

		m_po = 0;
	}

	/* Texture cleanup. */
	if (m_to)
	{
		gl.deleteTextures(1, &m_to);

		m_to = 0;
	}

	/* Errors cleanup. */
	while (gl.getError())
		;
}

void SamplerTest::CleanTest()
{
	/* Shortcut for GL functionality. */
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	/* Framebuffer cleanup. */
	if (m_fbo)
	{
		gl.deleteFramebuffers(1, &m_fbo);

		m_fbo = 0;
	}

	/* Renderbuffer cleanup. */
	if (m_rbo)
	{
		gl.deleteRenderbuffers(1, &m_rbo);

		m_rbo = 0;
	}

	/* Vertex arrays cleanup. */
	if (m_vao)
	{
		gl.deleteVertexArrays(1, &m_vao);

		m_vao = 0;
	}

	/* Errors cleanup. */
	while (gl.getError())
		;
}

const struct SamplerTest::Configuration SamplerTest::s_configurations[] = {
	/* regular floating point sampling */
	{ GL_TEXTURE_1D, "sampler1D", "texture(texture_input, 0.0)", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_2D, "sampler2D", "texture(texture_input, vec2(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_3D, "sampler3D", "texture(texture_input, vec3(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_CUBE_MAP, "samplerCube", "texture(texture_input, vec3(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_RECTANGLE, "sampler2DRect", "texture(texture_input, vec2(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_1D_ARRAY, "sampler1DArray", "texture(texture_input, vec2(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_2D_ARRAY, "sampler2DArray", "texture(texture_input, vec3(0.0))", { 0.f, 0.f, 0.f, 1.f } },
	{ GL_TEXTURE_CUBE_MAP_ARRAY, "samplerCubeArray", "texture(texture_input, vec4(0.0))", { 0.f, 0.f, 0.f, 1.f } },

	/* Shadow textures. */
	{ GL_TEXTURE_1D,
	  "sampler1DShadow",
	  "vec4(texture(texture_input, vec3(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_2D,
	  "sampler2DShadow",
	  "vec4(texture(texture_input, vec3(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_CUBE_MAP,
	  "samplerCubeShadow",
	  "vec4(texture(texture_input, vec4(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_RECTANGLE,
	  "sampler2DRectShadow",
	  "vec4(texture(texture_input, vec3(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_1D_ARRAY,
	  "sampler1DArrayShadow",
	  "vec4(texture(texture_input, vec3(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_2D_ARRAY,
	  "sampler2DArrayShadow",
	  "vec4(texture(texture_input, vec4(0.0)), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } },
	{ GL_TEXTURE_CUBE_MAP_ARRAY,
	  "samplerCubeArrayShadow",
	  "vec4(texture(texture_input, vec4(0.0), 1.0), 0.0, 0.0, 0.0)",
	  { 0.f, 0.f, 0.f, 0.f } }
};

const glw::GLuint SamplerTest::s_configurations_count = sizeof(s_configurations) / sizeof(s_configurations[0]);

const glw::GLchar* SamplerTest::s_vertex_shader = "#version 450\n"
												  "\n"
												  "void main()\n"
												  "{\n"
												  "    switch(gl_VertexID)\n"
												  "    {\n"
												  "        case 0:\n"
												  "            gl_Position = vec4(-1.0, 1.0, 0.0, 1.0);\n"
												  "            break;\n"
												  "        case 1:\n"
												  "            gl_Position = vec4( 1.0, 1.0, 0.0, 1.0);\n"
												  "            break;\n"
												  "        case 2:\n"
												  "            gl_Position = vec4(-1.0,-1.0, 0.0, 1.0);\n"
												  "            break;\n"
												  "        case 3:\n"
												  "            gl_Position = vec4( 1.0,-1.0, 0.0, 1.0);\n"
												  "            break;\n"
												  "    }\n"
												  "}\n";

const glw::GLchar* SamplerTest::s_fragment_shader_head = "#version 450\n"
														 "\n"
														 "uniform ";

const glw::GLchar* SamplerTest::s_fragment_shader_body = " texture_input;\n"
														 "out vec4 texture_output;\n"
														 "\n"
														 "void main()\n"
														 "{\n"
														 "    texture_output = ";

const glw::GLchar* SamplerTest::s_fragment_shader_tail = ";\n"
														 "}\n";

} /* IncompleteTextureAccess namespace */
} /* gl4cts namespace */