#ifndef _GLSSTATEQUERYUTIL_HPP
#define _GLSSTATEQUERYUTIL_HPP
/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 State Query test utils.
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestContext.hpp"
#include "tcuResultCollector.hpp"
#include "glwDefs.hpp"
#include "deMath.h"

namespace glu
{
class CallLogWrapper;
} // glu

namespace deqp
{
namespace gls
{
namespace StateQueryUtil
{

#define GLS_COLLECT_GL_ERROR(RES, ERR, MSG) \
	do \
	{ \
		const deUint32 err = (ERR); \
		if (err != GL_NO_ERROR) \
			(RES).fail(std::string("Got Error ") + glu::getErrorStr(err).toString() + ": " + (MSG)); \
	} \
	while (deGetFalse())

/*--------------------------------------------------------------------*//*!
 * \brief Rounds given float to the nearest integer (half up).
 *
 * Returns the nearest integer for a float argument. In the case that there
 * are two nearest integers at the equal distance (aka. the argument is of
 * form x.5), the integer with the higher value is chosen. (x.5 rounds to x+1)
 *//*--------------------------------------------------------------------*/
template <typename T>
T roundGLfloatToNearestIntegerHalfUp (float val)
{
	return (T)(deFloatFloor(val + 0.5f));
}

/*--------------------------------------------------------------------*//*!
 * \brief Rounds given float to the nearest integer (half down).
 *
 * Returns the nearest integer for a float argument. In the case that there
 * are two nearest integers at the equal distance (aka. the argument is of
 * form x.5), the integer with the higher value is chosen. (x.5 rounds to x)
 *//*--------------------------------------------------------------------*/
template <typename T>
T roundGLfloatToNearestIntegerHalfDown (float val)
{
	return (T)(deFloatCeil(val - 0.5f));
}

template <typename T>
class StateQueryMemoryWriteGuard
{
public:
					StateQueryMemoryWriteGuard	(void);

					operator T&					(void);
	T*				operator &					(void);

	bool			isUndefined					(void) const;
	bool			isMemoryContaminated		(void) const;
	bool			isPreguardContaminated		(void) const;
	bool			isPostguardContaminated		(void) const;
	bool			verifyValidity				(tcu::TestContext& testCtx) const;
	bool			verifyValidity				(tcu::ResultCollector& result) const;

	const T&		get							(void) const { return m_value; }

private:
	enum
	{
		WRITE_GUARD_VALUE = 0xDE
	};

	T				m_preguard;
	T				m_value;
	T				m_postguard; // \note guards are not const qualified since the GL implementation might modify them
};

template <typename T>
StateQueryMemoryWriteGuard<T>::StateQueryMemoryWriteGuard (void)
{
	DE_STATIC_ASSERT(sizeof(T) * 3 == sizeof(StateQueryMemoryWriteGuard<T>)); // tightly packed

	for (size_t i = 0; i < sizeof(T); ++i)
	{
		((deUint8*)&m_preguard)[i]	= (deUint8)WRITE_GUARD_VALUE;
		((deUint8*)&m_value)[i]		= (deUint8)WRITE_GUARD_VALUE;
		((deUint8*)&m_postguard)[i]	= (deUint8)WRITE_GUARD_VALUE;
	}
}

template <typename T>
StateQueryMemoryWriteGuard<T>::operator T& (void)
{
	return m_value;
}

template <typename T>
T* StateQueryMemoryWriteGuard<T>::operator & (void)
{
	return &m_value;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::isUndefined () const
{
	for (size_t i = 0; i < sizeof(T); ++i)
		if (((deUint8*)&m_value)[i] != (deUint8)WRITE_GUARD_VALUE)
			return false;
	return true;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::isMemoryContaminated () const
{
	return isPreguardContaminated() || isPostguardContaminated();
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::isPreguardContaminated (void) const
{
	for (size_t i = 0; i < sizeof(T); ++i)
		if (((deUint8*)&m_preguard)[i] != (deUint8)WRITE_GUARD_VALUE)
			return true;
	return false;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::isPostguardContaminated (void) const
{
	for (size_t i = 0; i < sizeof(T); ++i)
		if (((deUint8*)&m_postguard)[i] != (deUint8)WRITE_GUARD_VALUE)
			return true;
	return false;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::verifyValidity (tcu::TestContext& testCtx) const
{
	using tcu::TestLog;

	if (isPreguardContaminated())
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: Pre-guard value was modified " << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did an illegal memory write");

		return false;
	}
	else if (isPostguardContaminated())
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: Post-guard value was modified " << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did an illegal memory write");

		return false;
	}
	else if (isUndefined())
	{
		testCtx.getLog() << TestLog::Message << "// ERROR: Get* did not return a value" << TestLog::EndMessage;
		if (testCtx.getTestResult() == QP_TEST_RESULT_PASS ||
			testCtx.getTestResult() == QP_TEST_RESULT_LAST)
			testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Get* did not return a value");

		return false;
	}

	return true;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::verifyValidity (tcu::ResultCollector& result) const
{
	using tcu::TestLog;

	if (isPreguardContaminated())
	{
		result.fail("pre-guard value was modified");
		return false;
	}
	else if (isPostguardContaminated())
	{
		result.fail("post-guard value was modified");
		return false;
	}
	else if (isUndefined())
	{
		result.fail("Get* did not return a value");
		return false;
	}

	return true;
}

template<typename T>
std::ostream& operator<< (std::ostream& str, const StateQueryMemoryWriteGuard<T>& guard)
{
	return str << guard.get();
}

// Verifiers

enum QueryType
{
	QUERY_BOOLEAN = 0,
	QUERY_BOOLEAN_VEC4,
	QUERY_ISENABLED,
	QUERY_INTEGER,
	QUERY_INTEGER64,
	QUERY_FLOAT,

	// indexed
	QUERY_INDEXED_BOOLEAN,
	QUERY_INDEXED_BOOLEAN_VEC4,
	QUERY_INDEXED_ISENABLED,
	QUERY_INDEXED_INTEGER,
	QUERY_INDEXED_INTEGER_VEC4,
	QUERY_INDEXED_INTEGER64,
	QUERY_INDEXED_INTEGER64_VEC4,

	// attributes
	QUERY_ATTRIBUTE_INTEGER,
	QUERY_ATTRIBUTE_FLOAT,
	QUERY_ATTRIBUTE_PURE_INTEGER,
	QUERY_ATTRIBUTE_PURE_UNSIGNED_INTEGER,

	// fb
	QUERY_FRAMEBUFFER_INTEGER,

	// program
	QUERY_PROGRAM_INTEGER,
	QUERY_PROGRAM_INTEGER_VEC3,

	// program pipeline
	QUERY_PIPELINE_INTEGER,

	// texture param
	QUERY_TEXTURE_PARAM_INTEGER,
	QUERY_TEXTURE_PARAM_FLOAT,
	QUERY_TEXTURE_PARAM_PURE_INTEGER,
	QUERY_TEXTURE_PARAM_PURE_UNSIGNED_INTEGER,
	QUERY_TEXTURE_PARAM_INTEGER_VEC4,
	QUERY_TEXTURE_PARAM_FLOAT_VEC4,
	QUERY_TEXTURE_PARAM_PURE_INTEGER_VEC4,
	QUERY_TEXTURE_PARAM_PURE_UNSIGNED_INTEGER_VEC4,

	// texture level
	QUERY_TEXTURE_LEVEL_INTEGER,
	QUERY_TEXTURE_LEVEL_FLOAT,

	// pointer
	QUERY_POINTER,

	// object states
	QUERY_ISTEXTURE,

	// query queries
	QUERY_QUERY,

	// sampler state
	QUERY_SAMPLER_PARAM_INTEGER,
	QUERY_SAMPLER_PARAM_FLOAT,
	QUERY_SAMPLER_PARAM_PURE_INTEGER,
	QUERY_SAMPLER_PARAM_PURE_UNSIGNED_INTEGER,
	QUERY_SAMPLER_PARAM_INTEGER_VEC4,
	QUERY_SAMPLER_PARAM_FLOAT_VEC4,
	QUERY_SAMPLER_PARAM_PURE_INTEGER_VEC4,
	QUERY_SAMPLER_PARAM_PURE_UNSIGNED_INTEGER_VEC4,

	QUERY_LAST
};

enum DataType
{
	DATATYPE_BOOLEAN = 0,
	DATATYPE_INTEGER,
	DATATYPE_INTEGER64,
	DATATYPE_FLOAT,
	DATATYPE_UNSIGNED_INTEGER,
	DATATYPE_INTEGER_VEC3,
	DATATYPE_FLOAT_VEC4,
	DATATYPE_INTEGER_VEC4,
	DATATYPE_INTEGER64_VEC4,
	DATATYPE_UNSIGNED_INTEGER_VEC4,
	DATATYPE_BOOLEAN_VEC4,
	DATATYPE_POINTER,

	DATATYPE_LAST
};

class QueriedState
{
public:
	typedef glw::GLint		GLIntVec3[3];
	typedef glw::GLint		GLIntVec4[4];
	typedef glw::GLuint		GLUintVec4[4];
	typedef glw::GLfloat	GLFloatVec4[4];
	typedef bool			BooleanVec4[4];
	typedef glw::GLint64	GLInt64Vec4[4];

							QueriedState			(void);
	explicit				QueriedState			(glw::GLint);
	explicit				QueriedState			(glw::GLint64);
	explicit				QueriedState			(bool);
	explicit				QueriedState			(glw::GLfloat);
	explicit				QueriedState			(glw::GLuint);
	explicit				QueriedState			(const GLIntVec3&);
	explicit				QueriedState			(void*);
	explicit				QueriedState			(const GLIntVec4&);
	explicit				QueriedState			(const GLUintVec4&);
	explicit				QueriedState			(const GLFloatVec4&);
	explicit				QueriedState			(const BooleanVec4&);
	explicit				QueriedState			(const GLInt64Vec4&);

	bool					isUndefined				(void) const;
	DataType				getType					(void) const;

	glw::GLint&				getIntAccess			(void);
	glw::GLint64&			getInt64Access			(void);
	bool&					getBoolAccess			(void);
	glw::GLfloat&			getFloatAccess			(void);
	glw::GLuint&			getUintAccess			(void);
	GLIntVec3&				getIntVec3Access		(void);
	void*&					getPtrAccess			(void);
	GLIntVec4&				getIntVec4Access		(void);
	GLUintVec4&				getUintVec4Access		(void);
	GLFloatVec4&			getFloatVec4Access		(void);
	BooleanVec4&			getBooleanVec4Access	(void);
	GLInt64Vec4&			getInt64Vec4Access		(void);

private:
	DataType				m_type;
	union
	{
		glw::GLint			vInt;
		glw::GLint64		vInt64;
		bool				vBool;
		glw::GLfloat		vFloat;
		glw::GLuint			vUint;
		GLIntVec3			vIntVec3;
		void*				vPtr;
		GLIntVec4			vIntVec4;
		GLUintVec4			vUintVec4;
		GLFloatVec4			vFloatVec4;
		BooleanVec4			vBooleanVec4;
		GLInt64Vec4			vInt64Vec4;
	} m_v;
};

// query functions

void queryState									(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum pname, QueriedState& state);
void queryIndexedState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, int index, QueriedState& state);
void queryAttributeState						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, int index, QueriedState& state);
void queryFramebufferState						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, glw::GLenum pname, QueriedState& state);
void queryProgramState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLuint program, glw::GLenum pname, QueriedState& state);
void queryPipelineState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLuint pipeline, glw::GLenum pname, QueriedState& state);
void queryTextureParamState						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, glw::GLenum pname, QueriedState& state);
void queryTextureLevelState						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, int level, glw::GLenum pname, QueriedState& state);
void queryPointerState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum pname, QueriedState& state);
void queryObjectState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLuint handle, QueriedState& state);
void queryQueryState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLenum target, glw::GLenum pname, QueriedState& state);
void querySamplerState							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, QueryType type, glw::GLuint sampler, glw::GLenum pname, QueriedState& state);

// verification functions

void verifyBoolean								(tcu::ResultCollector& result, QueriedState& state, bool expected);
void verifyInteger								(tcu::ResultCollector& result, QueriedState& state, int expected);
void verifyIntegerMin							(tcu::ResultCollector& result, QueriedState& state, int minValue);
void verifyIntegerMax							(tcu::ResultCollector& result, QueriedState& state, int maxValue);
void verifyIntegersEqual						(tcu::ResultCollector& result, QueriedState& stateA, QueriedState& stateB);
void verifyFloat								(tcu::ResultCollector& result, QueriedState& state, float expected);
void verifyFloatMin								(tcu::ResultCollector& result, QueriedState& state, float minValue);
void verifyFloatMax								(tcu::ResultCollector& result, QueriedState& state, float maxValue);
void verifyIntegerVec3							(tcu::ResultCollector& result, QueriedState& state, const tcu::IVec3& expected);
void verifyIntegerVec4							(tcu::ResultCollector& result, QueriedState& state, const tcu::IVec4& expected);
void verifyUnsignedIntegerVec4					(tcu::ResultCollector& result, QueriedState& state, const tcu::UVec4& expected);
void verifyFloatVec4							(tcu::ResultCollector& result, QueriedState& state, const tcu::Vec4& expected);
void verifyBooleanVec4							(tcu::ResultCollector& result, QueriedState& state, const tcu::BVec4& expected);
void verifyPointer								(tcu::ResultCollector& result, QueriedState& state, const void* expected);
void verifyNormalizedI32Vec4					(tcu::ResultCollector& result, QueriedState& state, const tcu::IVec4& expected);

// Helper functions that both query and verify

void verifyStateBoolean							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		bool expected,			QueryType type);
void verifyStateInteger							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int expected,			QueryType type);
void verifyStateIntegerMin						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int minValue,			QueryType type);
void verifyStateIntegerMax						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int maxValue,			QueryType type);
void verifyStateIntegerEqualToOther				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum other,		QueryType type);
void verifyStateFloat							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		float reference,		QueryType type);
void verifyStateFloatMin						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		float minValue,			QueryType type);
void verifyStateFloatMax						(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		float maxValue,			QueryType type);
void verifyStatePointer							(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		const void* expected,	QueryType type);
void verifyStateIndexedBoolean					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int index,				bool expected,				QueryType type);
void verifyStateIndexedBooleanVec4				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int index,				const tcu::BVec4& expected,	QueryType type);
void verifyStateIndexedInteger					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int index,				int expected,				QueryType type);
void verifyStateIndexedIntegerMin				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int index,				int minValue,				QueryType type);
void verifyStateAttributeInteger				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int index,				int expected,				QueryType type);
void verifyStateFramebufferInteger				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		int expected,				QueryType type);
void verifyStateFramebufferIntegerMin			(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		int minValue,				QueryType type);
void verifyStateProgramInteger					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint program,	glw::GLenum pname,		int expected,				QueryType type);
void verifyStateProgramIntegerVec3				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint program,	glw::GLenum pname,		const tcu::IVec3& expected,	QueryType type);
void verifyStatePipelineInteger					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint pipeline,	glw::GLenum pname,		int expected,				QueryType type);
void verifyStateTextureParamInteger				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		int expected,				QueryType type);
void verifyStateTextureParamFloat				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		float expected,				QueryType type);
void verifyStateTextureParamFloatVec4			(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		const tcu::Vec4& expected,	QueryType type);
void verifyStateTextureParamNormalizedI32Vec4	(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		const tcu::IVec4& expected,	QueryType type);
void verifyStateTextureParamIntegerVec4			(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		const tcu::IVec4& expected,	QueryType type);
void verifyStateTextureParamUnsignedIntegerVec4	(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		const tcu::UVec4& expected,	QueryType type);
void verifyStateTextureLevelInteger				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		int level,				glw::GLenum pname,			int expected,		QueryType type);
void verifyStateObjectBoolean					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint handle,		bool expected,			QueryType type);
void verifyStateQueryInteger					(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLenum target,		glw::GLenum pname,		int expected,				QueryType type);
void verifyStateSamplerParamInteger				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		int expected,				QueryType type);
void verifyStateSamplerParamFloat				(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		float expected,				QueryType type);
void verifyStateSamplerParamFloatVec4			(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		const tcu::Vec4& expected,	QueryType type);
void verifyStateSamplerParamNormalizedI32Vec4	(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		const tcu::IVec4& expected,	QueryType type);
void verifyStateSamplerParamIntegerVec4			(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		const tcu::IVec4& expected,	QueryType type);
void verifyStateSamplerParamUnsignedIntegerVec4	(tcu::ResultCollector& result, glu::CallLogWrapper& gl, glw::GLuint sampler,	glw::GLenum pname,		const tcu::UVec4& expected,	QueryType type);

} // StateQueryUtil
} // gls
} // deqp

#endif // _GLSSTATEQUERYUTIL_HPP