/*
								+----------------------------------+
								|                                  |
								| ***   Texture loader class  ***  |
								|                                  |
								|   Copyright © -tHE SWINe- 2007   |
								|                                  |
								|          TextureUtil.h           |
								|                                  |
								+----------------------------------+
*/

#pragma once
#ifndef __TEXTURE_UTIL_INCLUDED
#define __TEXTURE_UTIL_INCLUDED

/**
 *	@file gles2/TextureUtil.h
 *	@author -tHE SWINe-
 *	@date 2007
 *	@brief texture loader class
 *
 *	@date 2007-07-14
 *
 *	removed GL_TEXTURE_2D target from CGLTexture_2D constructor calls
 *
 *	@date 2007-12-24
 *
 *	improved linux compatibility by using posix integer types
 *
 *	@date 2008-03-04
 *
 *	added CTextureLoader::p_LoadTexture which can decide fileformat by itself
 *
 *	@date 2008-05-07
 *
 *	fixed stupid error in CTextureLoader::p_LoadTexture which couldn't decide
 *	fileformat correctly
 *
 *	@date 2008-10-09
 *
 *	renamed CTextureLoader to CGLTextureLoader (but there's typedef on the end of the file
 *	so the code is backwards-compatible. however, new name should be always used.)
 *
 *	added CGLTextureLoader::p_LoadTexture_3D()
 *
 *	cleaned-up image format decission and loading a little bit
 *
 *	exposed internal texture format override in all texture loading functions
 *
 *	moved function bodies to TextureUtil.cpp
 *
 *	@date 2009-05-04
 *
 *	fixed mixed windows / linux line endings
 *
 *	@date 2009-10-20
 *
 *	fixed some warnings when compiling under VC 2005, implemented "Security
 *	Enhancements in the CRT" for VC 2008. compare against MyProjects_2009-10-19_
 *
 */

#include "../Tga.h"
#include "Texture.h"

/**
 *	@def __GLES_TEXTURE_LOADER_USE_PNG
 *	@brief uses the functions from CPngCodec to support PNG images as well
 */
#define __GLES_TEXTURE_LOADER_USE_PNG

/**
 *	@brief simple class with a few useful functions for loading textures directly from file
 */
class CGLESTextureLoader {
public:
	/**
	 *	@brief image format names
	 */
	enum {
		format_Unknown = -1, /**< @brief unknown format */
		format_Targa, /**< @brief Targa format */
		format_Jpeg, /**< @brief Jpeg format */
		format_PNG /**< @brief PNG format */
	};

	/**
	 *	@brief identifies format of image file by its extension (doesn't look at file data)
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns one of format_Unknown, format_Targa, format_Jpeg.
	 */
	static int n_IdentifyFormat(const char *p_s_filename);

	/**
	 *	@brief loads a Targa image
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns pointer to the image, loaded from the given file, or 0 on failure.
	 */
	static TBmp *p_LoadTargaImage(const char *p_s_filename);
	
	/**
	 *	@brief gets Targa image info
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns structure with image image information loaded from the
	 *		given file on success, or a structure filled with zeros on failure.
	 */
	static TBmp t_TargaImageInfo(const char *p_s_filename);

	/**
	 *	@brief loads a Jpeg image
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns pointer to the image, loaded from the given file, or 0 on failure.
	 */
	static TBmp *p_LoadJpegImage(const char *p_s_filename);
	
	/**
	 *	@brief gets Jpeg image info
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns structure with image image information loaded from the
	 *		given file on success, or a structure filled with zeros on failure.
	 */
	static TBmp t_JpegImageInfo(const char *p_s_filename);

	/**
	 *	@brief loads a PNG image
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns pointer to the image, loaded from the given file, or 0 on failure.
	 */
	static TBmp *p_LoadPNGImage(const char *p_s_filename);
	
	/**
	 *	@brief gets PNG image info
	 *	@param[in] p_s_filename is image file name
	 *	@return Returns structure with image image information loaded from the
	 *		given file on success, or a structure filled with zeros on failure.
	 */
	static TBmp t_PNGImageInfo(const char *p_s_filename);

	/**
	 *	@brief loads an image from a file
	 *
	 *	@param[in] p_s_filename is image file name
	 *	@param[in] n_force_format is a format to use (if format_Unknown, n_IdentifyFormat() will be used)
	 *
	 *	@return Returns pointer to the image, loaded from the given file, or 0 on failure.
	 */
	static TBmp *p_LoadImage(const char *p_s_filename, int n_force_format = format_Unknown);

	/**
	 *	@brief gets info about an image file
	 *
	 *	@param[in] p_s_filename is image file name
	 *	@param[in] n_force_format is a format to use (if format_Unknown, n_IdentifyFormat() will be used)
	 *
	 *	@return Returns structure with image image information loaded from the
	 *		given file on success, or a structure filled with zeros on failure.
	 */
	static TBmp t_GetImageInfo(const char *p_s_filename, int n_force_format = format_Unknown);

	/**
	 *	@brief gets optimal texture internal format
	 *	@param[in] p_bitmap is an image to be stored in a texture
	 *	@return Returns optimal texture internal format for the image
	 *		(based on image being greyscale and on alpha channel presence).
	 */
	static GLenum n_Optimal_InternalFormat(const TBmp *p_bitmap, bool b_force_alpha = false);

#if 0
	/**
	 *	@brief gets internal pixel format for an image
	 *
	 *	@param[in] p_image is input image
	 *
	 *	@return Returns pixel format of the image (eg. GL_RGB8).
	 *
	 *	@note This returns GL_RED8 and GL_RG8 for one- and two-compoent images.
	 *		These tokens are replaced by GL_LUMINANCE8 and GL_LUMINANCE_ALPHA8 in OpenGL ES 2.0.
	 *		The functionality that can be acheived is quite the similar in GL_RED
	 *		(provided that only the red channel is sampled in the shader), but
	 *		GL_LUMINANCE_ALPHA and GL_RG need some extra swizzling (use preprocessor in shaders).
	 */
	static GLenum n_Image_InternalFormat(const TImage *p_image);

	/**
	 *	@brief gets (external) pixel format for an image
	 *
	 *	@param[in] p_image is input image
	 *
	 *	@return Returns pixel format of the image (eg. GL_RGB).
	 *
	 *	@note This returns GL_RED and GL_RG for one- and two-compoent images.
	 *		These tokens are replaced by GL_LUMINANCE and GL_LUMINANCE_ALPHA in OpenGL ES 2.0.
	 *		The functionality that can be acheived is quite the similar in GL_RED
	 *		(provided that only the red channel is sampled in the shader), but
	 *		GL_LUMINANCE_ALPHA and GL_RG need some extra swizzling (use preprocessor in shaders).
	 */
	static GLenum n_Image_ExternalFormat(const TImage *p_image);
#endif // 0

	/**
	 *	@brief gets internal pixel format for certain number of components
	 *
	 *	@param[in] n_component_num is number of components (1 to 4)
	 *	@param[in] n_bit_num is number of bits per component (must be 8, 16 or 32)
	 *	@param[in] b_float is floating point flag (if set, n_bit_num must be 16 or 32,
	 *		otherwise n_bit_num must be 8 or 16)
	 *
	 *	@return Returns OpenGL pixel format (eg. GL_RGB8) or 0 on failure
	 *		(no such format supported by the implementation).
	 *
	 *	@note This returns some formats that will possibly not be supported by Tegra.
	 *	@note This returns GL_RED and GL_RG for one- and two-compoent images.
	 *		These tokens are replaced by GL_LUMINANCE and GL_LUMINANCE_ALPHA in OpenGL ES 2.0.
	 *		The functionality that can be acheived is quite the similar in GL_RED
	 *		(provided that only the red channel is sampled in the shader), but
	 *		GL_LUMINANCE_ALPHA and GL_RG need some extra swizzling (use preprocessor in shaders).
	 */
	static GLenum n_Pixel_InternalFormat(int n_component_num, int n_bit_num = 8, bool b_float = false);

	/**
	 *	@brief gets (external) pixel format for certain number of components
	 *
	 *	@param[in] n_component_num is number of components (1 to 4)
	 *
	 *	@return Returns OpenGL pixel format (eg. GL_RGB) or 0 on failure
	 *		(no such format supported by the implementation).
	 *
	 *	@note This returns GL_RED and GL_RG for one- and two-compoent images.
	 *		These tokens are replaced by GL_LUMINANCE and GL_LUMINANCE_ALPHA in OpenGL ES 2.0.
	 *		The functionality that can be acheived is quite the similar in GL_RED
	 *		(provided that only the red channel is sampled in the shader), but
	 *		GL_LUMINANCE_ALPHA and GL_RG need some extra swizzling (use preprocessor in shaders).
	 */
	static GLenum n_Pixel_ExternalFormat(int n_component_num);

#if 0
	/**
	 *	@brief gets pixel data type for an image
	 *
	 *	By default, the formats are GL_UNSIGNED_BYTE, GL_SHORT or GL_INT.
	 *	The signedness of the formats can be controlled by setting one of
	 *	b_force_signed or b_force_unsigned (results are undefined if both flags are set).
	 *	This merely selects integer which is big enough to store the datatype
	 *	used by the image (should work for most of the sane formats, though).
	 *
	 *	@param[in] p_image is input image
	 *	@param[in] b_force_signed is signed flag
	 *	@param[in] b_force_unsigned is unsigned flag
	 *
	 *	@return Returns pixel data type of the image (eg. GL_UNSIGNED_BYTE).
	 */
	static GLenum n_Image_DataType(const TImage *p_image, bool b_force_signed = false, bool b_force_unsigned = false);
#endif // 0

	static CGLTexture_2D *p_TextureFromBitmap(const TBmp *p_bitmap,
		bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format = 0);

#if 0
	static CGLTexture_2D *p_TextureFromImage(const TImage *p_image,
		bool b_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format = 0,
		GLenum n_force_datatype = 0, bool b_force_signed = false, bool b_force_unsigned = false);
#endif // 0

	/**
	 *	@brief gets maximal number of texture array layers
	 *	@return Returns maximal number of texture array layers,
	 *		or 0 if GL_EXT_texture_array is not supported).
	 */
	static int n_Max_TextureArray_Layer_Num();

#if 0
	/**
	 *	@brief creates a texture array from it's first layer
	 *
	 *	This loads the first layer of texture array. Use Upload_TextureArray_Layer() to upload the
	 *	following layers.
	 *
	 *	@param[in] p_image is the first layer of the array (it dictates pixel
	 *		formats and dimensions to use throughout the array)
	 *	@param[in] n_layer_num is total number of layers (must be at least 1)
	 *	@param[in] b_alloc_mipmaps is mipmap flag (note this only allocates mipmaps,
	 *		it doesn't create them; call glCreateMipmaps() once the whole array is specified)
	 *	@param[in] b_clamp_to_edge is clamp to edge flag (otherwise the texture is repeated)
	 *	@param[in] n_force_internal_format is internal format override (if 0, the internal
	 *		format is determined using n_Image_Format() function - a format which do not specify precission)
	 *	@param[in] n_force_datatype is datatype override (if 0, the datatype is determined
	 *		using n_Image_DataType() function; use with caution!)
	 *	@param[in] b_force_signed is signed datatype preference flag (ignored if n_force_datatype is nonzero)
	 *	@param[in] b_force_unsigned is unsigned datatype preference flag (ignored if n_force_datatype is nonzero)
	 *
	 *	@return Returns pointer to a new 2D texture array with the first layer initialized.
	 *
	 *	@note Note about data types and pixel formats - specify them if possible. Using n_Image_DataType()
	 *		may lead to out-of-bounds memory accesses or corrupt images. Also, pixels must be tightly
	 *		packed for this to work (scanlines do not, although it causes transfers to run much slower).
	 */
	static CGLTexture_2D_Array *p_TextureArrayFromImage(const TImage *p_image, int n_layer_num,
		bool b_alloc_mipmaps, bool b_clamp_to_edge, GLenum n_force_internal_format = 0,
		GLenum n_force_datatype = 0, bool b_force_signed = false, bool b_force_unsigned = false);

	/**
	 *	@brief uploads a texture array layer
	 *
	 *	@param[in] p_texture is the texture array object (eg. result of call to p_TextureArrayFromImage())
	 *	@param[in] p_image is the image to be uploaded
	 *	@param[in] n_layer is zero-based index of the layer being specified
	 *	@param[in] n_force_datatype is datatype override (if 0, the datatype is determined
	 *		using n_Image_DataType() function; use with caution!)
	 *	@param[in] b_force_signed is signed datatype preference flag (ignored if n_force_datatype is nonzero)
	 *	@param[in] b_force_unsigned is unsigned datatype preference flag (ignored if n_force_datatype is nonzero)
	 *
	 *	@return Returns pointer to a new 2D texture array with the first layer initialized.
	 *
	 *	@note Note about data types and pixel formats - specify them if possible. Using n_Image_DataType()
	 *		may lead to out-of-bounds memory accesses or corrupt images. Also, pixels must be tightly
	 *		packed for this to work (scanlines do not, although it causes transfers to run much slower).
	 */
	static void Upload_TextureArray_Layer(CGLTexture_2D_Array *p_texture, const TImage *p_image, int n_layer,
		GLenum n_force_datatype = 0, bool b_force_signed = false, bool b_force_unsigned = false);
#endif // 0

	/**
	 *	@brief finalizes the texture array object (call after the last call to Upload_TextureArray_Layer())
	 *
	 *	@param[in] p_texture is the texture array object (eg. result of call to p_TextureArrayFromImage())
	 *	@param[in] b_build_mipmaps is mipmap flag (it calls glGenerateMipmap() to build the mipmaps);
	 *		this only work if b_alloc_mipmaps was set in the call to p_TextureArrayFromImage().
	 *
	 *	@return Returns true on success, false on failure (it deleres p_texture so it can't be used anymore).
	 */
	static bool Finalize_TextureArray(CGLTexture_2D_Array *p_texture, bool b_build_mipmaps);

	/*
	 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture(CGLState *p_state,
	 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = false,
	 *		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown)
	 *		- returns new 2D texture
	 *		- b_mipmaps controls wheter texture is created with mipmaps
	 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
	 *		- passing nonzero n_force_internal_format sets texture internal format
	 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
	 *		- if n_force_format is not format_Unknown, image is loaded using
	 *		  corresponding codec (otherwise format is determined using filename
	 *		  extension)
	 */
	static CGLTexture_2D *p_LoadTexture(const char *p_s_filename,
		bool b_mipmaps = true, bool b_clamp_to_edge = false,
		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown);

	/*
	 *	static CGLTexture_2D *CGLTextureLoader::p_LoadTexture_Hybrid(CGLState *p_state,
	 *		const char *p_s_color_filename, const char *p_s_alpha_filename,
	 *		bool b_mipmaps = true, bool b_clamp_to_edge = false,
	 *		GLenum n_force_internal_format = 0)
	 *		- returns new 2D texture
	 *		- p_state is OpenGL state guard
	 *		- p_s_color_filename and p_s_alpha_filename are image filenames. rgb
	 *		  components of first image are merged with second image alpha channel
	 *		  (in case the second image doesn't have an alpha channel, it's red
	 *		  channel is used)
	 *		- b_mipmaps controls wheter texture is created with mipmaps
	 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
	 *		- passing nonzero n_force_internal_format sets texture internal format
	 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
	 */
	static CGLTexture_2D *p_LoadTexture_Hybrid(
		const char *p_s_color_filename, const char *p_s_alpha_filename,
		bool b_mipmaps = true, bool b_clamp_to_edge = false,
		GLenum n_force_internal_format = 0);

	/*
	 *	static CGLTexture_Cube *CGLTextureLoader::p_LoadTexture_Cube(CGLState *p_state,
	 *		const char *p_s_filename, bool b_mipmaps = true, bool b_clamp_to_edge = true,
	 *		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown)
	 *		- returns new cube-map texture
	 *		- p_state is OpenGL state guard, p_s_filename is image file name
	 *		  (file name contains formatting string for snprintf, such as
	 *		  "texture_%s.jpg", %s is replaced by "pos-x", "neg-x", "pos-y", ...)
	 *		- b_mipmaps controls wheter texture is created with mipmaps
	 *		- b_clamp_to_edge sets texture wrap mode to GL_CLAMP_TO_EDGE
	 *		- passing nonzero n_force_internal_format sets texture internal format
	 *		  to n_force_internal_format (otherwise RGB8 / RGBA8 is used)
	 *		- if n_force_format is not format_Unknown, image is loaded using
	 *		  corresponding codec (otherwise format is determined using filename
	 *		  extension)
	 */
	static CGLTexture_Cube *p_LoadTexture_Cube(const char *p_s_filename,
		bool b_mipmaps = true, bool b_clamp_to_edge = true,
		GLenum n_force_internal_format = 0, int n_force_format = format_Unknown);
};

/**
 *	@brief a simple overload of CGLTexture_2D to make it statically initializable
 *	@tparam b_keep_image is image storage flag (if set, the image in the
 *		host memory is kept along with the texture)
 */
template <const bool b_keep_image = false>
class CGLTexture_2D_FromBitmap : public CGLTexture_2D {
protected:
	TBmp *m_p_src_image; /**< @brief pointer to the original image */

public:
	/**
	 *	@brief default constructor
	 *
	 *	@param[in] p_texture is pointer to the texture image (becomes managed by this texture object)
	 *	@param[in] n_intformat is OpenGL internal format name or 0 to use optimal format for the given image
	 *	@param[in] b_mipmaps is mipmaps flag
	 *	@param[in] b_clamp_to_edge is clamp to edge flag
	 */
	inline CGLTexture_2D_FromBitmap(TBmp *p_texture, GLenum n_intformat = GL_RGBA,
		bool b_mipmaps = true, bool b_clamp_to_edge = false)
		:CGLTexture_2D((p_texture)? p_texture->n_width : 1,
		(p_texture)? p_texture->n_height : 1, (n_intformat)? n_intformat :
		CGLESTextureLoader::n_Optimal_InternalFormat(p_texture),
		b_mipmaps, GL_RGBA, GL_UNSIGNED_BYTE, (p_texture)?
		p_texture->p_buffer : 0), m_p_src_image(p_texture)
	{
		if(b_clamp_to_edge) {
			CGLTexture_2D::Set_Wrap_S(GL_CLAMP_TO_EDGE);
			CGLTexture_2D::Set_Wrap_T(GL_CLAMP_TO_EDGE);
		}
	}

	/**
	 *	@brief destructor; deletes the texture and the source image
	 */
	inline ~CGLTexture_2D_FromBitmap()
	{
		if(m_p_src_image)
			m_p_src_image->Delete();
	}

	/**
	 *	@brief gets source image
	 *	@return Returns the source image.
	 */
	inline const TBmp *p_Source_Image() const
	{
		return m_p_src_image;
	}

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

/**
 *	@brief a simple overload of CGLTexture_2D to make it statically initializable
 *		(specialization that does not keep the image)
 */
template <>
class CGLTexture_2D_FromBitmap<false> : public CGLTexture_2D {
public:
	/**
	 *	@brief default constructor
	 *
	 *	@param[in] p_texture is pointer to the texture image (becomes managed by this texture object)
	 *	@param[in] n_intformat is OpenGL internal format name or 0 to use optimal format for the given image
	 *	@param[in] b_mipmaps is mipmaps flag
	 *	@param[in] b_clamp_to_edge is clamp to edge flag
	 */
	inline CGLTexture_2D_FromBitmap(TBmp *p_texture, GLenum n_intformat = GL_RGBA,
		bool b_mipmaps = true, bool b_clamp_to_edge = false)
		:CGLTexture_2D((p_texture)? p_texture->n_width : 1,
		(p_texture)? p_texture->n_height : 1, (n_intformat)? n_intformat :
		CGLESTextureLoader::n_Optimal_InternalFormat(p_texture),
		b_mipmaps, GL_RGBA, GL_UNSIGNED_BYTE, (p_texture)? p_texture->p_buffer : 0)
	{
		if(p_texture)
			p_texture->Delete();
		if(b_clamp_to_edge) {
			CGLTexture_2D::Set_Wrap_S(GL_CLAMP_TO_EDGE);
			CGLTexture_2D::Set_Wrap_T(GL_CLAMP_TO_EDGE);
		}
	}

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

/**
 *	@brief a simple overload of CGLTexture_2D to make it statically initializable
 *	@tparam b_keep_image is image storage flag (if set, the image in the
 *		host memory is kept along with the texture)
 */
template <const bool b_keep_image = false>
class CGLTexture_2D_Loadable : public CGLTexture_2D_FromBitmap<b_keep_image> {
public:
	/**
	 *	@brief default constructor
	 *
	 *	@param[in] p_s_filename is null-terminated string, containing image file name
	 *	@param[in] n_intformat is OpenGL internal format name or 0 to use optimal format for the given image
	 *	@param[in] b_mipmaps is mipmaps flag
	 *	@param[in] b_clamp_to_edge is clamp to edge flag
	 *	@param[in] b_delete_image_right_away is host image deletion flag (if set, the image is deleted from
	 *		the host memory right after the texture is created)
	 */
	inline CGLTexture_2D_Loadable(const char *p_s_filename, GLenum n_intformat = GL_RGB,
		bool b_mipmaps = true, bool b_clamp_to_edge = false,
		bool b_delete_image_right_away = false)
		:CGLTexture_2D_FromBitmap<b_keep_image>(CGLESTextureLoader::p_LoadImage(p_s_filename),
		n_intformat, b_mipmaps, b_clamp_to_edge)
	{
		if(b_delete_image_right_away && this->m_p_src_image) {
			this->m_p_src_image->Delete();
			this->m_p_src_image = 0; // !!
		}
	}
};

/**
 *	@brief a simple overload of CGLTexture_2D to make it statically initializable
 *		(specialization that does not keep the image)
 */
template <>
class CGLTexture_2D_Loadable<false> : public CGLTexture_2D_FromBitmap<false> {
public:
	/**
	 *	@brief default constructor
	 *
	 *	@param[in] p_s_filename is null-terminated string, containing image file name
	 *	@param[in] n_intformat is OpenGL internal format name or 0 to use optimal format for the given image
	 *	@param[in] b_mipmaps is mipmaps flag
	 *	@param[in] b_clamp_to_edge is clamp to edge flag
	 */
	inline CGLTexture_2D_Loadable(const char *p_s_filename, GLenum n_intformat = GL_RGB,
		bool b_mipmaps = true, bool b_clamp_to_edge = false)
		:CGLTexture_2D_FromBitmap<false>(CGLESTextureLoader::p_LoadImage(p_s_filename),
		n_intformat, b_mipmaps, b_clamp_to_edge)
	{}
};

#endif // !__TEXTURE_UTIL_INCLUDED
