#ifndef _GLSFBOUTIL_HPP
#define _GLSFBOUTIL_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 Utilities for framebuffer objects.
 *//*--------------------------------------------------------------------*/

#include "gluRenderContext.hpp"
#include "gluContextInfo.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "gluTextureUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuDefs.hpp"

#include <map>
#include <set>
#include <vector>
#include <algorithm>
#include <iterator>

namespace deqp
{
namespace gls
{

//! A pair of iterators to present a range.
//! \note This must be POD to allow static initialization.
//! \todo [2013-12-03 lauri] Move this to decpp?
template <typename T>
struct Range
{
	typedef const T*	const_iterator;

	const T*	m_begin;
	const T*	m_end;

	const T*	begin		(void) const { return m_begin; }
	const T*	end			(void) const { return m_end; }
};

#define GLS_ARRAY_RANGE(ARR) { DE_ARRAY_BEGIN(ARR), DE_ARRAY_END(ARR) }

#define GLS_NULL_RANGE { DE_NULL, DE_NULL }


//! A pair type that, unlike stl::pair, is POD so it can be statically initialized.
template <typename T1, typename T2>
struct Pair
{
	typedef	T1	first_type;
	typedef T2	second_type;
	T1			first;
	T2			second;
};

namespace FboUtil
{

//! Configurations for framebuffer objects and their attachments.

class FboVerifier;
class FboBuilder;

typedef deUint32		FormatKey;

#define GLS_UNSIZED_FORMATKEY(FORMAT, TYPE) \
	(deUint32(TYPE) << 16 | deUint32(FORMAT))

typedef Range<FormatKey>	FormatKeys;

struct ImageFormat
{
	glw::GLenum				format;

	//! Type if format is unsized, GL_NONE if sized.
	glw::GLenum				unsizedType;

	bool					operator<		(const ImageFormat& other) const
	{
		return (format < other.format ||
				(format == other.format && unsizedType < other.unsizedType));
	}

	static ImageFormat		none			(void)
	{
		ImageFormat fmt = { GL_NONE, GL_NONE };
		return fmt;
	}
};

std::ostream& operator<< (std::ostream& stream, const ImageFormat& format);

static inline ImageFormat formatKeyInfo(FormatKey key)
{
	ImageFormat fmt = { key & 0xffff, key >> 16 };
	return fmt;
}

enum FormatFlags
{
	ANY_FORMAT			= 0,
	COLOR_RENDERABLE	= 1 << 0,
	DEPTH_RENDERABLE	= 1 << 1,
	STENCIL_RENDERABLE	= 1 << 2,
	RENDERBUFFER_VALID	= 1 << 3,
	TEXTURE_VALID		= 1 << 4,
	REQUIRED_RENDERABLE	= 1 << 5, //< Without this, renderability is allowed, not required.
};

static inline FormatFlags operator|(FormatFlags f1, FormatFlags f2)
{
	return FormatFlags(deUint32(f1) | deUint32(f2));
}

FormatFlags formatFlag(glw::GLenum context);

typedef std::set<ImageFormat> Formats;

class FormatDB
{
public:
	void								addCoreFormat				(ImageFormat format, FormatFlags flags);
	void								addExtensionFormat			(ImageFormat format, FormatFlags flags, const std::set<std::string>& requiredExtensions);

	Formats								getFormats					(FormatFlags requirements) const;
	bool								isKnownFormat				(ImageFormat format) const;
	FormatFlags							getFormatInfo				(ImageFormat format) const;
	std::set<std::set<std::string> >	getFormatFeatureExtensions	(ImageFormat format, FormatFlags requirements) const;

private:
	struct ExtensionInfo
	{
		FormatFlags					flags;
		std::set<std::string>		requiredExtensions;

		bool						operator<			(const ExtensionInfo& other) const;
	};

	typedef std::map<ImageFormat, FormatFlags>					FormatMap;
	typedef std::map<ImageFormat, std::set<ExtensionInfo> >		FormatExtensionMap;

	FormatMap							m_formatFlags;
	FormatExtensionMap					m_formatExtensions;
};

typedef Pair<FormatFlags, FormatKeys>				FormatEntry;
typedef Range<FormatEntry>							FormatEntries;

// \todo [2013-12-20 lauri] It turns out that format properties in extensions
// are actually far too fine-grained for this bundling to be reasonable,
// especially given the syntactic cumbersomeness of static arrays. It's better
// to list each entry separately.

struct FormatExtEntry
{
	const char*									extensions;
	deUint32									flags;
	Range<FormatKey>							formats;
};

typedef Range<FormatExtEntry>						FormatExtEntries;

// Check support for GL_* and DEQP_* extensions
bool				checkExtensionSupport		(const glu::RenderContext& ctx, const std::string& extension);

// Accepts GL_* and DEQP_* extension strings and converts DEQP_* strings to a human readable string
std::string			getExtensionDescription		(const std::string& extensionName);

void				addFormats					(FormatDB& db, FormatEntries stdFmts);
void				addExtFormats				(FormatDB& db, FormatExtEntries extFmts, const glu::RenderContext* ctx);
glu::TransferFormat	transferImageFormat			(const ImageFormat& imgFormat);

namespace config
{

struct Config
{
	virtual						~Config			(void) {};
};

struct Image : public Config
{
	ImageFormat					internalFormat;
	glw::GLsizei				width;
	glw::GLsizei				height;

protected:
								Image			(void)
									: internalFormat	(ImageFormat::none())
									, width				(0)
									, height			(0) {}
};

struct Renderbuffer : public Image
{
						Renderbuffer	(void) : numSamples(0) {}

	glw::GLsizei		numSamples;
};

struct Texture : public Image
{
							Texture			(void) : numLevels(1) {}

	glw::GLint				numLevels;
};

struct TextureFlat : public Texture
{
};

struct Texture2D : public TextureFlat
{
};

struct TextureCubeMap : public TextureFlat
{
};

struct TextureLayered : public Texture
{
							TextureLayered	(void) : numLayers(1) {}
	glw::GLsizei			numLayers;
};

struct Texture3D : public TextureLayered
{
};

struct Texture2DArray : public TextureLayered
{
};

struct Attachment : public Config
{
							Attachment		(void) : target(GL_FRAMEBUFFER), imageName(0) {}

	glw::GLenum				target;
	glw::GLuint				imageName;

	//! Returns `true` iff this attachment is "framebuffer attachment
	//! complete" when bound to attachment point `attPoint`, and the current
	//! image with name `imageName` is `image`, using `vfr` to check format
	//! renderability.
	bool					isComplete		(glw::GLenum attPoint, const Image* image,
											 const FboVerifier& vfr) const;
};

struct RenderbufferAttachment : public Attachment
{
				RenderbufferAttachment	(void)
				: renderbufferTarget(GL_RENDERBUFFER) {}

	glw::GLenum renderbufferTarget;
};

struct TextureAttachment : public Attachment
{
							TextureAttachment	(void) : level(0) {}

	glw::GLint				level;
};

struct TextureFlatAttachment : public TextureAttachment
{
							TextureFlatAttachment (void) : texTarget(GL_NONE) {}

	glw::GLenum				texTarget;
};

struct TextureLayerAttachment : public TextureAttachment
{
							TextureLayerAttachment (void) : layer(0) {}

	glw::GLsizei			layer;
};

glw::GLenum		attachmentType	(const Attachment& att);
glw::GLsizei	imageNumSamples	(const Image& img);

//! Mapping from attachment points to attachment configurations.
typedef std::map<glw::GLenum, const Attachment*> AttachmentMap;

//! Mapping from object names to texture configurations.
typedef std::map<glw::GLuint, const Texture*> TextureMap;

//! Mapping from object names to renderbuffer configurations.
typedef std::map<glw::GLuint, const Renderbuffer*> RboMap;

//! A framebuffer configuration.
struct Framebuffer
{
	AttachmentMap			attachments;
	TextureMap				textures;
	RboMap					rbos;

	void					attach			(glw::GLenum attPoint, const Attachment* att);
	void					setTexture		(glw::GLuint texName, const Texture& texCfg);
	void					setRbo			(glw::GLuint rbName, const Renderbuffer& rbCfg);
	const Image*			getImage		(glw::GLenum type, glw::GLuint imgName) const;
};

} // config

class FboBuilder : public config::Framebuffer
{
public:
	void						glAttach		(glw::GLenum attPoint,
												 const config::Attachment* att);
	glw::GLuint					glCreateTexture	(const config::Texture& texCfg);
	glw::GLuint					glCreateRbo		(const config::Renderbuffer& rbCfg);
								FboBuilder		(glw::GLuint fbo, glw::GLenum target,
												 const glw::Functions& gl);
								~FboBuilder		(void);
	glw::GLenum					getError		(void) { return m_error; }

	//! Allocate a new configuration of type `Config` (which must be a
	//! subclass of `config::Config`), and return a referenc to it. The newly
	//! allocated object will be freed when this builder object is destroyed.
	template<typename Config>
	Config&						makeConfig		(void)
	{
		Config* cfg = new Config();
		m_configs.insert(cfg);
		return *cfg;
	}

private:
	typedef std::set<config::Config*> Configs;

	void						checkError		(void);

	glw::GLenum					m_error;		//< The first GL error encountered.
	glw::GLenum					m_target;
	const glw::Functions&		m_gl;
	Configs						m_configs;
};

struct ValidStatusCodes
{
								ValidStatusCodes		(void);

	bool						isFBOStatusValid		(glw::GLenum fboStatus) const;
	bool						isFBOStatusRequired		(glw::GLenum fboStatus) const;
	bool						isErrorCodeValid		(glw::GLenum errorCode) const;
	bool						isErrorCodeRequired		(glw::GLenum errorCode) const;

	void						addErrorCode			(glw::GLenum error, const char* description);
	void						addFBOErrorStatus		(glw::GLenum status, const char* description);
	void						setAllowComplete		(bool);

	void						logLegalResults			(tcu::TestLog& log) const;
	void						logRules				(tcu::TestLog& log) const;

private:
	struct RuleViolation
	{
		glw::GLenum				errorCode;
		std::set<std::string>	rules;
	};

	void						logRule					(tcu::TestLog& log, const std::string& ruleName, const std::set<std::string>& rules) const;
	void						addViolation			(std::vector<RuleViolation>& dst, glw::GLenum code, const char* description) const;

	std::vector<RuleViolation>	m_errorCodes;			//!< Allowed GL errors, GL_NO_ERROR is not allowed
	std::vector<RuleViolation>	m_errorStatuses;		//!< Allowed FBO error statuses, GL_FRAMEBUFFER_COMPLETE is not allowed
	bool						m_allowComplete;		//!< true if (GL_NO_ERROR && GL_FRAMEBUFFER_COMPLETE) is allowed
};

void logFramebufferConfig (const config::Framebuffer& cfg, tcu::TestLog& log);

class Checker
{
public:
								Checker					(const glu::RenderContext&);
	virtual						~Checker				(void) {}

	void						addGLError				(glw::GLenum error, const char* description);
	void						addPotentialGLError		(glw::GLenum error, const char* description);
	void						addFBOStatus			(glw::GLenum status, const char* description);
	void						addPotentialFBOStatus	(glw::GLenum status, const char* description);

	ValidStatusCodes			getStatusCodes			(void) { return m_statusCodes; }

	virtual void				check					(glw::GLenum				attPoint,
														 const config::Attachment&	att,
														 const config::Image*		image) = 0;

protected:
	const glu::RenderContext&	m_renderCtx;

private:
	ValidStatusCodes			m_statusCodes;	//< Allowed return values for glCheckFramebufferStatus.
};

class CheckerFactory
{
public:
	virtual Checker*	createChecker	(const glu::RenderContext&) = 0;
};

typedef std::set<glw::GLenum> AttachmentPoints;
typedef std::set<ImageFormat> Formats;

class FboVerifier
{
public:
								FboVerifier				(const FormatDB&			formats,
														 CheckerFactory&			factory,
														 const glu::RenderContext&	renderCtx);

	ValidStatusCodes			validStatusCodes		(const config::Framebuffer& cfg) const;

private:
	const FormatDB&				m_formats;
	CheckerFactory&				m_factory;
	const glu::RenderContext&	m_renderCtx;
};

} // FboUtil
} // gls
} // deqp

#endif // _GLSFBOUTIL_HPP