#ifndef _GLSLONGSTRESSCASE_HPP
#define _GLSLONGSTRESSCASE_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 Parametrized, long-running stress case.
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "tcuTestCase.hpp"
#include "tcuTexture.hpp"
#include "tcuMatrix.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderUtil.hpp"
#include "glsTextureTestUtil.hpp"
#include "deRandom.hpp"
#include "deSharedPtr.hpp"

#include <string>
#include <vector>
#include <map>

namespace deqp
{
namespace gls
{

namespace LongStressCaseInternal
{

template <typename T> class GLObjectManager;
class Program;
class Buffer;
class Texture;
class DebugInfoRenderer;

}

struct VarSpec
{
	union Value
	{
		float	f[4*4]; // \note Matrices are stored in column major order.
		int		i[4];
	};

	std::string		name;
	glu::DataType	type;
	Value			minValue;
	Value			maxValue;

	template <typename T>
	VarSpec (const std::string& name_, const T& minValue_, const T& maxValue_)	: name(name_) { set(minValue_, maxValue_); }

	template <typename T>
	VarSpec (const std::string& name_, const T& value)							: name(name_) { set(value, value); }

	void set (float minValue_, float maxValue_)
	{
		type			= glu::TYPE_FLOAT;
		minValue.f[0]	= minValue_;
		maxValue.f[0]	= maxValue_;
	}

	template <int ValSize>
	void set (const tcu::Vector<float, ValSize>& minValue_, const tcu::Vector<float, ValSize>& maxValue_)
	{
		type = glu::getDataTypeFloatVec(ValSize);
		vecToArr(minValue_, minValue.f);
		vecToArr(maxValue_, maxValue.f);
	}

	template <int ValRows, int ValCols>
	void set (const tcu::Matrix<float, ValRows, ValCols>& minValue_, const tcu::Matrix<float, ValRows, ValCols>& maxValue_)
	{
		type = glu::getDataTypeMatrix(ValCols, ValRows);
		matToArr(minValue_, minValue.f);
		matToArr(maxValue_, maxValue.f);
	}

	void set (int minValue_, int maxValue_)
	{
		type			= glu::TYPE_INT;
		minValue.i[0]	= minValue_;
		maxValue.i[0]	= maxValue_;
	}

	template <int ValSize>
	void set (const tcu::Vector<int, ValSize>& minValue_, const tcu::Vector<int, ValSize>& maxValue_)
	{
		type = glu::getDataTypeVector(glu::TYPE_INT, ValSize);
		vecToArr(minValue_, minValue.i);
		vecToArr(maxValue_, maxValue.i);
	}

private:
	template <typename T, int SrcSize, int DstSize>
	static inline void vecToArr (const tcu::Vector<T, SrcSize>& src, T (&dst)[DstSize])
	{
		DE_STATIC_ASSERT(DstSize >= SrcSize);
		for (int i = 0; i < SrcSize; i++)
			dst[i] = src[i];
	}

	template <int ValRows, int ValCols, int DstSize>
	static inline void matToArr (const tcu::Matrix<float, ValRows, ValCols>& src, float (&dst)[DstSize])
	{
		DE_STATIC_ASSERT(DstSize >= ValRows*ValCols);
		tcu::Array<float, ValRows*ValCols> data = src.getColumnMajorData();
		for (int i = 0; i < ValRows*ValCols; i++)
			dst[i] = data[i];
	}
};

struct TextureSpec
{
	gls::TextureTestUtil::TextureType	textureType;
	deUint32							textureUnit;
	int									width;
	int									height;
	deUint32							format;
	deUint32							dataType;
	deUint32							internalFormat;
	bool								useMipmap;
	deUint32							minFilter;
	deUint32							magFilter;
	deUint32							sWrap;
	deUint32							tWrap;
	tcu::Vec4							minValue;
	tcu::Vec4							maxValue;

	TextureSpec (const gls::TextureTestUtil::TextureType	texType,
				 const deUint32								unit,
				 const int									width_,
				 const int									height_,
				 const deUint32								format_,
				 const deUint32								dataType_,
				 const deUint32								internalFormat_,
				 const bool									useMipmap_,
				 const deUint32								minFilter_,
				 const deUint32								magFilter_,
				 const deUint32								sWrap_,
				 const deUint32								tWrap_,
				 const tcu::Vec4&							minValue_,
				 const tcu::Vec4&							maxValue_)
		: textureType		(texType)
		, textureUnit		(unit)
		, width				(width_)
		, height			(height_)
		, format			(format_)
		, dataType			(dataType_)
		, internalFormat	(internalFormat_)
		, useMipmap			(useMipmap_)
		, minFilter			(minFilter_)
		, magFilter			(magFilter_)
		, sWrap				(sWrap_)
		, tWrap				(tWrap_)
		, minValue			(minValue_)
		, maxValue			(maxValue_)
	{
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Struct for a shader program sources and related data
 *
 * A ProgramContext holds a program's vertex and fragment shader sources
 * as well as specifications of its attributes, uniforms, and textures.
 * When given to a StressCase, the string ${NS} is replaced by a magic
 * number that varies between different compilations of the same program;
 * the same replacement is done in attributes' and uniforms' names. This
 * can be used to avoid shader caching by the GL, by e.g. suffixing each
 * attribute, uniform and varying name with ${NS} in the shader source.
 *//*--------------------------------------------------------------------*/
struct ProgramContext
{
	std::string							vertexSource;
	std::string							fragmentSource;
	std::vector<VarSpec>				attributes;
	std::vector<VarSpec>				uniforms;

	std::vector<TextureSpec>			textureSpecs;		//!< \note If multiple textures have same unit, one of them is picked randomly.

	std::string							positionAttrName;	//!< \note Position attribute may get a bit more careful handling than just complete random.

	ProgramContext (const char* const vtxShaderSource_,
					const char* const fragShaderSource_,
					const char* const positionAttrName_)
		: vertexSource		(vtxShaderSource_)
		, fragmentSource	(fragShaderSource_)
		, positionAttrName	(positionAttrName_)
	{
	}
};

class LongStressCase : public tcu::TestCase
{
public:
	//! Probabilities for actions that may be taken on each iteration. \note The texture and buffer specific actions are randomized per texture or buffer.
	struct FeatureProbabilities
	{
		float rebuildProgram;				//!< Rebuild program, with variable name-mangling.
		float reuploadTexture;				//!< Reupload texture, even if it already exists and has been uploaded.
		float reuploadBuffer;				//!< Reupload buffer, even if it already exists and has been uploaded.
		float reuploadWithTexImage;			//!< Use glTexImage*() when re-uploading texture, not glTexSubImage*().
		float reuploadWithBufferData;		//!< Use glBufferData() when re-uploading buffer, not glBufferSubData().
		float deleteTexture;				//!< Delete texture at end of iteration, even if we could re-use it.
		float deleteBuffer;					//!< Delete buffer at end of iteration, even if we could re-use it.
		float wastefulTextureMemoryUsage;	//!< Don't re-use a texture, and don't delete it until given memory limit is hit.
		float wastefulBufferMemoryUsage;	//!< Don't re-use a buffer, and don't delete it until given memory limit is hit.
		float clientMemoryAttributeData;	//!< Use client memory for vertex attribute data when drawing (instead of GL buffers).
		float clientMemoryIndexData;		//!< Use client memory for vertex indices when drawing (instead of GL buffers).
		float randomBufferUploadTarget;		//!< Use a random target when setting buffer data (i.e. not necessarily the one it'll be ultimately bound to).
		float randomBufferUsage;			//!< Use a random buffer usage parameter with glBufferData(), instead of the ones specified as params for the case.
		float useDrawArrays;				//!< Use glDrawArrays() instead of glDrawElements().
		float separateAttributeBuffers;		//!< Give each vertex attribute its own buffer.

		// Named parameter idiom: helpers that can be used when making temporaries, e.g. FeatureProbabilities().pReuploadTexture(1.0f).pReuploadWithTexImage(1.0f)
		FeatureProbabilities& pRebuildProgram				(const float prob) { rebuildProgram					= prob; return *this; }
		FeatureProbabilities& pReuploadTexture				(const float prob) { reuploadTexture				= prob; return *this; }
		FeatureProbabilities& pReuploadBuffer				(const float prob) { reuploadBuffer					= prob; return *this; }
		FeatureProbabilities& pReuploadWithTexImage			(const float prob) { reuploadWithTexImage			= prob; return *this; }
		FeatureProbabilities& pReuploadWithBufferData		(const float prob) { reuploadWithBufferData			= prob; return *this; }
		FeatureProbabilities& pDeleteTexture				(const float prob) { deleteTexture					= prob; return *this; }
		FeatureProbabilities& pDeleteBuffer					(const float prob) { deleteBuffer					= prob; return *this; }
		FeatureProbabilities& pWastefulTextureMemoryUsage	(const float prob) { wastefulTextureMemoryUsage		= prob; return *this; }
		FeatureProbabilities& pWastefulBufferMemoryUsage	(const float prob) { wastefulBufferMemoryUsage		= prob; return *this; }
		FeatureProbabilities& pClientMemoryAttributeData	(const float prob) { clientMemoryAttributeData		= prob; return *this; }
		FeatureProbabilities& pClientMemoryIndexData		(const float prob) { clientMemoryIndexData			= prob; return *this; }
		FeatureProbabilities& pRandomBufferUploadTarget		(const float prob) { randomBufferUploadTarget		= prob; return *this; }
		FeatureProbabilities& pRandomBufferUsage			(const float prob) { randomBufferUsage				= prob; return *this; }
		FeatureProbabilities& pUseDrawArrays				(const float prob) { useDrawArrays					= prob; return *this; }
		FeatureProbabilities& pSeparateAttribBuffers		(const float prob) { separateAttributeBuffers		= prob; return *this; }

		FeatureProbabilities (void)
			: rebuildProgram				(0.0f)
			, reuploadTexture				(0.0f)
			, reuploadBuffer				(0.0f)
			, reuploadWithTexImage			(0.0f)
			, reuploadWithBufferData		(0.0f)
			, deleteTexture					(0.0f)
			, deleteBuffer					(0.0f)
			, wastefulTextureMemoryUsage	(0.0f)
			, wastefulBufferMemoryUsage		(0.0f)
			, clientMemoryAttributeData		(0.0f)
			, clientMemoryIndexData			(0.0f)
			, randomBufferUploadTarget		(0.0f)
			, randomBufferUsage				(0.0f)
			, useDrawArrays					(0.0f)
			, separateAttributeBuffers		(0.0f)
		{
		}
	};

															LongStressCase						(tcu::TestContext&						testCtx,
																								 const glu::RenderContext&				renderCtx,
																								 const char*							name,
																								 const char*							desc,
																								 int									maxTexMemoryUsageBytes, //!< Approximate upper bound on GL texture memory usage.
																								 int									maxBufMemoryUsageBytes, //!< Approximate upper bound on GL buffer memory usage.
																								 int									numDrawCallsPerIteration,
																								 int									numTrianglesPerDrawCall,
																								 const std::vector<ProgramContext>&		programContexts,
																								 const FeatureProbabilities&			probabilities,
																								 deUint32								indexBufferUsage,
																								 deUint32								attrBufferUsage,
																								 int									redundantBufferFactor = 1,
																								 bool									showDebugInfo = false);

															~LongStressCase						(void);

	void													init								(void);
	void													deinit								(void);

	IterateResult											iterate								(void);

private:
															LongStressCase						(const LongStressCase&);
	LongStressCase&											operator=							(const LongStressCase&);

	const glu::RenderContext&								m_renderCtx;
	const int												m_maxTexMemoryUsageBytes;
	const int												m_maxBufMemoryUsageBytes;
	const int												m_numDrawCallsPerIteration;
	const int												m_numTrianglesPerDrawCall;
	const int												m_numVerticesPerDrawCall;
	const std::vector<ProgramContext>						m_programContexts;
	const FeatureProbabilities								m_probabilities;
	const deUint32											m_indexBufferUsage;
	const deUint32											m_attrBufferUsage;
	const int												m_redundantBufferFactor; //!< By what factor we allocate redundant buffers. Default is 1, i.e. no redundancy.
	const bool												m_showDebugInfo;

	const int												m_numIterations;
	const bool												m_isGLES3;

	int														m_currentIteration;
	deUint64												m_startTimeSeconds; //!< Set at beginning of first iteration.
	deUint64												m_lastLogTime;
	int														m_lastLogIteration;
	int														m_currentLogEntryNdx;

	de::Random												m_rnd;
	LongStressCaseInternal::GLObjectManager<
		LongStressCaseInternal::Program>*					m_programs;
	LongStressCaseInternal::GLObjectManager<
		LongStressCaseInternal::Buffer>*					m_buffers;
	LongStressCaseInternal::GLObjectManager<
		LongStressCaseInternal::Texture>*					m_textures;
	std::vector<deUint16>									m_vertexIndices;

	struct ProgramResources
	{
		std::vector<deUint8>							attrDataBuf;
		std::vector<int>								attrDataOffsets;
		std::vector<int>								attrDataSizes;
		std::vector<de::SharedPtr<tcu::TextureLevel> >	dummyTextures;
		std::string										shaderNameManglingSuffix;
	};

	std::vector<ProgramResources>							m_programResources;

	LongStressCaseInternal::DebugInfoRenderer*				m_debugInfoRenderer;
};


} // gls
} // deqp

#endif // _GLSLONGSTRESSCASE_HPP