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

//TODO: surfaces need to be either visible or getable.
class glLibTexture2D : public glLibInternal_texture {
	friend void glLibSceneToTexture(int rect[4], glLibTexture2D* texture);
	friend void glLibSceneToTexture(glLibFlag rect, glLibTexture2D* texture);
	
	//Update reference(...) when changing attributes
	public:
		unsigned long x, width;
		unsigned long y, height;
		unsigned long size[2];
		glLibFlagTextureEdge edge_x, edge_y;
		unsigned long num_texels;
		unsigned long rect[4];

	public:
		SDL_Surface *data_surf1, *data_surf2;
		Uint8* data_uint8;
		float* data_float32;
		GLint data_type;
	
	public:
		//Constructors
		//TODO: make filter/mipmap by default false
		//TODO: test colorkey!
		//TODO: constructor for std::string!

		glLibTexture2D();// : glLibInternal_texture(true) //see .cpp
		template <                    typename type_rect, typename type_filter_min, typename type_filter_mag                        > glLibTexture2D(                type_rect flag_rect, glLibFlagTextureFormat format, type_filter_min flag_filter_min, type_filter_mag flag_filter_mag,                         int precision=8) {
			glLibInternal_make((int)(-1), flag_rect, format, flag_filter_min, flag_filter_mag, (int)(-1), precision);
		}
		template <typename type_data, typename type_rect, typename type_filter_min, typename type_filter_mag                        > glLibTexture2D(type_data data, type_rect flag_rect, glLibFlagTextureFormat format, type_filter_min flag_filter_min, type_filter_mag flag_filter_mag,                         int precision=8) {
			glLibInternal_make(data, flag_rect, format, flag_filter_min, flag_filter_mag, (int)(-1), precision);
		}
		template <                    typename type_rect, typename type_filter_min, typename type_filter_mag, typename type_colorkey> glLibTexture2D(                type_rect flag_rect, glLibFlagTextureFormat format, type_filter_min flag_filter_min, type_filter_mag flag_filter_mag, type_colorkey colorkey, int precision=8) {
			glLibInternal_make((int)(-1), flag_rect, format, flag_filter_min, flag_filter_mag, colorkey, precision);
		}
		template <typename type_data, typename type_rect, typename type_filter_min, typename type_filter_mag, typename type_colorkey> glLibTexture2D(type_data data, type_rect flag_rect, glLibFlagTextureFormat format, type_filter_min flag_filter_min, type_filter_mag flag_filter_mag, type_colorkey colorkey, int precision=8) {
			glLibInternal_make(data, flag_rect, format, flag_filter_min, flag_filter_mag, colorkey, precision);
		}

	public:    ~glLibTexture2D();

	public:    glLibTexture2D& reference(glLibTexture2D* ref_tex);

	public:    void fastest(void);
	public:    void nicest(void);
		
	public:    void set_edge(glLibFlagTextureEdge value);
	public:    void set_edge(glLibFlagTextureEdge* value);

	public:    float set_anisotropy(glLibFlag value);
	public:    float set_anisotropy(float value);
	public:    float set_anisotropy(double value);
	public:    float set_anisotropy(int value);
	public:    float set_anisotropy(bool value);
	private:   float glLibInternal_set_anis(float value);

	public:    void load_data(void);

	private:   void glLibInternal_update_mipmap(glLibFlagFilter filtering);

	private:
		void glLibInternal_make_init(void);
		
		void glLibInternal_make_set_rect_data_from(unsigned short rect[]);
		void glLibInternal_make_set_rect_data_from(SDL_Surface* surf);
		void glLibInternal_make_check_rect(SDL_Surface* surf, string addendum);

		#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS //all four functions in this #ifdef throw errors
		void glLibInternal_make_rects_load_surfaces(Uint8*           data, glLibFlag rect_flag) {
			glLibInternal_make_throw_rect_infer("Uint32");
		}
		void glLibInternal_make_rects_load_surfaces(float*           data, glLibFlag rect_flag) {
			glLibInternal_make_throw_rect_infer("Float32");
		}
		void glLibInternal_make_rects_load_surfaces(double*          data, glLibFlag rect_flag) {
			glLibInternal_make_throw_rect_infer("Double");
		}
		void glLibInternal_make_rects_load_surfaces(int         null_data, glLibFlag rect_flag) {
			throw glLibError("For blank textures, the data size cannot be inferred!  Pass a rectangle instead.");
		}
		#endif
		void glLibInternal_make_rects_load_surfaces(Uint8*           data, unsigned short rect_data[4]) {
			glLibInternal_make_set_rect_data_from(rect_data);
		}
		void glLibInternal_make_rects_load_surfaces(float*           data, unsigned short rect_data[4]) {
			glLibInternal_make_set_rect_data_from(rect_data);
		}
		void glLibInternal_make_rects_load_surfaces(double*          data, unsigned short rect_data[4]) {
			glLibInternal_make_set_rect_data_from(rect_data);
		}
		void glLibInternal_make_rects_load_surfaces(int         null_data, unsigned short rect_data[4]) {
			glLibInternal_make_set_rect_data_from(rect_data);
		}

		void glLibInternal_make_rects_load_surfaces(SDL_Surface*     data, glLibFlag rect_flag) {
			if (rect_flag==GLLIB_ALL) {
				glLibInternal_make_set_rect_data_from(data);
			}
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			else throw glLibError("Invalid rect flag!  Must be \"GLLIB_ALL\".");
			#endif
		}
		void glLibInternal_make_rects_load_surfaces(SDL_Surface*     data, unsigned short rect_data[4]) {
			glLibInternal_make_set_rect_data_from(rect_data);

			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			glLibInternal_make_check_rect(data," passed to this texture");
			#endif
		}
		
		void glLibInternal_make_rects_load_surfaces(const char* path_data, glLibFlag rect_flag) {
			data_surf1 = glLibInternal_load_surface(path_data);

			if (rect_flag==GLLIB_ALL) {
				glLibInternal_make_set_rect_data_from(data_surf1);
			}
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			else throw glLibError("Invalid rect flag!  Must be \"GLLIB_ALL\".");
			#endif
		}
		void glLibInternal_make_rects_load_surfaces(const char* path_data, unsigned short rect_data[4]) {
			data_surf1 = glLibInternal_load_surface(path_data);

			glLibInternal_make_set_rect_data_from(rect_data);

			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			//Check that the rect is reasonable
			glLibInternal_make_check_rect( data_surf1, string(" loaded from file \"")+path_data+"\"" );
			#endif
		}
		
		//TODO: short?
		//TODO: UINT for depth @ 32 bits, ushort for depth @ 16
		void* glLibInternal_make_create_duplicate_data(Uint8*           data, int precision) {
			switch (precision) {
				case  8: { return glLibInternal_make_duplicate_data_to_uint8(data,num_texels); }
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: { return glLibInternal_make_duplicate_data_to_float32(data,num_texels); }
			}
			return NULL; //shouldn't happen
		}
		void* glLibInternal_make_create_duplicate_data(float*           data, int precision) {
			switch (precision) {
				case  8: {
					#ifdef GLLIB_ERROR_MODE_REPORT_WARNINGS
					glLibWarning("Requested 8-bit precision on 32-bit data!  Reducing data precision to 8-bit.");
					#endif
					
					return glLibInternal_make_duplicate_data_to_uint8(data,num_texels);
				}
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: { return glLibInternal_make_duplicate_data_to_float32(data,num_texels); }
			}
			return NULL; //shouldn't happen
		}
		void* glLibInternal_make_create_duplicate_data(double*          data, int precision) {
			switch (precision) {
				case  8: {
					#ifdef GLLIB_ERROR_MODE_REPORT_WARNINGS
					glLibWarning("Requested 8-bit precision on 64-bit data!  Reducing data precision to 8-bit.");
					#endif
					
					return glLibInternal_make_duplicate_data_to_uint8(data,num_texels);
				}
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: {
					//Nothing we can do about it--we'll have to reduce 64 to 32.  No warning.
					return glLibInternal_make_duplicate_data_to_float32(data,num_texels);
				}
			}
			return NULL; //shouldn't happen
		}
		void* glLibInternal_make_create_duplicate_data(SDL_Surface*     data, int precision) {
			switch (precision) {
				case  8: {
					data_surf2 = glLibInternal_make_desired_surface(data,x,y,width,height,texture_format);
					return glLibInternal_make_duplicate_data_to_uint8( (Uint8*)(data_surf2->pixels), num_texels );
				}
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: {
					data_surf2 = glLibInternal_make_desired_surface(data,x,y,width,height,texture_format);
					return glLibInternal_make_duplicate_data_to_uint8( (Uint8*)(data_surf2->pixels), num_texels );
				}
			}
			return NULL; //shouldn't happen
		}
		void* glLibInternal_make_create_duplicate_data(const char* path_data, int precision) {
			switch (precision) {
				case  8: {
					data_surf2 = glLibInternal_make_desired_surface(data_surf1,x,y,width,height,texture_format);
					return glLibInternal_make_duplicate_data_to_uint8( (Uint8*)(data_surf2->pixels), num_texels );
				}
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: {
					data_surf2 = glLibInternal_make_desired_surface(data_surf1,x,y,width,height,texture_format);
					return glLibInternal_make_duplicate_data_to_float32( (Uint8*)(data_surf2->pixels), num_texels );
				}
			}
			return NULL; //shouldn't happen
		}
		void* glLibInternal_make_create_duplicate_data(int         null_data, int precision) {
			return NULL;
		}

		template <typename type_data> void glLibInternal_make_create(type_data data, double colorkey[4], int precision) {
			void* duplicate_data = glLibInternal_make_create_duplicate_data(data, precision);

			if (texture_format&GLLIB_RGBA) {
				glLibInternal_make_create_evaluate_colorkey(duplicate_data,colorkey,num_texels);
			}

			switch (precision) {
				case  8: { data_type = GL_UNSIGNED_BYTE;   data_uint8 = (Uint8*)duplicate_data; break; }
				case 16: {} //Fall through to 24->32
				case 24: {} //Fall through to 32
				case 32: { data_type =         GL_FLOAT; data_float32 = (float*)duplicate_data; break; }
			}

			//Load data
			load_data();
		}

		template <typename type_data, typename type_rect, typename type_filter_min, typename type_filter_mag, typename type_colorkey> void glLibInternal_make(
			type_data data,
			type_rect flag_rect,
			glLibFlagTextureFormat format,
			type_filter_min flag_filter_min,
			type_filter_mag flag_filter_mag,
			type_colorkey colorkey_data,
			int precision)
		{
			//Set up various parameters and initialize pointers to NULL
			glLibInternal_make_init();

			//Set format, internal format, and precision (also checks their validity)
			glLibInternal_make_set_format(format,precision);

			//Set min and mag filter
			glLibInternal_make_set_filters(flag_filter_min,flag_filter_mag);

			//Set colorkey
			double colorkey[4] = {-1.0,-1.0,-1.0,-1.0};
			glLibInternal_make_colorkey(colorkey, colorkey_data, format);

			//Load rect data (and, if from a file, load data)
			glLibInternal_make_rects_load_surfaces(data,flag_rect);

			//Process data according to precision
			glLibInternal_make_create(data, colorkey, precision);
		}
};