/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2018 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 Built-in function tests for uniform constants.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderUniformIntegerFunctionTests.hpp"
#include "glsShaderExecUtil.hpp"
#include "glwFunctions.hpp"
#include "tcuTestLog.hpp"
#include <iostream>

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::vector;
using std::string;
using tcu::TestLog;
using namespace gls::ShaderExecUtil;

class UniformIntegerFunctionCase : public TestCase
{
public:
								UniformIntegerFunctionCase				(Context& context, const char* description, int inputValue, glu::Precision precision, glu::ShaderType shaderType);
								~UniformIntegerFunctionCase				(void);

	void						init									(void);
	void						deinit									(void);
	IterateResult				iterate									(void);
	virtual const char*			getFunctionName()						= 0;
	virtual int					computeExpectedResult(deInt32 value)	= 0;

protected:
								UniformIntegerFunctionCase				(const UniformIntegerFunctionCase& other);
	UniformIntegerFunctionCase&	operator=								(const UniformIntegerFunctionCase& other);

private:
	ShaderSpec					m_spec;
	glu::ShaderType				m_shaderType;
	int							m_input;
	int							m_value;
	ShaderExecutor*				m_executor;
};

static std::string getCaseName (glu::Precision precision, glu::ShaderType shaderType);

UniformIntegerFunctionCase::UniformIntegerFunctionCase(Context& context, const char* description, int inputValue, glu::Precision precision, glu::ShaderType shaderType)
	: TestCase(context, getCaseName(precision, shaderType).c_str(), description)
	, m_shaderType(shaderType)
	, m_input(inputValue)
	, m_value(0)
	, m_executor(DE_NULL)
{
	m_spec.version = glu::GLSL_VERSION_310_ES;

	std::ostringstream oss;
	glu::VarType varType(glu::TYPE_INT, precision);
	oss << "uniform " << glu::declare(varType, "value", 0) << ";\n";
	m_spec.globalDeclarations = oss.str();
	m_spec.outputs.push_back(Symbol("result", glu::VarType(glu::TYPE_INT, glu::PRECISION_LOWP)));
	m_spec.outputs.push_back(Symbol("comparison", glu::VarType(glu::TYPE_BOOL, glu::PRECISION_LAST)));
}

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

void UniformIntegerFunctionCase::deinit(void)
{
	delete m_executor;
	m_executor = DE_NULL;
}

void UniformIntegerFunctionCase::init(void)
{
	std::ostringstream oss;
	oss <<
		"result = " << getFunctionName() << "(value);\n"
		"comparison = (" << getFunctionName() << "(value) == " << computeExpectedResult(m_input) << ");\n";
	m_spec.source = oss.str();

	DE_ASSERT(!m_executor);
	m_executor = createExecutor(m_context.getRenderContext(), m_shaderType, m_spec);
	m_testCtx.getLog() << m_executor;

	if (!m_executor->isOk())
		throw tcu::TestError("Compile failed");

	m_value = m_context.getRenderContext().getFunctions().getUniformLocation(m_executor->getProgram(), "value");
}

tcu::TestNode::IterateResult UniformIntegerFunctionCase::iterate(void)
{
	deInt32					result;
	deBool					comparison;
	vector<void*>			outputPointers	(2);

	outputPointers[0]= &result;
	outputPointers[1] = &comparison;

	m_executor->useProgram();
	m_context.getRenderContext().getFunctions().uniform1i(m_value, m_input);
	m_executor->execute(1, DE_NULL, &outputPointers[0]);

	int expectedResult = computeExpectedResult(m_input);
	if (result != expectedResult) {
		m_testCtx.getLog() << TestLog::Message << "ERROR: comparison failed for " << getFunctionName() << "(" << m_input << ") == " << expectedResult << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << "input: " << m_input << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << "result: " << result << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
		return STOP;
	} else if (!comparison) {
		m_testCtx.getLog() << TestLog::Message << "ERROR: result is as expected, but not when use in condition statement (" << getFunctionName() << "(" << m_input << ") == " << expectedResult << ") == true" << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << "input:" << m_input << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << "result: " << result << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << "comparison: " << comparison << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result comparison failed");
		return STOP;
	}
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

static const char* getPrecisionPostfix (glu::Precision precision)
{
	static const char* s_postfix[] =
		{
			"lowp",
			"mediump",
			"highp"
		};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_postfix) == glu::PRECISION_LAST);
	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
	return s_postfix[precision];
}

static const char* getShaderTypePostfix (glu::ShaderType shaderType)
{
	static const char* s_postfix[] =
		{
			"_vertex",
			"_fragment",
			"_geometry",
			"_tess_control",
			"_tess_eval",
			"_compute"
		};
	DE_ASSERT(de::inBounds<int>(shaderType, 0, DE_LENGTH_OF_ARRAY(s_postfix)));
	return s_postfix[shaderType];
}

static std::string getCaseName (glu::Precision precision, glu::ShaderType shaderType)
{
	return string(getPrecisionPostfix(precision)) + getShaderTypePostfix(shaderType);
}

static int findMSB (deInt32 value)
{
	if (value > 0)
		return 31 - deClz32((deUint32)value);
	else if (value < 0)
		return 31 - deClz32(~(deUint32)value);
	else
		return -1;
}

class FindMSBEdgeCase : public UniformIntegerFunctionCase {
public:
	FindMSBEdgeCase(Context& context, int inputValue, glu::Precision precision, glu::ShaderType shaderType)
		: UniformIntegerFunctionCase(context, "findMSB", inputValue, precision, shaderType)
	{
	}

protected:
	const char* getFunctionName() {
		return "findMSB";
	}

	int computeExpectedResult(deInt32 input) {
		return findMSB(input);
	}
};

static int findLSB (deUint32 value)
{
	for (int i = 0; i < 32; i++)
	{
		if (value & (1u<<i))
			return i;
	}
	return -1;
}

class FindLSBEdgeCase : public UniformIntegerFunctionCase {
public:
	FindLSBEdgeCase(Context& context, int inputValue, glu::Precision precision, glu::ShaderType shaderType)
		: UniformIntegerFunctionCase(context, "findLSB", inputValue, precision, shaderType)
	{
	}

protected:
	const char* getFunctionName() {
		return "findLSB";
	}

	int computeExpectedResult(deInt32 input) {
		return findLSB(input);
	}
};


template<class TestClass>
static void addFunctionCases (TestCaseGroup* parent, const char* functionName, int input)
{
	tcu::TestCaseGroup* group = new tcu::TestCaseGroup(parent->getTestContext(), functionName, functionName);
	parent->addChild(group);
	for (int prec = glu::PRECISION_LOWP; prec <= glu::PRECISION_HIGHP; prec++)
	{
		for (int shaderTypeNdx = 0; shaderTypeNdx < glu::SHADERTYPE_LAST; shaderTypeNdx++)
		{
			group->addChild(new TestClass(parent->getContext(), input, glu::Precision(prec), glu::ShaderType(shaderTypeNdx)));
		}
	}
}

ShaderUniformIntegerFunctionTests::ShaderUniformIntegerFunctionTests(Context& context)
	: TestCaseGroup(context, "uniform", "Function on uniform")
{
}

ShaderUniformIntegerFunctionTests::~ShaderUniformIntegerFunctionTests()
{
}
void ShaderUniformIntegerFunctionTests::init()
{
	addFunctionCases<FindMSBEdgeCase>(this, "findMSBZero", 0);
	addFunctionCases<FindMSBEdgeCase>(this, "findMSBMinusOne", -1);
	addFunctionCases<FindLSBEdgeCase>(this, "findLSBZero", 0);
	addFunctionCases<FindLSBEdgeCase>(this, "findLSBMinusOne", -1);
}

}
}
}