#pragma once
#include "../../stdafx.h"
#include "../glLibLocals.h"
#include "../glLibError.h"
#include "../glLibView.h"

namespace glLib {

namespace Texture {
	class texture_cube;
}

//TODO: const on the function?
void glLibInternal_update_cubemap(Texture::texture_cube* cubemap, const View::view_3D* cubemap_view, double x, double y, double z, void(*draw_reflectees)(void), const bool update[6]);

class material;

namespace Texture {

class glLibInternal_texture {
	friend class ::glLib::material;
	friend void ::glLib::glLibInternal_update_cubemap(::glLib::Texture::texture_cube* cubemap, const View::view_3D* cubemap_view ,double x, double y, double z, void(*draw_reflectees)(void), const bool update[6]);

	//Update glLibInternal_reference_base_data(...) when changing attributes
	public:
		GLuint texture_id;

		glLibFlagTextureType texture_type;

		glLibFlagFilter texture_filter_minify;
		glLibFlagFilter texture_filter_magnify;

		bool texture_mipmapping;
		glLibFlagTextureFormat texture_format;
		unsigned char texture_channels;

		unsigned long* texture_data_refcount;
	//Update glLibInternal_reference_base_data(...) when changing attributes
	protected:
		bool contains_data;
		bool has_mipmap_data;
		bool created;
		GLenum enum_texture_format;
		GLenum enum_texture_format_internal;
		GLenum enum_texture_type;

	//Creation/destruction/copies
	public:    glLibInternal_texture(bool empty=false);
	protected: glLibInternal_texture(const glLibInternal_texture& copy_tex);
	protected: glLibInternal_texture& operator=(glLibInternal_texture const &assign_tex);
	public:    ~glLibInternal_texture(void);
	protected: void glLibInternal_delete_reference(void);

	//Functionality
	protected: virtual glLibInternal_texture& reference(const glLibInternal_texture* ref_tex);
	protected: void glLibInternal_reference_base_data(const glLibInternal_texture* ref_tex);

	//Must be overridden
	protected: virtual void load_data(void);
	protected: virtual void glLibInternal_update_mipmap(const glLibFlagFilter filtering) = 0;

	#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
	private:   void glLibInternal_check_filter_validity(const glLibFlagFilter min, const glLibFlagFilter mag) const {
				glLibFlagFilter valid_min_flags =
					GLLIB_FILTER_NEAREST|
					GLLIB_FILTER_BILINEAR|
					GLLIB_FILTER_NEAREST_MIPMAP_NEAREST|
					GLLIB_FILTER_BILINEAR_MIPMAP_NEAREST|
					GLLIB_FILTER_NEAREST_MIPMAP_BILINEAR|
					GLLIB_FILTER_BILINEAR_MIPMAP_BILINEAR;
				glLibFlagFilter valid_mag_flags =
					GLLIB_FILTER_NEAREST|
					GLLIB_FILTER_BILINEAR;
				if (!( min & valid_min_flags )) {
					throw error("Invalid minification filter!");
				}
				if (!(mag & valid_mag_flags )) {
					throw error("Invalid magnification filter!");
				}
			}
	#endif
	public:    void set_filtering(const glLibFlagFilter new_texture_filter_minify, const glLibFlagFilter new_texture_filter_magnify) {
			if (!(new_texture_filter_minify&texture_filter_minify)||
				!(new_texture_filter_magnify&texture_filter_magnify))
			{
				#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
				glLibInternal_check_filter_validity(new_texture_filter_minify,new_texture_filter_magnify);
				#endif

				texture_filter_minify = new_texture_filter_minify;
				texture_filter_magnify = new_texture_filter_magnify;
				glLibInternal_set_filtering();
			}
		}
	public:    void set_filtering(bool new_texture_filter_minify, bool new_texture_filter_magnify) {
			if (new_texture_filter_minify) {
				if (new_texture_filter_magnify) set_filtering(GLLIB_FILTER_BILINEAR_MIPMAP_BILINEAR,GLLIB_FILTER_BILINEAR);
				else                            set_filtering(GLLIB_FILTER_BILINEAR_MIPMAP_BILINEAR,GLLIB_FILTER_NEAREST);
			}
			else {
				if (new_texture_filter_magnify) set_filtering(GLLIB_FILTER_NEAREST,GLLIB_FILTER_BILINEAR);
				else                            set_filtering(GLLIB_FILTER_NEAREST,GLLIB_FILTER_NEAREST);
			}
		}
	protected: void glLibInternal_set_filtering(void);

	//Utilities
	//	Misc.
	protected: void glLibInternal_set_edge(GLenum pname, const glLibFlagTextureEdge value);
	protected: SDL_Surface* glLibInternal_load_surface(const char* path) const;
	protected: void glLibInternal_make_set_format(const glLibFlagTextureFormat format, int precision);

	#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
	protected: void glLibInternal_make_throw_rect_infer(const char* data_type);
	#endif

	protected: void glLibInternal_make_set_filter_min(const glLibFlagFilter filter_min_flag) {
			texture_filter_minify = filter_min_flag;
		}
	protected: void glLibInternal_make_set_filter_min(                 bool filter_min_flag) {
			texture_filter_minify = (filter_min_flag) ? GLLIB_FILTER_BILINEAR_MIPMAP_BILINEAR : GLLIB_FILTER_NEAREST;
		}
	protected: void glLibInternal_make_set_filter_mag(const glLibFlagFilter filter_mag_flag) {
			texture_filter_magnify = filter_mag_flag;
		}
	protected: void glLibInternal_make_set_filter_mag(                 bool filter_mag_flag) {
			texture_filter_magnify = (filter_mag_flag) ? GLLIB_FILTER_BILINEAR : GLLIB_FILTER_NEAREST;
		}
	protected: template <typename type_filter_min, typename type_filter_mag> void glLibInternal_make_set_filters(const type_filter_min flag_filter_min, const type_filter_mag flag_filter_mag) {
			glLibInternal_make_set_filter_min(flag_filter_min);
			glLibInternal_make_set_filter_mag(flag_filter_mag);
			glLibInternal_set_filtering();
		}

	protected:                                   void glLibInternal_make_colorkey(double* colorkey,                 int     null_data, const glLibFlagTextureFormat format) const {
		}
	protected: template <typename type_colorkey> void glLibInternal_make_colorkey(double* colorkey, const type_colorkey colorkey_data, const glLibFlagTextureFormat format) const {
			colorkey[0] = (double)(*(colorkey_data  ));
			colorkey[1] = (double)(*(colorkey_data+1));
			colorkey[2] = (double)(*(colorkey_data+2));
			colorkey[3] = (double)(*(colorkey_data+3));

			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if ((colorkey[0]!=-1.0)||(colorkey[1]!=-1.0)||(colorkey[2]!=-1.0)||(colorkey[3]!=-1.0)) {
				if (!(format&GLLIB_RGBA)) {
					throw GLLIB_ERROR_TYPE_ERROR("Colorkeys may not be set unless the texture is of type GLLIB_RGBA.");
				}
			}
			#endif
		}
	protected: SDL_Surface* glLibInternal_make_desired_surface(SDL_Surface* surf, int x, int y, int width, int height, const glLibFlagTextureFormat format) const;

	//	Colorkey
	protected: void glLibInternal_evaluate_colorkey(Uint8* data, const double colorkey[4], unsigned long num_texels) const;
	protected: void glLibInternal_evaluate_colorkey(float* data, const double colorkey[4], unsigned long num_texels) const;
	protected: void glLibInternal_make_create_evaluate_colorkey(void* data, const double colorkey[4], int precision) const;

	//	Data duplication functions
	protected: Uint8* glLibInternal_make_duplicate_data_to_uint8(const  Uint8* data, unsigned long num_texels) const;
	protected: Uint8* glLibInternal_make_duplicate_data_to_uint8(const  float* data, unsigned long num_texels) const;
	protected: Uint8* glLibInternal_make_duplicate_data_to_uint8(const double* data, unsigned long num_texels) const;
	protected: float* glLibInternal_make_duplicate_data_to_float32(const  Uint8* data, unsigned long num_texels) const;
	protected: float* glLibInternal_make_duplicate_data_to_float32(const  float* data, unsigned long num_texels) const;
	protected: float* glLibInternal_make_duplicate_data_to_float32(const double* data, unsigned long num_texels) const;
};

};};