/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * 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 Integer64 State Query tests.
 *//*--------------------------------------------------------------------*/

#include "es3fInteger64StateQueryTests.hpp"
#include "glsStateQueryUtil.hpp"
#include "es3fApiCase.hpp"
#include "gluRenderContext.hpp"
#include "tcuRenderTarget.hpp"
#include "glwEnums.hpp"

#include <limits>

using namespace glw; // GLint and other
using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;

namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace Integer64StateQueryVerifiers
{

// StateVerifier

class StateVerifier : protected glu::CallLogWrapper
{
public:
						StateVerifier							(const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix);
	virtual				~StateVerifier							(); // make GCC happy

	const char*			getTestNamePostfix						(void) const;

	virtual void		verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference) = DE_NULL;

private:
	const char*	const	m_testNamePostfix;
};

StateVerifier::StateVerifier (const glw::Functions& gl, tcu::TestLog& log, const char* testNamePostfix)
	: glu::CallLogWrapper	(gl, log)
	, m_testNamePostfix		(testNamePostfix)
{
	enableLogging(true);
}

StateVerifier::~StateVerifier ()
{
}

const char* StateVerifier::getTestNamePostfix (void) const
{
	return m_testNamePostfix;
}

// GetBooleanVerifier

class GetBooleanVerifier : public StateVerifier
{
public:
			GetBooleanVerifier				(const glw::Functions& gl, tcu::TestLog& log);
	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
};

GetBooleanVerifier::GetBooleanVerifier (const glw::Functions& gl, tcu::TestLog& log)
	: StateVerifier(gl, log, "_getboolean")
{
}

void GetBooleanVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
{
	using tcu::TestLog;

	StateQueryMemoryWriteGuard<GLboolean> state;
	glGetBooleanv(name, &state);

	if (!state.verifyValidity(testCtx))
		return;

	if (state == GL_TRUE) // state is non-zero, could be greater than reference (correct)
		return;

	if (state == GL_FALSE) // state is zero
	{
		if (reference > 0) // and reference is greater than zero?
		{
			testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE" << TestLog::EndMessage;
			if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
				testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
		}
	}
	else
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: expected GL_TRUE or GL_FALSE" << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid boolean value");
	}
}

//GetIntegerVerifier

class GetIntegerVerifier : public StateVerifier
{
public:
			GetIntegerVerifier				(const glw::Functions& gl, tcu::TestLog& log);
	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
};

GetIntegerVerifier::GetIntegerVerifier (const glw::Functions& gl, tcu::TestLog& log)
	: StateVerifier(gl, log, "_getinteger")
{
}

void GetIntegerVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
{
	using tcu::TestLog;

	StateQueryMemoryWriteGuard<GLint> state;
	glGetIntegerv(name, &state);

	if (!state.verifyValidity(testCtx))
		return;

	// check that the converted value would be in the correct range, otherwise checking wont tell us anything
	if (reference > (GLuint64)std::numeric_limits<GLint>::max())
		return;

	if (GLuint(state) < reference)
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << reference << "; got " << state << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid integer value");
	}
}

//GetFloatVerifier

class GetFloatVerifier : public StateVerifier
{
public:
			GetFloatVerifier					(const glw::Functions& gl, tcu::TestLog& log);
	void	verifyUnsignedInteger64GreaterOrEqual	(tcu::TestContext& testCtx, GLenum name, GLuint64 reference);
};

GetFloatVerifier::GetFloatVerifier (const glw::Functions& gl, tcu::TestLog& log)
	: StateVerifier(gl, log, "_getfloat")
{
}

void GetFloatVerifier::verifyUnsignedInteger64GreaterOrEqual (tcu::TestContext& testCtx, GLenum name, GLuint64 reference)
{
	using tcu::TestLog;

	StateQueryMemoryWriteGuard<GLfloat> state;
	glGetFloatv(name, &state);

	if (!state.verifyValidity(testCtx))
		return;

	if (state < GLfloat(reference))
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: expected greater or equal to " << GLfloat(reference) << "; got " << state << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid float value");
	}
}

} // Integer64StateQueryVerifiers

namespace
{

using namespace Integer64StateQueryVerifiers;

class ConstantMinimumValue64TestCase : public ApiCase
{
public:
	ConstantMinimumValue64TestCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLuint64 minValue)
		: ApiCase		(context, name, description)
		, m_targetName	(targetName)
		, m_minValue	(minValue)
		, m_verifier	(verifier)
	{
	}

	void test (void)
	{
		m_verifier->verifyUnsignedInteger64GreaterOrEqual(m_testCtx, m_targetName, m_minValue);
		expectError(GL_NO_ERROR);
	}

private:
	GLenum			m_targetName;
	GLuint64		m_minValue;
	StateVerifier*	m_verifier;
};

class MaxCombinedStageUniformComponentsCase : public ApiCase
{
public:
	MaxCombinedStageUniformComponentsCase (Context& context, StateVerifier* verifier, const char* name, const char* description, GLenum targetName, GLenum targetMaxUniformBlocksName, GLenum targetMaxUniformComponentsName)
		: ApiCase							(context, name, description)
		, m_targetName						(targetName)
		, m_targetMaxUniformBlocksName		(targetMaxUniformBlocksName)
		, m_targetMaxUniformComponentsName	(targetMaxUniformComponentsName)
		, m_verifier						(verifier)
	{
	}

	void test (void)
	{
		GLint uniformBlockSize = 0;
		glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &uniformBlockSize);
		expectError(GL_NO_ERROR);

		GLint maxUniformBlocks = 0;
		GLint maxUniformComponents = 0;
		glGetIntegerv(m_targetMaxUniformBlocksName, &maxUniformBlocks);
		glGetIntegerv(m_targetMaxUniformComponentsName, &maxUniformComponents);
		expectError(GL_NO_ERROR);

		// MAX_stage_UNIFORM_BLOCKS * MAX_UNIFORM_BLOCK_SIZE / 4 + MAX_stage_UNIFORM_COMPONENTS
		const GLuint64 minCombinedUniformComponents = GLuint64(maxUniformBlocks) * uniformBlockSize / 4 + maxUniformComponents;

		m_verifier->verifyUnsignedInteger64GreaterOrEqual(m_testCtx, m_targetName, minCombinedUniformComponents);
		expectError(GL_NO_ERROR);
	}

private:
	GLenum			m_targetName;
	GLenum			m_targetMaxUniformBlocksName;
	GLenum			m_targetMaxUniformComponentsName;
	StateVerifier*	m_verifier;
};

#define FOR_EACH_VERIFIER(VERIFIERS, CODE_BLOCK)												\
	for (int _verifierNdx = 0; _verifierNdx < DE_LENGTH_OF_ARRAY(VERIFIERS); _verifierNdx++)	\
	{																							\
		StateVerifier* verifier = VERIFIERS[_verifierNdx];										\
		CODE_BLOCK;																				\
	}

} // anonymous

Integer64StateQueryTests::Integer64StateQueryTests (Context& context)
	: TestCaseGroup			(context, "integers64", "Integer (64) Values")
	, m_verifierBoolean		(DE_NULL)
	, m_verifierInteger		(DE_NULL)
	, m_verifierFloat		(DE_NULL)
{
}

Integer64StateQueryTests::~Integer64StateQueryTests (void)
{
	deinit();
}

void Integer64StateQueryTests::init (void)
{
	DE_ASSERT(m_verifierBoolean == DE_NULL);
	DE_ASSERT(m_verifierInteger == DE_NULL);
	DE_ASSERT(m_verifierFloat == DE_NULL);

	m_verifierBoolean		= new GetBooleanVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
	m_verifierInteger		= new GetIntegerVerifier		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
	m_verifierFloat			= new GetFloatVerifier			(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());

	const struct LimitedStateInteger64
	{
		const char*		name;
		const char*		description;
		GLenum			targetName;
		GLuint64		minValue;
	} implementationLimits[] =
	{
		{ "max_element_index",			"MAX_ELEMENT_INDEX",		GL_MAX_ELEMENT_INDEX,			0x00FFFFFF /*2^24-1*/	},
		{ "max_server_wait_timeout",	"MAX_SERVER_WAIT_TIMEOUT",	GL_MAX_SERVER_WAIT_TIMEOUT,		0						},
		{ "max_uniform_block_size",		"MAX_UNIFORM_BLOCK_SIZE",	GL_MAX_UNIFORM_BLOCK_SIZE,		16384					}
	};

	// \note do not check the values with integer64 verifier as that has already been checked in implementation_limits
	StateVerifier* verifiers[] = {m_verifierBoolean, m_verifierInteger, m_verifierFloat};

	for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(implementationLimits); testNdx++)
		FOR_EACH_VERIFIER(verifiers, addChild(new ConstantMinimumValue64TestCase(m_context, verifier, (std::string(implementationLimits[testNdx].name) + verifier->getTestNamePostfix()).c_str(), implementationLimits[testNdx].description, implementationLimits[testNdx].targetName, implementationLimits[testNdx].minValue)));

	FOR_EACH_VERIFIER(verifiers, addChild(new MaxCombinedStageUniformComponentsCase (m_context, verifier,	(std::string("max_combined_vertex_uniform_components")		+ verifier->getTestNamePostfix()).c_str(),	"MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS,		GL_MAX_VERTEX_UNIFORM_BLOCKS,		GL_MAX_VERTEX_UNIFORM_COMPONENTS)));
	FOR_EACH_VERIFIER(verifiers, addChild(new MaxCombinedStageUniformComponentsCase (m_context, verifier,	(std::string("max_combined_fragment_uniform_components")	+ verifier->getTestNamePostfix()).c_str(),	"MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS,	GL_MAX_FRAGMENT_UNIFORM_BLOCKS,		GL_MAX_FRAGMENT_UNIFORM_COMPONENTS)));
}

void Integer64StateQueryTests::deinit (void)
{
	if (m_verifierBoolean)
	{
		delete m_verifierBoolean;
		m_verifierBoolean = DE_NULL;
	}
	if (m_verifierInteger)
	{
		delete m_verifierInteger;
		m_verifierInteger = DE_NULL;
	}
	if (m_verifierFloat)
	{
		delete m_verifierFloat;
		m_verifierFloat = DE_NULL;
	}

	this->TestCaseGroup::deinit();
}

} // Functional
} // gles3
} // deqp