/**
 * 	Kepler - Core
 **/
 
import {vector2, vector3, matrix4 } 	from './math.js';

import sampler2D 						from './sampler2D.js';

import samplerCube 						from './samplerCube.js';


class uniform_template{
	
	name;

	value;

	prepared = false;
	
}
 
 
/**
 * Shader Object
 * this class takes care of the shader management for you
 */
class shader {

	program;
		
	uniform_templates 	= new Array();
		
	uniforms 			= new Array();

	attributes 			= new Array();
		
	url;
		
	samplerId 			= 0;

	libraryContent 		= false;

	librarys 			= new Array();
		
	pragmas 			= new Array();
		
	rawShader;

	programInfo;
		
	compiled 			= false;
		
	blend 				= 0;


	/**
	 * set viewport
	 * @param {(viewport)} viewport
	**/
	setViewport( viewport ){
		
		this.viewport 		= viewport;

		this.gl 			= viewport.gl;
	
	}
	
	createFromFile( url ) {
		
		this.url = url;
		
		var shaderText 				= kepler.resources.loadTextFileSynchronous(url);
		
		var splitShader 			= shaderText.split("// #keplerEngine - Split");
		
		this.vertexShaderText 		= splitShader[0];

		this.fragmentShaderText 	= splitShader[1];
		
		//add pragma's
		for(var c = 0; c<this.pragmas.length; c++) {
			var currentPragma = this.pragmas[c];
			
			switch (currentPragma.type) {
				case "define":
				
					var pragma = "#define " + currentPragma.name + " " + currentPragma.value + '    \n  ';
					
						this.fragmentShaderText = pragma + this.fragmentShaderText;
						this.vertexShaderText = pragma + this.vertexShaderText;
					
				break;
			}
		}
		
		var allLines = this.fragmentShaderText.split("\n");
		
		for(var b = 0; b<allLines.length; b++) {

			var currentLine = allLines[b];
			
			if( currentLine.includes("#include") ) {
				
				console.log("include file", currentLine);
				
				var fileName = currentLine.replace("#include",'').replace('"','').replace('"','').replace(' ','').replace(' ','').replace(' ','');

				var fileContent = kepler.resources.loadTextFileSynchronous("shaders/"+fileName);
			
				//this.fragmentShaderText = fileContent + this.fragmentShaderText;
				
				allLines[b] = fileContent;

				//console.log(allLines[b]);
				
			}

		}
	
		this.fragmentShaderText = allLines.join("\n");
		
		var shader_version_3 = this.fragmentShaderText.includes("#version 300 es");
		
		if( shader_version_3 ) {
							
			this.fragmentShaderText 	= this.fragmentShaderText.replace("#version 300 es",'');

			this.fragmentShaderText 	= this.fragmentShaderText.replace("#version 300 es",'');

			this.vertexShaderText 		= this.vertexShaderText.replace("#version 300 es",'');

			this.vertexShaderText 		= this.vertexShaderText.replace("#version 300 es",'');
			
			this.fragmentShaderText 	= "#version 300 es \n" + this.fragmentShaderText;

			this.vertexShaderText 		= "#version 300 es \n" + this.vertexShaderText;
		
		}
		
		for (var i = 0; i < 10; i++) {

			this.fragmentShaderText = this.fragmentShaderText.replace("#include",'//');
			
		}
		
		this.vertexShaderText = this.vertexShaderText.replace("#include",'//');
		
		this.precompile();
	
	}

	getTypeName( index ) {

		switch( index ) {

			case 35676:

				return "mat4";

			break;

			case 5126:

				return "float";

			break;

			case 35665:

				return "vec3";

			break;

			case 35678:

				return "sampler2D";

			break;

			case 35680:

				return "samplerCube";

			break;


			default:

			return index;
			

		}

	}

	extractUniforms() {


		const numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
		for (let i = 0; i < numUniforms; ++i) {

			const info = gl.getActiveUniform(this.program, i);

			//console.log("name:", info.name, "type:", info.type, "size:", info.size, "typeName" );

			info.typeName = this.getTypeName(info.type);

			var uniform = {};
			
			uniform.type = this.getTypeName(info.type);
			uniform.name = info.name;
			
			this.addUniform(uniform);

		}


	}

	precompile() {

		this.gl = gl;

		this.isCompiled 	= true;
		
		this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
		this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
		
		this.gl.shaderSource(this.vertexShader, this.vertexShaderText);
		this.gl.compileShader(this.vertexShader);
		
		this.gl.shaderSource(this.fragmentShader, this.fragmentShaderText);
		this.gl.compileShader(this.fragmentShader);
		
		this.program = this.gl.createProgram();



		
		this.gl.attachShader(this.program, this.vertexShader);
		this.gl.attachShader(this.program, this.fragmentShader);



		
		if (!this.gl.getShaderParameter(this.vertexShader, this.gl.COMPILE_STATUS)) {
			alert(this.gl.getShaderInfoLog(this.vertexShader));
			return null;
		}
		
		if (!this.gl.getShaderParameter(this.fragmentShader, this.gl.COMPILE_STATUS)) {
			alert(this.gl.getShaderInfoLog(this.fragmentShader));
			return null;
		}

		this.gl.linkProgram(this.program);

		if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
			alert("Could not initialise shaders");
		}

		this.gl.useProgram(this.program);


		const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);

		for (let i = 0; i < numAttribs; ++i) {

			const info = gl.getActiveAttrib(this.program, i);

			//console.log("attribute 	name:", info.name, "type:", info.type, "size:", info.size);

			this.addAttribute(info.name);

		}

		this.extractUniforms();
			


		
		for(var c = 0; c<this.attributes.length; c++) {
			
			var attribute = this.attributes[c];
			
			this.bindAttribute( attribute );
			
		}
		

	}
	
	compile(){

		this.bindUniforms();
		
		this.compiled = true;
	}

	/**
	 * create shader library object
	 * @param {(String)} fileUrl of shader file.
	**/
	createLibraryFomFile( fileUrl ){

		this.libraryContent =  kepler.resources.loadTextFileSynchronous(fileUrl);

	};


	/**
	 * Add library to shader object
	 * @param {(shaderObject)} shader.
	 * @param {(int)} type (pixel shader = 0, vertex shader = 1).
	**/
	addLibrary( shader, type ){
		var library = {};
		library.content = shader.libraryContent;
		library.type = type;
		library.uniforms = shader.uniforms;
		
		//this.pragmas = pragmas.concat(shader.pragmas);
		this.librarys.push(library);
	};


	/**
	 * Define pragma's
	 * @param {(String)} name.
	 * @param {(string)} value.
	**/
	definePragma(name, value) {
		var pragma = {};
		pragma.type = "define";
		pragma.name = name;
		pragma.value = value;
		
		this.pragmas.push(pragma);
	};


	/**
	 * Get uniform by name
	 * @param {(string)} name.
	**/
	getUniformByName( name ) {

		var uniforms = this.uniforms;

		for(var c = 0; c < uniforms.length; c++) {
			
			var uniform = uniforms[c];
			
			if( uniform.name == name ){

				return uniform.uniformLocation;

			}
		}
		
		return false;
		
		console.log("could not locate buffer :"+name);
	};


	asddsaas = 0;
	/**
	 * Update uniform variable
	 * @param {(uniformObject)} uniform.
	**/
	updateUniform(uniform) {
		var uniformLocation = uniform.uniformLocation;
		var value = uniform.value;

		switch(uniform.type) {
			case "sampler2D":

				var sampler = value;
				var type = sampler.type;
				
				//if(asddsaas==0) 
				//console.log(sampler);
				this.gl.activeTexture(this.gl.TEXTURE0 + sampler.id );

				//if transparent
				if (!sampler.useAlpha) {
					this.gl.disable(this.gl.BLEND);
					this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
					this.gl.enable(this.gl.DEPTH_TEST);
				} else {
					this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
					this.gl.enable(this.gl.BLEND);
					this.gl.disable(this.gl.DEPTH_TEST);
				}

				this.gl.disable(this.gl.BLEND);
				this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
				this.gl.enable(this.gl.DEPTH_TEST);
		
				//this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
				//this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
				//this.gl.enable(this.gl.BLEND); 	
				
			
				//for(var b = 0; b<sampler.textures.length;b++) {
					
					var texture = sampler.getTexture();
					var glTexture = texture.glTexture;
					
					this.gl.bindTexture(sampler.target, glTexture);
					this.gl.uniform1i(uniformLocation, sampler.id);
					
				//}
				//this.gl.bindTexture(this.gl.TEXTURE_2D, null);
			break;
			case "samplerCube":
				//console.log("hiiier");
				
				var sampler = value;
				
				//if(asddsaas==0) {
				//	console.log(sampler);
				//	asddsaas++;
				//}
				
				var type = sampler.type;
				
				
				this.gl.activeTexture(this.gl.TEXTURE0 + sampler.id);

				//if transparent
				if (!sampler.useAlpha) {
					this.gl.disable(this.gl.BLEND);
					this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
					this.gl.enable(this.gl.DEPTH_TEST);
				} else {
					this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
					this.gl.enable(this.gl.BLEND);
					this.gl.disable(this.gl.DEPTH_TEST);
				}

				this.gl.disable(this.gl.BLEND);
				this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
				this.gl.enable(this.gl.DEPTH_TEST);
		
				//this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
				//this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
				//this.gl.enable(this.gl.BLEND); 	
				
			
				//for(var b = 0; b<sampler.textures.length;b++) {
					
					var texture = sampler.cubeTexture;
					var glTexture = texture;
					
					this.gl.bindTexture(sampler.target, glTexture);
					this.gl.uniform1i(uniformLocation, sampler.id);
					
				//}
				//this.gl.bindTexture(this.gl.TEXTURE_2D, null);

				
			break;
			case "float":
				this.gl.uniform1f(uniformLocation, value);
			break;
			case "vec2":
				this.gl.uniform2f(uniformLocation, parseFloat(value[0]), parseFloat(value[1]) );
			break;
			case "vec3":
			
				this.gl.uniform3f(uniformLocation, parseFloat(value[0]), parseFloat(value[1]), parseFloat(value[2])  );
			break;
			case "vec4":
				this.gl.uniform4f(uniformLocation, parseFloat(value[0]), parseFloat(value[1]), parseFloat(value[2]), parseFloat(value[3]));
			break;
			case "mat2":
				this.gl.uniformMatrix2fv(uniformLocation, false, matrix2.getMatrixElements(value));
			break;
			case "mat3":
				this.gl.uniformMatrix3fv(uniformLocation, false, matrix3.getMatrixElements(value));
			break;
			case "mat4":
				this.gl.uniformMatrix4fv(uniformLocation, false, matrix4.getMatrixElements(value));
			break;
			case "int":
			
			break;
			case "ivec2":
			
			break;
			case "array":
				var arrayType = uniform.arrayType;
				//console.log(arrayType);
				switch(arrayType) {
					case "float":
						this.gl.uniform1fv(uniformLocation, value );
					break;
					case "vec2":
						this.gl.uniform2fv(uniformLocation, value );
					break;
					case "vec3":
						var flat = [];

						for(var c = 0; c<value.length; c++) {
							var v = value[c];
							flat.push(v[0], v[1], v[2]);
						}
					
						this.gl.uniform3fv( uniformLocation, flat );
					break;
					case "vec4":
					
						var flat = [];

						for(var c = 0; c<value.length; c++) {
							var v = value[c];
							flat.push(v[0], v[1], v[2], v[3]);
						}
					
						this.gl.uniform4fv( uniformLocation, flat );
					break;
				}
			break;
		} 
	}



	setUniform( name, value ) {
		//console.log(name, value);

		for(var c = 0; c < this.uniforms.length; c++) {

			if( this.uniforms[c].name == name ) {
				
				this.uniforms[c].value = value;
				
				
				if(this.compiled && this.uniforms[c].uniformLocation){

					this.updateUniform( this.uniforms[c] );

				}

			}
			
		}
		

		
	}

	/**
	 * Set uniform variable
	 * @param {(String)} name.
	 * @param {(String)} value.
	 * @param {(boolean)} noError.
	**/
	/*
	setUniform(name, value, update) {
		
		var a = 0;
		var uniforms = this.uniforms;

		for(var c = 0; c < uniforms.length; c++) {
			if(uniforms[c].name == name) {
				
				if(uniforms[c].type == "sampler2D") {
					//console.log(value);
					value.bind(this);
				}
				
				if(uniforms[c].type == "samplerCube") {
					//console.log(value);
					value.bind(this);
				}
				
				uniforms[c].value = value;
				
				if(update)
					this.updateUniform(uniforms[c]);
				
				a++;
			}
		}
		
		if(this.libraryContent) {
			var uniform = {};
			
			uniform.name = name;
			uniform.value = value;
			
			uniforms.push(uniform)
			a++;
		}
		
		if(a == 0)
			console.log('uniform '+name+' not found in shader ', this.url, this);
	}
*/
	/**
	 * Set uniform variable
	 * @param {(String)} name.
	 * @param {(String)} value.
	 * @param {(boolean)} noError.
	**/
	
	addUniform( uniform ) {  
	
		uniform.value 		= this.createEmptyValue(uniform);

		uniform.objectType 	= 'uniform';
		
		this.uniforms.push( uniform );
		
	}
	
	
	/**
	 * Set uniform variable
	 * @param {(String)} name.
	 * @param {(String)} value.
	 * @param {(boolean)} noError.
	**/
	
	bindUniforms() { 
	
		//console.log("bind uniforms");
	
		var oldUniforms = this.uniforms;
		this.uniforms = [];
		
		this.gl.useProgram(this.program);
		
		for(var c = 0; c<oldUniforms.length; c++) {
			
			var uniform = oldUniforms[c];
			//console.log("huh", uniform);

			
			//this.gl.useProgram(this.program);
		
		//if(!uniform.compiled) {
			
			//console.log("bind uniform", uniform.name);
			
			uniform.uniformLocation = this.gl.getUniformLocation(this.program, uniform.name);
			
			//console.log("uniformLocation", uniform.name, uniform.uniformLocation);
	
			if(!uniform.value)
			uniform.value = this.createEmptyValue(uniform);
		
			uniform.objectType = 'uniform';
			
			if(uniform.type == "sampler2D") {
				
				var originalSampler = uniform.value; 
				var sampler = new sampler2D();

				//console.log('uniform loading', uniform);
				
				
				var texture = originalSampler.texture;
	
				if(!texture.loaded && !originalSampler.isFramebuffer && texture.dataType != "int") {
					sampler.texture = kepler.resources.getLoadedTexture( texture.name, this.viewport );	
				}
				
				if(originalSampler.isFramebuffer)
				{
					sampler.datatype = originalSampler.datatype;
					sampler.format = originalSampler.format;
					sampler.internalFormat = originalSampler.internalFormat;
						
					sampler.filter = originalSampler.filter;
					sampler.texture = originalSampler.texture;
					
				}
				
				if(texture.dataType == "int") {
					sampler.texture = originalSampler.texture;
					sampler.texture.setViewport( this.viewport );
				}
				
				//sampler.samplerID = ++this.samplerId;
				//console.log("new texture", kepler.resources, texture.name, sampler);
				
				sampler.setViewport( this.viewport );
				
				if(!sampler.binded) {
				
				}
				

				
				
				sampler.bind(this);
				//console.log("new sampler", sampler);
				uniform.value = sampler;
			}
		
			if(uniform.type == "samplerCube") {
				var sampler = uniform.value;
				
				sampler.setViewport( this.viewport );
				
				//sampler.samplerID = ++this.samplerId;
				
				var textures = sampler.textures;
				
				for(var b = 0; b<textures.length;b++) {
					//console.log("cube texture", textures[c]);
					//console.log(sampler.textures[c].texture.name);
					var texture = textures[b];
					
					if(!texture.loaded) {
						sampler.textures[b].texture = kepler.resources.getLoadedTexture(sampler.textures[b].texture.name, this.viewport );
					}
				}
				
				if(!sampler.binded) {
				
				}
				
				sampler.bind(this);
				
				uniform.value = sampler;
			}
			
			uniform.compiled = true;
			
			var exist = this.getUniformByName(uniform.name);

			if(uniform.uniformLocation != null && !exist){
				
				//console.log("add", uniform.name);
				this.uniforms.push(uniform);
				
				//console.log("update", uniform.name);
				this.updateUniform(uniform);
				
			}
			
		}
	
			//if(uniform.uniformLocation)
			//	this.uniforms.push(uniform);
				
		//}
	}


	/**
	 * Create a empty object based on the type of the uniform object
	 * @param {(uniformObject)} uniform.
	**/
	createEmptyValue(uniform) {
		switch(uniform.type) {
			case "sampler2D":
				var def = kepler.resources.getTexture("default");
				var sampler = new sampler2D( kepler );
				
				sampler.addTexture(def);
				return sampler;
			break;
			case "samplerCube":
				
				var dataArray = [];
				var width = 512;
				var height = 512;
				
				for( var y = 0; y < height; y++ )
				{
					for( var x = 0; x < width; x++ )
					{
						dataArray.push(x / width);
						dataArray.push( y / width);

						dataArray.push(  x  / width);
						dataArray.push(  y * x / width); 
					}
				}
			
				var def = kepler.textureFromArray(dataArray, width, height, true);
			
				//var def = kepler.resources.getTexture("default");
				 var sampler = new samplerCube( );

				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_POSITIVE_X);
				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_NEGATIVE_X);
				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_POSITIVE_Y);
				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y);
				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_POSITIVE_Z);
				 sampler.addTexture(def, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z);
				 
				return sampler;
			break;
			
			
			case "float":
				return 0.0;
			break;
			case "vec2":
				return new vector2(0,0);
			break;
			case "vec3":
				return new vector3(0,0,0);
			break;
			case "vec4":
				return new vector4(0,0,0,0);
			break;
			case "mat2":
				return matrix2.identity();
			break;
			case "mat3":
				return matrix3.identity();
			break;
			case "mat4":
				return matrix4.identity();
			break;
			case "int":
				return 0;
			break;
			case "ivec2":
			
			break;
			case "array":
				/*
				var arrayType = uniform.arrayType;
				switch(arrayType) {
				
					case "vec2":
						//this.gl.uniform2fv(uniformLocation, value );
					break;
					case "vec3":
						//this.gl.uniform3fv(uniformLocation, value );
					break;
					case "vec4":
						//this.gl.uniform4fv(uniformLocation, value );
					break;
				}
				*/
			break;
		} 
	}

	/**
	 * Update shader
	**/
	update(viewport) {
		
		
		
		
		if( !this.compiled ) {
			
			this.setViewport( viewport );
			
			this.gl.useProgram(this.program);
			
			this.compile();
			
		}
		
		this.gl.useProgram(this.program);

		var uniforms = this.uniforms;
		
		for(var c = 0; c < uniforms.length; c++) {
			var uniform = uniforms[c];
			this.updateUniform(uniform);
		}
	}


	/**
	 * Get Attribute by name
	 * @param {(String)} name.
	**/
	getAttributeByName(name) {
		var attributes = this.attributes;
		
		for(var c = 0; c < attributes.length; c++) {
			var attribute = attributes[c];

			if(attribute.name == name)
				return attribute.uniformLocation;
		}

		console.log("could not locate buffer :"+name);
	}
	
	addAttribute( name ) {
		
		var attr = {};
		attr.name = name;
		
		this.attributes.push( attr );
	}

	/**
	 * add attribute to shader.
	 * @param {(String)} name.
	**/
	
	bindAttribute( attr ) {
		//var attr = {};
		
		this.gl.useProgram(this.program);
		//console.log('this.gl.getAttribLocation()', this.program, name);
		
		//attr.name = name;
		//console.log("bind", this.program, attr.name);
		attr.uniformLocation = this.gl.getAttribLocation(this.program, attr.name);
		
		if(attr.uniformLocation != null) {
			
			this.gl.enableVertexAttribArray( attr.uniformLocation );
		}
		
		if(typeof(attr.uniformLocation)!='number')
			console.log("attribute '"+attr.name+"' Does not exist in shader ",this);
		
		//else
		//	console.log("added attribute",attr);
		
		//this.attributes.push( attr );
	}

	
	
}


export {shader as default};


	/**
	 * number padding
	 * @param {(int)} number.
	**/
	function pad2(number) {
		return (number < 10 ? '0' : '') + number
	}


	/**
	 * Contains
	 * @param {(object)} obj.
	**/
	Array.prototype.contains = function(obj) {
		var i = this.length;
		while (i--) {
			if (this[i] === obj) {
				return true;
			}
		}
		return false;
	};


	/**
	 * Between
	 * @param {(string)} prefix.
	 * @param {(string)} suffix.
	**/
	String.prototype.between = function(prefix, suffix)  {
	  s = this;
	  var i = s.indexOf(prefix);
	  if (i >= 0) {
		s = s.substring(i + prefix.length);
	  }
	  else {
		return '';
	  }
	  if (suffix) {
		i = s.indexOf(suffix);
		if (i >= 0) {
		  s = s.substring(0, i);
		}
		else {
		  return '';
		}
	  }
	  return s;
	};


	/**
	 * Chech if object is array
	 * @param {(obj)} object.
	**/
	function isArray(obj) {
		return obj.constructor == Array;
	}

	
	/**
	 * add string to string at a particular index
	 * @param {(String)} src.
	 * @param {(int)} index.
	 * @param {(String)} str.
	**/
	function insertAt(src, index, str) {
		return src.substr(0, index) + str + src.substr(index)
	}

var ttt= 0;
var samplerId = 0;