#ifndef _GLSVERTEXARRAYTESTS_HPP
#define _GLSVERTEXARRAYTESTS_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 Vertex array and buffer tests
 *//*--------------------------------------------------------------------*/

#include "tcuTestCase.hpp"
#include "tcuVector.hpp"
#include "tcuSurface.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "tcuTestLog.hpp"
#include "gluShaderProgram.hpp"
#include "deFloat16.h"
#include "tcuFloat.hpp"
#include "tcuPixelFormat.hpp"
#include "sglrContext.hpp"

namespace sglr
{

class ReferenceContextBuffers;
class ReferenceContext;
class Context;

} // sglr

namespace deqp
{
namespace gls
{

class Array
{
public:
	enum Target
	{
		// \note [mika] Are these actualy used somewhere?
		TARGET_ELEMENT_ARRAY = 0,
		TARGET_ARRAY,

		TARGET_LAST
	};

	enum InputType
	{
		INPUTTYPE_FLOAT = 0,
		INPUTTYPE_FIXED,
		INPUTTYPE_DOUBLE,

		INPUTTYPE_BYTE,
		INPUTTYPE_SHORT,

		INPUTTYPE_UNSIGNED_BYTE,
		INPUTTYPE_UNSIGNED_SHORT,

		INPUTTYPE_INT,
		INPUTTYPE_UNSIGNED_INT,
		INPUTTYPE_HALF,
		INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		INPUTTYPE_INT_2_10_10_10,

		INPUTTYPE_LAST
	};

	enum OutputType
	{
		OUTPUTTYPE_FLOAT = 0,
		OUTPUTTYPE_VEC2,
		OUTPUTTYPE_VEC3,
		OUTPUTTYPE_VEC4,

		OUTPUTTYPE_INT,
		OUTPUTTYPE_UINT,

		OUTPUTTYPE_IVEC2,
		OUTPUTTYPE_IVEC3,
		OUTPUTTYPE_IVEC4,

		OUTPUTTYPE_UVEC2,
		OUTPUTTYPE_UVEC3,
		OUTPUTTYPE_UVEC4,

		OUTPUTTYPE_LAST
	};

	enum Usage
	{
		USAGE_DYNAMIC_DRAW = 0,
		USAGE_STATIC_DRAW,
		USAGE_STREAM_DRAW,

		USAGE_STREAM_READ,
		USAGE_STREAM_COPY,

		USAGE_STATIC_READ,
		USAGE_STATIC_COPY,

		USAGE_DYNAMIC_READ,
		USAGE_DYNAMIC_COPY,

		USAGE_LAST
	};

	enum Storage
	{
		STORAGE_USER = 0,
		STORAGE_BUFFER,

		STORAGE_LAST
	};

	enum Primitive
	{
		PRIMITIVE_POINTS = 0,
		PRIMITIVE_TRIANGLES,
		PRIMITIVE_TRIANGLE_FAN,
		PRIMITIVE_TRIANGLE_STRIP,

		PRIMITIVE_LAST
	};

	static std::string	targetToString		(Target target);
	static std::string	inputTypeToString	(InputType type);
	static std::string	outputTypeToString	(OutputType type);
	static std::string	usageTypeToString	(Usage usage);
	static std::string	storageToString		(Storage storage);
	static std::string	primitiveToString	(Primitive primitive);
	static int			inputTypeSize		(InputType type);

	virtual				~Array				(void) {}
	virtual void		data				(Target target, int size, const char* data, Usage usage) = 0;
	virtual void		subdata				(Target target, int offset, int size, const char* data) = 0;
	virtual void		bind				(int attribNdx, int offset, int size, InputType inType, OutputType outType, bool normalized, int stride) = 0;
	virtual void		unBind				(void) = 0;

	virtual bool		isBound				(void) const = 0;
	virtual int			getComponentCount	(void) const = 0;
	virtual Target		getTarget			(void) const = 0;
	virtual InputType	getInputType		(void) const = 0;
	virtual OutputType	getOutputType		(void) const = 0;
	virtual Storage		getStorageType		(void) const = 0;
	virtual bool		getNormalized		(void) const = 0;
	virtual int			getStride			(void) const = 0;
	virtual int			getAttribNdx		(void) const = 0;
	virtual void		setAttribNdx		(int attribNdx) = 0;
};

class ContextArray : public Array
{
public:
								ContextArray		(Storage storage, sglr::Context& context);
	virtual						~ContextArray		(void);
	virtual void				data				(Target target, int size, const char* data, Usage usage);
	virtual void				subdata				(Target target, int offset, int size, const char* data);
	virtual void				bind				(int attribNdx, int offset, int size, InputType inType, OutputType outType, bool normalized, int stride);
	virtual void				bindIndexArray		(Array::Target storage);
	virtual void				unBind				(void) { m_bound = false; }
	virtual bool				isBound				(void) const { return m_bound; }

	virtual int					getComponentCount	(void) const { return m_componentCount; }
	virtual Array::Target		getTarget			(void) const { return m_target; }
	virtual Array::InputType	getInputType		(void) const { return m_inputType; }
	virtual Array::OutputType	getOutputType		(void) const { return m_outputType; }
	virtual Array::Storage		getStorageType		(void) const { return m_storage; }
	virtual bool				getNormalized		(void) const { return m_normalize; }
	virtual int					getStride			(void) const { return m_stride; }
	virtual int					getAttribNdx		(void) const { return m_attribNdx; }
	virtual void				setAttribNdx		(int attribNdx) { m_attribNdx = attribNdx; }

	void						glBind				(deUint32 loc);
	static deUint32				targetToGL			(Array::Target target);
	static deUint32				usageToGL			(Array::Usage usage);
	static deUint32				inputTypeToGL		(Array::InputType type);
	static std::string			outputTypeToGLType	(Array::OutputType type);
	static deUint32				primitiveToGL		(Array::Primitive primitive);

private:
	Storage						m_storage;
	sglr::Context&				m_ctx;
	deUint32					m_glBuffer;

	bool						m_bound;
	int							m_attribNdx;
	int							m_size;
	char*						m_data;
	int							m_componentCount;
	Array::Target				m_target;
	Array::InputType			m_inputType;
	Array::OutputType			m_outputType;
	bool						m_normalize;
	int							m_stride;
	int							m_offset;
};

class ContextArrayPack
{
public:
								ContextArrayPack	(glu::RenderContext& renderCtx, sglr::Context& drawContext);
	virtual						~ContextArrayPack	(void);
	virtual Array*				getArray			(int i);
	virtual int					getArrayCount		(void);
	virtual	void				newArray			(Array::Storage storage);
	virtual void				render 				(Array::Primitive primitive, int firstVertex, int vertexCount, bool useVao, float coordScale, float colorScale);

	const tcu::Surface&			getSurface			(void) const { return m_screen; }
private:
	void 						updateProgram		(void);
	glu::RenderContext&			m_renderCtx;
	sglr::Context&				m_ctx;

	std::vector<ContextArray*>	m_arrays;
	sglr::ShaderProgram*		m_program;
	tcu::Surface				m_screen;
};

class GLValue
{
public:

	template<class Type>
	class WrappedType
	{
	public:
		static WrappedType<Type>	create			(Type value)							{ WrappedType<Type> v; v.m_value = value; return v; }
		inline Type					getValue		(void) const							{ return m_value; }

		inline WrappedType<Type>	operator+		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value + other.getValue()); }
		inline WrappedType<Type>	operator*		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value * other.getValue()); }
		inline WrappedType<Type>	operator/		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value / other.getValue()); }
		inline WrappedType<Type>	operator-		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create(m_value - other.getValue()); }

		inline WrappedType<Type>&	operator+=		(const WrappedType<Type>& other)		{ m_value += other.getValue(); return *this; }
		inline WrappedType<Type>&	operator*=		(const WrappedType<Type>& other)		{ m_value *= other.getValue(); return *this; }
		inline WrappedType<Type>&	operator/=		(const WrappedType<Type>& other)		{ m_value /= other.getValue(); return *this; }
		inline WrappedType<Type>&	operator-=		(const WrappedType<Type>& other)		{ m_value -= other.getValue(); return *this; }

		inline bool					operator==		(const WrappedType<Type>& other) const	{ return m_value == other.m_value; }
		inline bool					operator!=		(const WrappedType<Type>& other) const	{ return m_value != other.m_value; }
		inline bool					operator<		(const WrappedType<Type>& other) const	{ return m_value < other.m_value; }
		inline bool					operator>		(const WrappedType<Type>& other) const	{ return m_value > other.m_value; }
		inline bool					operator<=		(const WrappedType<Type>& other) const	{ return m_value <= other.m_value; }
		inline bool					operator>=		(const WrappedType<Type>& other) const	{ return m_value >= other.m_value; }

		inline 						operator Type	(void) const							{ return m_value; }
		template<class T>
		inline T					to				(void) const							{ return (T)m_value; }
	private:
		Type	m_value;
	};

	typedef WrappedType<deInt16>	Short;
	typedef WrappedType<deUint16>	Ushort;

	typedef WrappedType<deInt8>		Byte;
	typedef WrappedType<deUint8>	Ubyte;

	typedef WrappedType<float>		Float;
	typedef WrappedType<double>		Double;

	typedef WrappedType<deInt32>	Int;
	typedef WrappedType<deUint32>	Uint;

	class Half
	{
	public:
		static Half			create			(float value)				{ Half h; h.m_value = floatToHalf(value); return h; }
		inline deFloat16	getValue		(void) const				{ return m_value; }

		inline Half			operator+		(const Half& other) const	{ return create(halfToFloat(m_value) + halfToFloat(other.getValue())); }
		inline Half			operator*		(const Half& other) const	{ return create(halfToFloat(m_value) * halfToFloat(other.getValue())); }
		inline Half			operator/		(const Half& other) const	{ return create(halfToFloat(m_value) / halfToFloat(other.getValue())); }
		inline Half			operator-		(const Half& other) const	{ return create(halfToFloat(m_value) - halfToFloat(other.getValue())); }

		inline Half&		operator+=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) + halfToFloat(m_value)); return *this; }
		inline Half&		operator*=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) * halfToFloat(m_value)); return *this; }
		inline Half&		operator/=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) / halfToFloat(m_value)); return *this; }
		inline Half&		operator-=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) - halfToFloat(m_value)); return *this; }

		inline bool			operator==		(const Half& other) const	{ return m_value == other.m_value; }
		inline bool			operator!=		(const Half& other) const	{ return m_value != other.m_value; }
		inline bool			operator<		(const Half& other) const	{ return halfToFloat(m_value) < halfToFloat(other.m_value); }
		inline bool			operator>		(const Half& other) const	{ return halfToFloat(m_value) > halfToFloat(other.m_value); }
		inline bool			operator<=		(const Half& other) const	{ return halfToFloat(m_value) <= halfToFloat(other.m_value); }
		inline bool			operator>=		(const Half& other) const	{ return halfToFloat(m_value) >= halfToFloat(other.m_value); }

		template<class T>
		inline T			to				(void) const				{ return (T)halfToFloat(m_value); }

		inline static deFloat16	floatToHalf		(float f);
		inline static float		halfToFloat		(deFloat16 h);
	private:
		deFloat16 m_value;
	};

	class Fixed
	{
	public:
		static Fixed		create			(deInt32 value)				{ Fixed v; v.m_value = value; return v; }
		inline deInt32		getValue		(void) const				{ return m_value; }

		inline Fixed		operator+		(const Fixed& other) const	{ return create(m_value + other.getValue()); }
		inline Fixed		operator*		(const Fixed& other) const	{ return create(m_value * other.getValue()); }
		inline Fixed		operator/		(const Fixed& other) const	{ return create(m_value / other.getValue()); }
		inline Fixed		operator-		(const Fixed& other) const	{ return create(m_value - other.getValue()); }

		inline Fixed&		operator+=		(const Fixed& other)		{ m_value += other.getValue(); return *this; }
		inline Fixed&		operator*=		(const Fixed& other)		{ m_value *= other.getValue(); return *this; }
		inline Fixed&		operator/=		(const Fixed& other)		{ m_value /= other.getValue(); return *this; }
		inline Fixed&		operator-=		(const Fixed& other)		{ m_value -= other.getValue(); return *this; }

		inline bool			operator==		(const Fixed& other) const	{ return m_value == other.m_value; }
		inline bool			operator!=		(const Fixed& other) const	{ return m_value != other.m_value; }
		inline bool			operator<		(const Fixed& other) const	{ return m_value < other.m_value; }
		inline bool			operator>		(const Fixed& other) const	{ return m_value > other.m_value; }
		inline bool			operator<=		(const Fixed& other) const	{ return m_value <= other.m_value; }
		inline bool			operator>=		(const Fixed& other) const	{ return m_value >= other.m_value; }

		inline 				operator deInt32 (void) const				{ return m_value; }
		template<class T>
		inline T			to				(void) const				{ return (T)m_value; }
	private:
		deInt32				m_value;
	};

	// \todo [mika] This is pretty messy
						GLValue			(void)			: type(Array::INPUTTYPE_LAST) {}
	explicit			GLValue			(Float value)	: type(Array::INPUTTYPE_FLOAT),				fl(value)	{}
	explicit			GLValue			(Fixed value)	: type(Array::INPUTTYPE_FIXED),				fi(value)	{}
	explicit			GLValue			(Byte value)	: type(Array::INPUTTYPE_BYTE),				b(value)	{}
	explicit			GLValue			(Ubyte value)	: type(Array::INPUTTYPE_UNSIGNED_BYTE),		ub(value)	{}
	explicit			GLValue			(Short value)	: type(Array::INPUTTYPE_SHORT),				s(value)	{}
	explicit			GLValue			(Ushort value)	: type(Array::INPUTTYPE_UNSIGNED_SHORT),	us(value)	{}
	explicit			GLValue			(Int value)		: type(Array::INPUTTYPE_INT),				i(value)	{}
	explicit			GLValue			(Uint value)	: type(Array::INPUTTYPE_UNSIGNED_INT),		ui(value)	{}
	explicit			GLValue			(Half value)	: type(Array::INPUTTYPE_HALF),				h(value)	{}
	explicit			GLValue			(Double value)	: type(Array::INPUTTYPE_DOUBLE),			d(value)	{}

	float				toFloat			(void) const;

	static GLValue		getMaxValue		(Array::InputType type);
	static GLValue		getMinValue		(Array::InputType type);

	Array::InputType	type;

	union
	{
		Float		fl;
		Fixed		fi;
		Double		d;
		Byte		b;
		Ubyte		ub;
		Short		s;
		Ushort		us;
		Int			i;
		Uint		ui;
		Half		h;
	};
};

class VertexArrayTest : public tcu::TestCase
{
public:
									VertexArrayTest		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name ,const char* desc);
	virtual							~VertexArrayTest	(void);
	virtual void					init				(void);
	virtual void					deinit				(void);

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

	void							compare				(void);

	glu::RenderContext&				m_renderCtx;

	sglr::ReferenceContextBuffers*	m_refBuffers;
	sglr::ReferenceContext*			m_refContext;
	sglr::Context*					m_glesContext;

	ContextArrayPack*				m_glArrayPack;
	ContextArrayPack*				m_rrArrayPack;
	bool							m_isOk;

	int								m_maxDiffRed;
	int								m_maxDiffGreen;
	int								m_maxDiffBlue;
};

class MultiVertexArrayTest : public VertexArrayTest
{
public:
	class Spec
	{
	public:
		class ArraySpec
		{
		public:
								ArraySpec	(Array::InputType inputType, Array::OutputType outputType, Array::Storage storage, Array::Usage usage, int componetCount, int offset, int stride, bool normalize, GLValue min, GLValue max);

			Array::InputType	inputType;
			Array::OutputType	outputType;
			Array::Storage		storage;
			Array::Usage		usage;
			int 				componentCount;
			int					offset;
			int					stride;
			bool				normalize;
			GLValue				min;
			GLValue				max;
		};

		std::string 			getName		(void) const;
		std::string				getDesc		(void) const;

		Array::Primitive		primitive;
		int						drawCount;			//!<Number of primitives to draw
		int						first;

		std::vector<ArraySpec>	arrays;
	};

							MultiVertexArrayTest	(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const Spec& spec, const char* name, const char* desc);
	virtual					~MultiVertexArrayTest	(void);
	virtual IterateResult	iterate					(void);

private:
	bool					isUnalignedBufferOffsetTest		(void) const;
	bool					isUnalignedBufferStrideTest		(void) const;

	Spec					m_spec;
	int						m_iteration;
};

inline deFloat16 GLValue::Half::floatToHalf (float f)
{
	// No denorm support.
	tcu::Float<deUint16, 5, 10, 15, tcu::FLOAT_HAS_SIGN> v(f);
	DE_ASSERT(!v.isNaN() && !v.isInf());
	return v.bits();
}

inline float GLValue::Half::halfToFloat (deFloat16 h)
{
	return tcu::Float16((deUint16)h).asFloat();
}

} // gls
} // deqp

#endif // _GLSVERTEXARRAYTESTS_HPP