#pragma once
#include "../stdafx.h"
#include "glLibLocals.h"
#include "glLibTexturing/glLibTexturing.h"

//#include "glLibPrebuiltShaders.h"
//#include "shaderfunctions.h"

class glLibShader {
	friend class glLibObject;
	friend class glLibParticleSystem;
	friend class glLibRendererParticle;

	public:
		string errors;

		bool is_source_present;
		bool is_compiled;
		bool is_linked;
		bool is_valid;

		int version_program_vert;
		int version_program_geom;
		int version_program_frag;

		GLuint program_id;

		string string_name;

	private:
		string _all_header;
		string _all_user_variables;

		string _vert_extension_functions;
		string _frag_extension_functions;

		string _frag_texcoord_transform;

		string _vert_pre_vertex_transform;
		string _vert_vertex_transform;
		string _vert_post_vertex_transform;

		string _frag_render_equation;

		//value is three bit code.  MSB->LSB: requested in vertex, requested in geometry, requested in fragment
		map<string,char> _includes;
		//typedef void (*Function_Ptr)(string* arg);
		//map<string,Function_Ptr> _includes_functions;
		map<string,void (*)(string* arg)> _includes_functions;
		map<string,void (*)(string* arg)> _includes_variables;

		unsigned char _numof_textures_1D, _numof_textures_2D, _numof_textures_3D,_numof_textures_cube;
		unsigned char _numof_rendertargets;
		unsigned char _numof_effect_matrices;
		//bool automaxtextures,automaxrendertargets;

		map<string,GLint> _uniform_locations;

		string _program_vertex,_program_geometry,_program_fragment;
		bool _requested_vert, _requested_geom, _requested_frag;

	public:    glLibShader(glLibFlagShader type);
	public:    ~glLibShader(void);
	
	private:          void glLibInternal_check_configurable(char profile) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if ( profile==1 && !_requested_vert) throw glLibError("This shader does not have a vertex profile!");
			if ( profile==2 && !_requested_geom) throw glLibError("This shader does not have a geometry profile!");
			if ( profile==3 && !_requested_frag) throw glLibError("This shader does not have a fragment profile!");
			#endif
		}
	//TODO: Make these output warning when not ending with newline
	//These should NOT give warning when already set.
	#ifndef GLLIB_ERROR_MODE_REPORT_WARNINGS
	public:    inline void              set_render_equation(string              new_render_equation) { glLibInternal_check_configurable(3);       _frag_render_equation =              new_render_equation; }
	public:    inline void               set_user_variables(string               new_user_variables) {                                              _all_user_variables =               new_user_variables; }
	public:    inline void         set_pre_vertex_transform(string         new_pre_vertex_transform) { glLibInternal_check_configurable(1);  _vert_pre_vertex_transform =         new_pre_vertex_transform; }
	public:    inline void             set_vertex_transform(string             new_vertex_transform) { glLibInternal_check_configurable(1);      _vert_vertex_transform =             new_vertex_transform; }
	public:    inline void        set_post_vertex_transform(string        new_post_vertex_transform) { glLibInternal_check_configurable(1); _vert_post_vertex_transform =        new_post_vertex_transform; }
	public:    inline void           set_texcoord_transform(string           new_texcoord_transform) { glLibInternal_check_configurable(3);    _frag_texcoord_transform =           new_texcoord_transform; }
	public:    inline void   set_extension_functions_vertex(string   new_extension_functions_vertex) { glLibInternal_check_configurable(1);   _vert_extension_functions =   new_extension_functions_vertex; }
	public:    inline void set_extension_functions_fragment(string new_extension_functions_fragment) { glLibInternal_check_configurable(3);   _frag_extension_functions = new_extension_functions_fragment; }
	public:    inline void                       set_header(string                       new_header) {                                                      _all_header =                       new_header; }
	#else
	private:   inline void glLibInternal_check_string_format(string str) {
			if ((str.size()!=0)&&(str[str.length()-1]!='\n')) {
				glLibWarning("Each shader element should end with a newline character.");
			}
		}
	public:    inline void              set_render_equation(string              new_render_equation) { glLibInternal_check_string_format(             new_render_equation); glLibInternal_check_configurable(3);       _frag_render_equation =              new_render_equation; }
	public:    inline void               set_user_variables(string               new_user_variables) { glLibInternal_check_string_format(              new_user_variables);                                              _all_user_variables =               new_user_variables; }
	public:    inline void         set_pre_vertex_transform(string         new_pre_vertex_transform) { glLibInternal_check_string_format(        new_pre_vertex_transform); glLibInternal_check_configurable(1);  _vert_pre_vertex_transform =         new_pre_vertex_transform; }
	public:    inline void             set_vertex_transform(string             new_vertex_transform) { glLibInternal_check_string_format(            new_vertex_transform); glLibInternal_check_configurable(1);      _vert_vertex_transform =             new_vertex_transform; }
	public:    inline void        set_post_vertex_transform(string        new_post_vertex_transform) { glLibInternal_check_string_format(       new_post_vertex_transform); glLibInternal_check_configurable(1); _vert_post_vertex_transform =        new_post_vertex_transform; }
	public:    inline void           set_texcoord_transform(string           new_texcoord_transform) { glLibInternal_check_string_format(          new_texcoord_transform); glLibInternal_check_configurable(3);    _frag_texcoord_transform =           new_texcoord_transform; }
	public:    inline void   set_extension_functions_vertex(string   new_extension_functions_vertex) { glLibInternal_check_string_format(  new_extension_functions_vertex); glLibInternal_check_configurable(1);   _vert_extension_functions =   new_extension_functions_vertex; }
	public:    inline void set_extension_functions_fragment(string new_extension_functions_fragment) { glLibInternal_check_string_format(new_extension_functions_fragment); glLibInternal_check_configurable(3);   _frag_extension_functions = new_extension_functions_fragment; }
	public:    inline void                       set_header(string                       new_header) { glLibInternal_check_string_format(                      new_header);                                                      _all_header =                       new_header; }
	#endif

	public:    inline void   set_numof_textures_1D(unsigned char new_number) {   _numof_textures_1D = new_number; }
	public:    inline void   set_numof_textures_2D(unsigned char new_number) {   _numof_textures_2D = new_number; }
	public:    inline void   set_numof_textures_3D(unsigned char new_number) {   _numof_textures_3D = new_number; }
	public:    inline void set_numof_textures_cube(unsigned char new_number) { _numof_textures_cube = new_number; }

	public:    inline void set_numof_render_targets(unsigned char new_number) { _numof_rendertargets = new_number; }

	public:    inline void set_numof_effect_matrices(unsigned char new_number) { _numof_effect_matrices = new_number; }

	//public:    inline void set_auto_numof_textures(bool value) { automaxtextures = value; }
	//public:    inline void set_auto_numof_render_targets(bool value) { automaxrendertargets = value; }
		
		/*
		def save_vertex( numbers=False,path=None) {
			shader_lines =  source.vert.split("\n")
			file_string = ""
			for string in  glLibInternal_get_shader_strings(shader_lines,"",numbers):
				file_string += string
				file_string += "\n"
			if path == None: file = open("vertex.vert","w")
			else:            file = open(path,"w")
			file.write(file_string)
			file.close()
		}
		def save_fragment( numbers=False,path=None) {
			shader_lines =  source.frag.split("\n")
			file_string = ""
			for string in  glLibInternal_get_shader_strings(shader_lines,"",numbers):
				file_string += string
				file_string += "\n"
			if path == None: file = open("fragment.frag","w")
			else:            file = open(path,"w")
			file.write(file_string)
			file.close()
		}*/
	private:
		/*def glLibInternal_get_shader_strings( shader_lines,parts,numbering) {
			first_indent = len(shader_lines[1])-len(shader_lines[1].strip())
			last_rel_indent = 0
			line_num = 1
			max_num_length = len(str(len(shader_lines)))
			strings = []
			for shader_line_indent in shader_lines:
				shader_line = shader_line_indent.strip()
				indent = len(shader_line_indent)-len(shader_line)
				rel_indent = indent-first_indent
				if rel_indent != last_rel_indent:
					if shader_line in parts:
						rel_indent = last_rel_indent
				line_num_str = str(line_num)
				string = ""
				if numbering:
					string += line_num_str + ": " + ((max_num_length-len(line_num_str))*" ")
				string += rel_indent*" "
				string += shader_line
				strings.append(string)
				line_num += 1
				last_rel_indent = rel_indent
			return strings
		void glLibInternal_format_matrix( mat) { return np.array(mat,dtype="f"); }
		}*/
	private:   GLint glLibInternal_get_location(const char* name, bool suppress_warnings=false);
	public:    void pass_texture(int number);
	//public:    void pass_texture(  glLibTexture1D* texture, int number);
	public:    void pass_texture(  glLibTexture2D* texture, int number);
	//public:    void pass_texture(  glLibTexture3D* texture, int number);
	public:    void pass_texture(glLibTextureCube* texture, int number);
	public:    inline void pass_float(string name, float value) { glUniform1f(glLibInternal_get_location(name.c_str()),value); }
	public:    inline void pass_int  (string name,   int value) { glUniform1i(glLibInternal_get_location(name.c_str()),value); }
	public:    inline void pass_bool (string name,  bool value) { glUniform1i(glLibInternal_get_location(name.c_str()),value); }
	public:    inline void pass_vec2(string name, float vec2[2]) { glUniform2f(glLibInternal_get_location(name.c_str()),vec2[0],vec2[1]); }
	public:    inline void pass_vec3(string name, float vec3[3]) { glUniform3f(glLibInternal_get_location(name.c_str()),vec3[0],vec3[1],vec3[2]); }
	public:    inline void pass_vec4(string name, float vec4[4]) { glUniform4f(glLibInternal_get_location(name.c_str()),vec4[0],vec4[1],vec4[2],vec4[3]); }
	public:    inline void pass_matrix2x2(string name, float* mat2x2) { glUniformMatrix2fv(glLibInternal_get_location(name.c_str()),1,false,mat2x2); }
	public:    inline void pass_matrix3x3(string name, float* mat3x3) { glUniformMatrix3fv(glLibInternal_get_location(name.c_str()),1,false,mat3x3); }
	public:    inline void pass_matrix4x4(string name, float* mat4x4) { glUniformMatrix4fv(glLibInternal_get_location(name.c_str()),1,false,mat4x4); }

	public:    void get_variable_float(const char* name, GLfloat* variable) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			glLibInternal_check_get_var(name);
			#endif
			glLibInternal_get_var_float(name,variable);
		}
	public:    void   get_variable_int(const char* name,   GLint* variable) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			glLibInternal_check_get_var(name);
			#endif
			glLibInternal_get_var_int(name,variable);
		}
	#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
	private:   void glLibInternal_check_get_var(const char* name) {
			if (!is_compiled) {
				throw glLibError("Shader not yet compiled!");
			}
			if (_uniform_locations.find(name)==_uniform_locations.end()) {
				throw glLibError("Uniform or attribute \""+string(name)+"\" could not be located.  Check that the shader is compiled correctly, is enabled, that the variable exists, and that it is used.");
			}
		}
	#endif
	private:   void glLibInternal_get_var_float(const char* name, GLfloat* variable) {
			glGetUniformfv(program_id,_uniform_locations[name],variable);
		}
	private:   void glLibInternal_get_var_int(const char* name, GLint* variable) {
			glGetUniformiv(program_id,_uniform_locations[name],variable);
		}

	public:    void use_prebuilt(glLibFlagShader type);
	public:    void set_compiler_version(glLibFlagShader shader, int version);

	private:   void glLibInternal_add_function_to_profile_data(unsigned long line_num, const char* name, char acceptable_profiles, char current_profile, void(*add_function_to_program)(string* arg));
	private:   void glLibInternal_add_variable_to_profile_data(unsigned long line_num, const char* name, char acceptable_profiles, char current_profile, void(*add_variable_to_program)(string* arg));
	private:   void glLibInternal_resolve_includes(void);
	private:   void glLibInternal_check_present_but_missing_dependency(string name, const char* missing);
	private:   void glLibInternal_add_functions_vert(void);
	private:   void glLibInternal_add_functions_geom(void);
	private:   void glLibInternal_add_functions_frag(void);
	private:   bool glLibInternal_was_requested_func(string feature);
	private:   bool glLibInternal_was_requested_var(string feature);
	private:   void glLibInternal_add_version(string* program, int version);
	private:   void glLibInternal_add_variable_list(string* program, const char* type, const char* variable, unsigned char number);
	private:   void glLibInternal_add_varyings(string* program);
	private:   void glLibInternal_add_textures(string* program);
	private:   void glLibInternal_generate_program_vert(void);
	private:   void glLibInternal_generate_program_geom(void);
	private:   void glLibInternal_generate_program_frag(void);

	public:    void       source_generate(void);
	public:    void            source_use(string source_vert="", string source_geom="", string source_frag="");
	public:    void               compile(void);
	public:    void                  link(void);
	public:    void generate_compile_link(void);
	public:    void             decompile(void);
	private:   void glLibInternal_compile(void);
	private:   void glLibInternal_set_log(GLuint object);

	public:    void   print_program_vertex(bool with_line_numbers=true);
	public:    void print_program_geometry(bool with_line_numbers=true);
	public:    void print_program_fragment(bool with_line_numbers=true);
	public:    void glLibInternal_print_program(const char* program, unsigned long program_length, bool with_line_numbers);
};

void glLibUseShader(glLibShader* shader);