/*
								+--------------------------------+
								|                                |
								|     ***   OpenGL 4.0   ***     |
								|                                |
								|  Copyright © -tHE SWINe- 2009  |
								|                                |
								|           Texture.h            |
								|                                |
								+--------------------------------+
*/

#pragma once
#ifndef __TEXTURE_MANAGER4_INCLUDED
#define __TEXTURE_MANAGER4_INCLUDED

/**
 *	@file gles2/Texture.h
 *	@author -tHE SWINe-
 *	@date 2009
 *	@brief OpenGL 4.0 textures
 *
 *	@todo Write documentation for this, make sure all useful extensions are implemented.
 *	@todo Implement GL_ARB_texture_multisample, GL_ARB_texture_cube_map_array, GL_EXT_texture_buffer_object.
 *
 *	Done:
 *		- Implement GL_EXT_texture_array.
 *
 *	@date 2012-06-19
 *
 *	Added \#pragma once.
 */

#ifdef __HAVE_GLES
#include "GLES20.h"
#else // __HAVE_GLES
#include "GLES20Emu.h"
#endif // __HAVE_GLES
#include "../Unused.h"

/**
 *	@def __GL_TEXTURE_STORE_INTERNAL_FORMAT
 *	@brief if defined, all texture classes (derived from CGLTexture) store internal
 *		texture image format in a member variable; otherwise CGLTexture::n_Internal_Format()
 *		is not available
 */
//#define __GL_TEXTURE_STORE_INTERNAL_FORMAT

/**
 *	@def b_IsTexture1D
 *	@brief determines whether a given texture target specifies a 1D texture
 *	@param[in] n_tex_target is OpenGL texture target name
 */
#define b_IsTexture1D(n_tex_target) (false/*(n_tex_target) == GL_TEXTURE_1D*/)

/**
 *	@def b_IsTextureCubeFace
 *	@brief determines whether a given texture target specifies a cube-map texture face
 *	@param[in] n_tex_target is OpenGL texture target name
 */
#define b_IsTextureCubeFace(n_tex_target) ( \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_NEGATIVE_X || \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_POSITIVE_X || \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y || \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_POSITIVE_Y || \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z || \
	(n_tex_target) == GL_TEXTURE_CUBE_MAP_POSITIVE_Z)

/**
 *	@def b_IsTextureFace
 *	@brief determines whether a given texture target specifies a texture face
 *	@param[in] n_tex_target is OpenGL texture target name
 *	@note At the moment, only cube map textures do have faces.
 *		This is here for future extensibility only.
 */
#define b_IsTextureFace(n_tex_target) (b_IsTextureCubeFace(n_tex_target))

/**
 *	@def b_IsTexture2D
 *	@brief determines whether a given texture target specifies a 2D texture
 *	@param[in] n_tex_target is OpenGL texture target name
 */
#define b_IsTexture2D(n_tex_target) ((n_tex_target) == GL_TEXTURE_2D || \
	/*(n_tex_target) == GL_TEXTURE_1D_ARRAY_EXT  ||*/ \
	/*(n_tex_target) == GL_TEXTURE_RECTANGLE ||*/ b_IsTextureCubeFace(n_tex_target))

/**
 *	@def b_IsTexture3D
 *	@brief determines whether a given texture target specifies a 3D texture
 *	@param[in] n_tex_target is OpenGL texture target name
 */
#define b_IsTexture3D(n_tex_target) ((n_tex_target) == GL_TEXTURE_3D_OES  || \
	(n_tex_target) == GL_TEXTURE_2D_ARRAY_EXT )

/**
 *	@brief base class for OpenGL texture
 */
class CGLTexture {
protected:
	GLuint m_n_id;
#ifdef __GL_TEXTURE_STORE_INTERNAL_FORMAT
	GLenum m_n_internal_format; // GL_RGB8
#endif // __GL_TEXTURE_STORE_INTERNAL_FORMAT
	GLenum m_n_target; // GL_TEXTURE_2D

public:
	/**
	 *	@brief default constructor; stores the texture parameters only
	 *
	 *	@param[in] n_internal_format is internal format name
	 *	@param[in] n_target is texture target
	 *
	 *	@note n_internal_format is only stored if __GL_TEXTURE_STORE_INTERNAL_FORMAT is defined
	 *		(the parameter is retained for implementation simplicity).
	 */
	CGLTexture(GLenum n_internal_format, GLenum n_target);

	/**
	 *	@brief destructor; deletes OpenGL texture object in case it was created
	 */
	~CGLTexture();

	/**
	 *	@brief gets texture status
	 *	@return Returns true in case texture was successfuly created
	 *		and can be used for texturing, otherwise returns false.
	 */
	bool b_Status() const;

	/**
	 *	@brief binds the texture to the current texturing unit
	 */
	inline void Bind() const
	{
		glBindTexture(m_n_target, m_n_id);
	}

	/**
	 *	@brief makes the specified texturing unit active and binds the texture to it
	 *	@param[in] n_texture_unit is name of the terget texturing unit (eg. GL_TEXTURE0)
	 */
	inline void Bind(GLenum n_texture_unit) const
	{
		glActiveTexture(n_texture_unit);
		glBindTexture(m_n_target, m_n_id);
	}

#if 0
	/**
	 *	@brief binds the texture to the specified texturing unit (without making it active)
	 *	@param[in] n_texture_unit is name of the terget texturing unit (eg. GL_TEXTURE0)
	 *	@note This requires the GL_EXT_direct_state_access extension.
	 */
	inline void BindTo(GLenum n_texture_unit) const
	{
		_ASSERTE(GLEH_EXT_direct_state_access); // the extension must be present
		glBindMultiTextureEXT(n_texture_unit, m_n_target, m_n_id);
	}
#endif // 0

	/**
	 *	@brief gets texture target
	 *	@return Returns texture target (eg. GL_TEXTURE_2D).
	 */
	inline GLenum n_Target() const
	{
		return m_n_target;
	}

#ifdef __GL_TEXTURE_STORE_INTERNAL_FORMAT
	/**
	 *	@brief gets texture internal format
	 *
	 *	@return Returns the stored texture internal format.
	 *
	 *	@note This function is only available if __GL_TEXTURE_STORE_INTERNAL_FORMAT is defined.
	 *	@note This assumes all the texture levels have the same format
	 *		and that the format did not change by texture image respecification.
	 */
	inline GLenum n_Internal_Format() const
	{
		return m_n_internal_format;

		/*int n_internal_format;
		glGetTextureParameterivEXT(m_n_id, m_n_target, GL_TEXTURE_INTERNAL_FORMAT, &n_internal_format);
		return n_internal_format;*/
		// doesn't work; GL_TEXTURE_INTERNAL_FORMAT is not a valid texture parameter target

		/*int n_internal_format;
		glGetTexLevelParameteriv(m_n_target, 0, GL_TEXTURE_INTERNAL_FORMAT, &n_internal_format);
		return n_internal_format;*/
		// does work, at least for level 0 but disturbs the state

		/*int n_internal_format;
		glGetTextureLevelParameterivEXT(m_n_id, m_n_target, 0, GL_TEXTURE_INTERNAL_FORMAT, &n_internal_format);
		return n_internal_format;*/
		// does work, at least for level 0 but requires GL_EXT_direct_state_access
	}
#endif // __GL_TEXTURE_STORE_INTERNAL_FORMAT

	// texture parameter getters; texture must be bound to use them
	GLenum n_Get_Wrap_S() const;
	GLenum n_Get_Wrap_T() const;
	//GLenum n_Get_Wrap_R() const;
	GLenum n_Get_Min_Filter() const;
	GLenum n_Get_Mag_Filter() const;
	/*float f_Get_Min_LOD() const;
	float f_Get_Max_LOD() const;
	int n_Get_Base_Level() const;
	int n_Get_Max_Level() const;
	GLenum n_Get_Compare_Mode() const;
	GLenum n_Get_Compare_Func() const;
	float f_Get_Anisotropy() const;*/

	// texture parameter setters; texture must be bound to use them
	// note those involve calling glGetIntegerv() / glGetFloatv(),
	// and possibly unwanted GPU / CPU synchronisation
	void Set_Wrap_S(GLenum n_wrap_mode) const;
	void Set_Wrap_T(GLenum n_wrap_mode) const;
	//void Set_Wrap_R(GLenum n_wrap_mode) const;
	void Set_Min_Filter(GLenum n_minify_filter) const;
	void Set_Mag_Filter(GLenum n_magnify_filter) const;
	/*void Set_Min_LOD(float f_min_lod) const;
	void Set_Max_LOD(float f_max_lod) const;
	void Set_Base_Level(int n_base_level) const;
	void Set_Max_Level(int n_max_level) const;
	void Set_Compare_Mode(GLenum n_compare_mode) const;
	void Set_Compare_Func(GLenum n_compare_func) const;
	void Set_Anisotropy(float f_anisotropy) const;*/

	/**
	 *	@brief gets maximal texture size
	 *
	 *	@return Returns maximal texture size.
	 *
	 *	@note This involves calling glGetIntegerv(), and possibly unwanted GPU / CPU synchronisation.
	 */
	static int n_Max_Size();

	/**
	 *	@brief gets maximal texture anisotropy
	 *
	 *	@return Returns maximal texture anisotropy.
	 *
	 *	@note This involves calling glGetFloatv(), and possibly unwanted GPU / CPU synchronisation.
	 */
	//static float f_Max_Anisotropy(); // not in OpenGL ES (yet)

	inline void FramebufferTexture(GLenum n_target, GLenum n_attachment, int n_level) const
	{
		_ASSERTE(!b_IsTexture1D(m_n_target)); // 1D textures not supported at the moment
		/*if(b_IsTexture1D(m_n_target)) // evaluates to compile-time false
			glFramebufferTexture1D(n_target, n_attachment, m_n_target, m_n_id, n_level);
		else*/
			glFramebufferTexture2D(n_target, n_attachment, m_n_target, m_n_id, n_level);
	}

	inline void FramebufferTextureLayer(GLenum n_target,
		GLenum n_attachment, int n_level, int n_layer) const
	{
		glFramebufferTextureLayerEXT(n_target, n_attachment, m_n_id, n_level, n_layer);
	}

	inline void FramebufferTextureFace(GLenum n_target,
		GLenum n_attachment, int n_level, GLenum n_face) const
	{
		glFramebufferTexture2D(n_target, n_attachment, n_face, m_n_id, n_level);
	}

private:
	CGLTexture(const CGLTexture &r_tex); /**< @brief copy-constructor (cannot be used) */
	CGLTexture &operator =(const CGLTexture &r_tex); /**< @brief copy-operator (cannot be used) */
};

class CGLTexture_1D_Base : public CGLTexture {
public:
	inline CGLTexture_1D_Base(GLenum n_internal_format, GLenum n_target)
		:CGLTexture(n_internal_format, n_target)
	{
		_ASSERTE(b_IsTexture1D(n_target));
	}

#if 0
	inline void FramebufferTexture1D(GLenum n_target = GL_DRAW_FRAMEBUFFER,
		GLenum n_attachment = GL_COLOR_ATTACHMENT0, int n_level = 0) const
	{
		glFramebufferTexture1D(n_target, n_attachment, m_n_target, m_n_id, n_level);
	}

	inline void FramebufferTexture1D(GLenum n_target, GLenum n_attachment,
		GLenum n_tex_target, int n_level) const
	{
		glFramebufferTexture1D(n_target, n_attachment, n_tex_target, m_n_id, n_level);
	}
#endif // 0
};

class CGLTexture_2D_Base : public CGLTexture {
public:
	inline CGLTexture_2D_Base(GLenum n_internal_format, GLenum n_target)
		:CGLTexture(n_internal_format, n_target)
	{
		_ASSERTE(b_IsTexture2D(n_target) && !b_IsTextureCubeFace(n_target));
	}

	inline void FramebufferTexture2D(GLenum n_target = GL_FRAMEBUFFER,
		GLenum n_attachment = GL_COLOR_ATTACHMENT0, int n_level = 0) const
	{
		glFramebufferTexture2D(n_target, n_attachment, m_n_target, m_n_id, n_level);
	}

	inline void FramebufferTexture2D(GLenum n_target, GLenum n_attachment,
		GLenum n_tex_target, int n_level) const
	{
		glFramebufferTexture2D(n_target, n_attachment, n_tex_target, m_n_id, n_level);
	}
};

class CGLTexture_3D_Base : public CGLTexture {
public:
	inline CGLTexture_3D_Base(GLenum n_internal_format, GLenum n_target)
		:CGLTexture(n_internal_format, n_target)
	{
		_ASSERTE(b_IsTexture3D(n_target));
	}

	inline void FramebufferTexture3D(GLenum n_target,
		GLenum n_attachment, int n_level, int n_layer) const
	{
		glFramebufferTexture3DOES(n_target, n_attachment, m_n_target, m_n_id, n_level, n_layer);
	}

	inline void FramebufferTexture2D(GLenum n_target, GLenum n_attachment,
		GLenum n_tex_target, int n_level, int n_layer) const
	{
		glFramebufferTexture3DOES(n_target, n_attachment, n_tex_target, m_n_id, n_level, n_layer);
	}
};

#if 0
class CGLTexture_1D : public CGLTexture_1D_Base {
protected:
	int m_n_width;

public:
	/*
	 *	CGLTexture_1D::CGLTexture_1D(CGLState *p_state, int n_width, GLenum n_internal_format,
	 *		bool b_create_mipmaps = true, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates one-dimensional texture of size n_width and color format n_internal format
	 *		  with border n_border_width pixels wide
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_1D(int n_width, GLenum n_internal_format,
		bool b_create_mipmaps = true,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
};
#endif // 0

class CGLTexture_2D : public CGLTexture_2D_Base {
protected:
	int m_n_width;
	int m_n_height;

public:
	/*
	 *	CGLTexture_2D::CGLTexture_2D(CGLState *p_state, int n_width, int n_height,
	 *		GLenum n_internal_format, bool b_create_mipmaps = true, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates two-dimensional texture of size n_width per n_height and color format
	 *		  n_internal format with border n_border_width pixels wide
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_2D(int n_width, int n_height, GLenum n_internal_format,
		bool b_create_mipmaps = true,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Height() const { return m_n_height; }
};

#if 0
class CGLTexture_Rect : public CGLTexture_2D_Base {
protected:
	int m_n_width;
	int m_n_height;

public:
	/*
	 *	CGLTexture_Rect::CGLTexture_Rect(CGLState *p_state, int n_width, int n_height,
	 *		GLenum n_internal_format, GLenum n_format = GL_RGB,
	 *		GLenum n_data_type = GL_UNSIGNED_BYTE, const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates two-dimensional texture of size n_width per n_height and color format
	 *		  n_internal format (no border and no mip-mapping allowed for rectangle textures)
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_Rect(int n_width, int n_height, GLenum n_internal_format,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Height() const { return m_n_height; }
};
#endif // 0

class CGLTexture_3D : public CGLTexture_3D_Base {
protected:
	int m_n_width;
	int m_n_height;
	int m_n_depth;

public:
	/*
	 *	CGLTexture_3D::CGLTexture_3D(CGLState *p_state, int n_width, int n_height, int n_depth,
	 *		GLenum n_internal_format, bool b_create_mipmaps = true, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates three-dimensional texture of size n_width per n_height per n_depth
	 *		  and color format n_internal format with border n_border_width pixels wide
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_3D(int n_width, int n_height, int n_depth,
		GLenum n_internal_format, bool b_create_mipmaps = true,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Height() const { return m_n_height; }
	inline int n_Depth() const { return m_n_depth; }
};

#if 0

class CGLTexture_1D_Array : public CGLTexture_2D_Base {
protected:
	int m_n_width;
	int m_n_layer_num;

public:
	/*
	 *	CGLTexture_3D::CGLTexture_3D(CGLState *p_state, int n_width, int n_height, int n_depth,
	 *		GLenum n_internal_format, bool b_create_mipmaps = true, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates three-dimensional texture of size n_width per n_height per n_depth
	 *		  and color format n_internal format with border n_border_width pixels wide
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_1D_Array(int n_width, int n_layer_num,
		GLenum n_internal_format, bool b_create_mipmaps = true,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Layer_Num() const { return m_n_layer_num; }
};

#endif // 0

class CGLTexture_2D_Array : public CGLTexture_3D_Base {
protected:
	int m_n_width;
	int m_n_height;
	int m_n_layer_num;

public:
	/*
	 *	CGLTexture_3D::CGLTexture_3D(CGLState *p_state, int n_width, int n_height, int n_depth,
	 *		GLenum n_internal_format, bool b_create_mipmaps = true, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates three-dimensional texture of size n_width per n_height per n_depth
	 *		  and color format n_internal format with border n_border_width pixels wide
	 *		- texture can be initialized with image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_2D_Array(int n_width, int n_height, int n_layer_num,
		GLenum n_internal_format, bool b_create_mipmaps = true,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Height() const { return m_n_height; }
	inline int n_Layer_Num() const { return m_n_layer_num; }
};

class CGLTexture_Cube : public CGLTexture {
protected:
	static const GLenum p_cube_face[6];
	int m_n_width;

public:
	/*
	 *	CGLTexture_Cube::CGLTexture_Cube(CGLState *p_state, int n_width, GLenum n_internal_format,
	 *		bool b_create_mipmaps = true, bool b_software_mipmaps = false, int n_border_width = 0,
	 *		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
	 *		const void *p_src_data[6] = 0)
	 *		- default constructor
	 *		- p_state is OpenGL state guard
	 *		- creates cube-map texture of size n_width per n_width per n_width
	 *		  and color format n_internal format with border n_border_width pixels wide
	 *		- texture can be initialized with six images image of color format n_format stored in
	 *		  data type n_data_type in array p_src_data (in order x- x+ y- y+ z- z+)
	 *		- call b_Status() to find out wheter constructor succeeded
	 *		- note this calls glGetError() and so it fails in case there are some
	 *		  uncaught errors despite texture creation is successful
	 */
	CGLTexture_Cube(int n_width, GLenum n_internal_format,
		bool b_create_mipmaps = true, bool b_software_mipmaps = false,
		GLenum n_format = GL_RGB, GLenum n_data_type = GL_UNSIGNED_BYTE,
		const void *p_src_data[6] = 0);

	inline int n_Width() const { return m_n_width; }
	inline int n_Height() const { return m_n_width; }
	inline int n_Depth() const { return m_n_width; }
};

#endif // !__TEXTURE_MANAGER4_INCLUDED
