#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 "deMath.h"
#include "tcuDefs.hpp"
#include "tcuTestLog.hpp"

namespace deqp
{
namespace gls
{
namespace StateQueryUtil
{

/*--------------------------------------------------------------------*//*!
 * \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			verifyValidity				(tcu::TestContext& testCtx) const;

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

private:
	enum
	{
		GUARD_VALUE = 0xDEDEADCD
	};
	enum
	{
		WRITE_GUARD_VALUE = 0xDE
	};

	deInt32			m_preguard;
	union
	{
		T			m_value;
		deUint8		m_isWrittenToGuard[sizeof(T)];
	};
	deInt32			m_postguard; // \note guards are not const qualified since the GL implementation might modify them
};

template <typename T>
StateQueryMemoryWriteGuard<T>::StateQueryMemoryWriteGuard (void)
	: m_preguard	((deInt32)(GUARD_VALUE))
	, m_postguard	((deInt32)(GUARD_VALUE))
{
	for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(m_isWrittenToGuard); ++i)
		m_isWrittenToGuard[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 < DE_LENGTH_OF_ARRAY(m_isWrittenToGuard); ++i)
		if (m_isWrittenToGuard[i] != (deUint8)WRITE_GUARD_VALUE)
			return false;
	return true;
}

template <typename T>
bool StateQueryMemoryWriteGuard<T>::isMemoryContaminated () const
{
	return (m_preguard != (deInt32)(GUARD_VALUE)) || (m_postguard != (deInt32)(GUARD_VALUE));
}

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

	if (m_preguard != (deInt32)(GUARD_VALUE))
	{
		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 (m_postguard != (deInt32)(GUARD_VALUE))
	{
		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>
std::ostream& operator<< (std::ostream& str, const StateQueryMemoryWriteGuard<T>& guard)
{
	return str << guard.get();
}

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

#endif // _GLSSTATEQUERYUTIL_HPP