#ifndef _GLSLIFETIMETESTS_HPP
#define _GLSLIFETIMETESTS_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 Common object lifetime tests.
 *//*--------------------------------------------------------------------*/

#include "deRandom.hpp"
#include "deUniquePtr.hpp"
#include "tcuSurface.hpp"
#include "tcuTestCase.hpp"
#include "tcuTestContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluRenderContext.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <vector>

namespace deqp
{
namespace gls
{
namespace LifetimeTests
{
namespace details
{

using std::vector;
using de::MovePtr;
using de::Random;
using tcu::Surface;
using tcu::TestCaseGroup;
using tcu::TestContext;
using tcu::TestLog;
using glu::CallLogWrapper;
using glu::RenderContext;
using namespace glw;

typedef void		(CallLogWrapper::*BindFunc)		(GLenum target, GLuint name);
typedef void		(CallLogWrapper::*GenFunc)		(GLsizei n, GLuint* names);
typedef void		(CallLogWrapper::*DeleteFunc)	(GLsizei n, const GLuint* names);
typedef GLboolean	(CallLogWrapper::*ExistsFunc)	(GLuint name);

class Context
{
public:
							Context				(const RenderContext& renderCtx,
												 TestContext& testCtx)
								: m_renderCtx	(renderCtx)
								, m_testCtx		(testCtx) {}
	const RenderContext&	getRenderContext	(void) const { return m_renderCtx; }
	TestContext&			getTestContext		(void) const { return m_testCtx; }
	const Functions&		gl					(void) const { return m_renderCtx.getFunctions(); }
	TestLog&				log					(void) const { return m_testCtx.getLog(); }

private:
	const RenderContext&	m_renderCtx;
	TestContext&			m_testCtx;
};

class ContextWrapper : public CallLogWrapper
{
public:
	const Context&			getContext			(void) const { return m_ctx; }
	const RenderContext&	getRenderContext	(void) const { return m_ctx.getRenderContext(); }
	TestContext&			getTestContext		(void) const { return m_ctx.getTestContext(); }
	const Functions&		gl					(void) const { return m_ctx.gl(); }
	TestLog&				log					(void) const { return m_ctx.log(); }
	void					enableLogging		(bool enable)
	{
		CallLogWrapper::enableLogging(enable);
	}

protected:
							ContextWrapper				(const Context& ctx);
	const Context			m_ctx;
};

class Binder : public ContextWrapper
{
public:
	virtual				~Binder			(void) {}
	virtual void		bind			(GLuint name) = 0;
	virtual GLuint		getBinding		(void) = 0;
	virtual bool		genRequired		(void) const { return true; }

protected:
						Binder			(const Context& ctx) : ContextWrapper(ctx) {}
};

class SimpleBinder : public Binder
{
public:
						SimpleBinder	(const Context& ctx,
										 BindFunc bindFunc,
										 GLenum bindTarget,
										 GLenum bindingParam,
										 bool genRequired_ = false)
							: Binder			(ctx)
							, m_bindFunc		(bindFunc)
							, m_bindTarget		(bindTarget)
							, m_bindingParam	(bindingParam)
							, m_genRequired		(genRequired_) {}

	void				bind			(GLuint name);
	GLuint				getBinding		(void);
	bool				genRequired		(void) const { return m_genRequired; }

private:
	const BindFunc		m_bindFunc;
	const GLenum		m_bindTarget;
	const GLenum		m_bindingParam;
	const bool			m_genRequired;
};

class Type : public ContextWrapper
{
public:
	virtual					~Type			(void) {}
	virtual GLuint			gen				(void) = 0;
	virtual void			release			(GLuint name) = 0;
	virtual bool			exists			(GLuint name) = 0;
	virtual bool			isDeleteFlagged	(GLuint name) { DE_UNREF(name); return false; }
	virtual Binder*			binder			(void) const { return DE_NULL; }
	virtual const char*		getName			(void) const = 0;
	virtual bool			nameLingers		(void) const { return false; }
	virtual bool			genCreates		(void) const { return false; }

protected:
							Type			(const Context& ctx) : ContextWrapper(ctx) {}
};

class SimpleType : public Type
{
public:
				SimpleType	(const Context& ctx, const char* name,
							 GenFunc genFunc, DeleteFunc deleteFunc, ExistsFunc existsFunc,
							 Binder* binder_ = DE_NULL, bool genCreates_ = false)
						: Type			(ctx)
						, m_getName		(name)
						, m_genFunc		(genFunc)
						, m_deleteFunc	(deleteFunc)
						, m_existsFunc	(existsFunc)
						, m_binder		(binder_)
						, m_genCreates	(genCreates_) {}

	GLuint			gen			(void);
	void			release		(GLuint name)		{ (this->*m_deleteFunc)(1, &name); }
	bool			exists		(GLuint name)		{ return (this->*m_existsFunc)(name) != GL_FALSE; }
	Binder*			binder		(void) const		{ return m_binder; }
	const char*		getName		(void) const		{ return m_getName; }
	bool			nameLingers	(void) const		{ return false; }
	bool			genCreates	(void) const		{ return m_genCreates; }

private:
	const char* const	m_getName;
	const GenFunc		m_genFunc;
	const DeleteFunc	m_deleteFunc;
	const ExistsFunc	m_existsFunc;
	Binder* const		m_binder;
	const bool			m_genCreates;
};

class ProgramType : public Type
{
public:
					ProgramType		(const Context& ctx) : Type(ctx) {}
	bool			nameLingers		(void) const	{ return true; }
	bool			genCreates		(void) const	{ return true; }
	const char*		getName			(void) const	{ return "program"; }
	GLuint			gen				(void)			{ return glCreateProgram(); }
	void			release			(GLuint name)	{ glDeleteProgram(name); }
	bool			exists			(GLuint name)	{ return glIsProgram(name) != GL_FALSE; }
	bool			isDeleteFlagged	(GLuint name);
};

class ShaderType : public Type
{
public:
					ShaderType		(const Context& ctx) : Type(ctx) {}
	bool			nameLingers		(void) const { return true; }
	bool			genCreates		(void) const { return true; }
	const char*		getName			(void) const { return "shader"; }
	GLuint			gen				(void) { return glCreateShader(GL_FRAGMENT_SHADER); }
	void			release			(GLuint name) { glDeleteShader(name); }
	bool			exists			(GLuint name) { return glIsShader(name) != GL_FALSE; }
	bool			isDeleteFlagged	(GLuint name);
};

class Attacher : public ContextWrapper
{
public:
	virtual void		initAttachment			(GLuint seed, GLuint attachment) = 0;
	virtual void		attach					(GLuint element, GLuint container) = 0;
	virtual void		detach					(GLuint element, GLuint container) = 0;
	virtual GLuint		getAttachment			(GLuint container) = 0;
	virtual bool		canAttachDeleted		(void) const { return true; }

	Type&				getElementType			(void) const { return m_elementType; }
	Type&				getContainerType		(void) const { return m_containerType; }
	virtual				~Attacher				(void) {}

protected:
						Attacher				(const Context& ctx,
												 Type& elementType, Type& containerType)
							: ContextWrapper	(ctx)
							, m_elementType		(elementType)
							, m_containerType	(containerType) {}

private:
	Type&				m_elementType;
	Type&				m_containerType;
};

class InputAttacher : public ContextWrapper
{
public:
	Attacher&			getAttacher				(void) const { return m_attacher; }
	virtual void		drawContainer			(GLuint container, Surface& dst) = 0;
protected:
						InputAttacher			(Attacher& attacher)
							: ContextWrapper	(attacher.getContext())
							, m_attacher		(attacher) {}
	Attacher&			m_attacher;
};

class OutputAttacher : public ContextWrapper
{
public:
	Attacher&			getAttacher				(void) const { return m_attacher; }
	virtual void		setupContainer			(GLuint seed, GLuint container) = 0;
	virtual void		drawAttachment			(GLuint attachment, Surface& dst) = 0;
protected:
						OutputAttacher			(Attacher& attacher)
							: ContextWrapper	(attacher.getContext())
							, m_attacher		(attacher) {}
	Attacher&			m_attacher;
};

class Types : public ContextWrapper
{
public:
									Types				(const Context& ctx)
										: ContextWrapper(ctx) {}
	virtual Type&					getProgramType		(void) = 0;
	const vector<Type*>&			getTypes			(void) { return m_types; }
	const vector<Attacher*>&		getAttachers		(void) { return m_attachers; }
	const vector<InputAttacher*>&	getInputAttachers	(void) { return m_inAttachers; }
	const vector<OutputAttacher*>&	getOutputAttachers	(void) { return m_outAttachers; }
	virtual							~Types				(void) {}

protected:
	vector<Type*>					m_types;
	vector<Attacher*>				m_attachers;
	vector<InputAttacher*>			m_inAttachers;
	vector<OutputAttacher*>			m_outAttachers;
};

class FboAttacher : public Attacher
{
public:
	void			initAttachment		(GLuint seed, GLuint element);

protected:
					FboAttacher			(const Context& ctx,
										 Type& elementType, Type& containerType)
						: Attacher		(ctx, elementType, containerType) {}
	virtual void	initStorage			(void) = 0;
};

class FboInputAttacher : public InputAttacher
{
public:
			FboInputAttacher		(FboAttacher& attacher)
				: InputAttacher		(attacher) {}
	void	drawContainer			(GLuint container, Surface& dst);
};

class FboOutputAttacher : public OutputAttacher
{
public:
			FboOutputAttacher			(FboAttacher& attacher)
				: OutputAttacher		(attacher) {}
	void	setupContainer				(GLuint seed, GLuint container);
	void	drawAttachment				(GLuint attachment, Surface& dst);
};

class TextureFboAttacher : public FboAttacher
{
public:
			TextureFboAttacher	(const Context& ctx, Type& elementType, Type& containerType)
				: FboAttacher	(ctx, elementType, containerType) {}

	void	initStorage			(void);
	void	attach				(GLuint element, GLuint container);
	void	detach				(GLuint element, GLuint container);
	GLuint	getAttachment		(GLuint container);
};

class RboFboAttacher : public FboAttacher
{
public:
			RboFboAttacher		(const Context& ctx, Type& elementType, Type& containerType)
				: FboAttacher	(ctx, elementType, containerType) {}

	void	initStorage			(void);
	void	attach				(GLuint element, GLuint container);
	void	detach				(GLuint element, GLuint container);
	GLuint	getAttachment		(GLuint container);
};

class ShaderProgramAttacher : public Attacher
{
public:
			ShaderProgramAttacher	(const Context& ctx,
									 Type& elementType, Type& containerType)
				: Attacher			(ctx, elementType, containerType) {}

	void	initAttachment		(GLuint seed, GLuint element);
	void	attach				(GLuint element, GLuint container);
	void	detach				(GLuint element, GLuint container);
	GLuint	getAttachment		(GLuint container);
};

class ShaderProgramInputAttacher : public InputAttacher
{
public:
			ShaderProgramInputAttacher	(Attacher& attacher)
				: InputAttacher			(attacher) {}

	void	drawContainer				(GLuint container, Surface& dst);
};

class ES2Types : public Types
{
public:
								ES2Types		(const Context& ctx);
	Type&						getProgramType	(void) { return m_programType; }

protected:
	SimpleBinder				m_bufferBind;
	SimpleType					m_bufferType;
	SimpleBinder				m_textureBind;
	SimpleType					m_textureType;
	SimpleBinder				m_rboBind;
	SimpleType					m_rboType;
	SimpleBinder				m_fboBind;
	SimpleType					m_fboType;
	ShaderType					m_shaderType;
	ProgramType					m_programType;
	TextureFboAttacher			m_texFboAtt;
	FboInputAttacher			m_texFboInAtt;
	FboOutputAttacher			m_texFboOutAtt;
	RboFboAttacher				m_rboFboAtt;
	FboInputAttacher			m_rboFboInAtt;
	FboOutputAttacher			m_rboFboOutAtt;
	ShaderProgramAttacher		m_shaderAtt;
	ShaderProgramInputAttacher	m_shaderInAtt;
};

MovePtr<TestCaseGroup>	createGroup		(TestContext& testCtx, Type& type);
void					addTestCases	(TestCaseGroup& group, Types& types);

struct Rectangle
{
			Rectangle	(GLint x_, GLint y_, GLint width_, GLint height_)
				: x			(x_)
				, y			(y_)
				, width		(width_)
				, height	(height_) {}
	GLint	x;
	GLint	y;
	GLint	width;
	GLint	height;
};

Rectangle	randomViewport	(const RenderContext& ctx, GLint maxWidth, GLint maxHeight,
							 Random& rnd);
void		setViewport		(const RenderContext& renderCtx, const Rectangle& rect);
void		readRectangle	(const RenderContext& renderCtx, const Rectangle& rect,
							 Surface& dst);

} // details

using details::BindFunc;
using details::GenFunc;
using details::DeleteFunc;
using details::ExistsFunc;

using details::Context;
using details::Binder;
using details::SimpleBinder;
using details::Type;
using details::SimpleType;
using details::Attacher;
using details::InputAttacher;
using details::OutputAttacher;
using details::Types;
using details::ES2Types;

using details::createGroup;
using details::addTestCases;

using details::Rectangle;
using details::randomViewport;
using details::setViewport;
using details::readRectangle;

} // LifetimeTests
} // gls
} // deqp

#endif // _GLSLIFETIMETESTS_HPP