/*
* Copyright 2021 Markus Heimerl, OTH Regensburg
* Licensed under CC BY-NC 4.0
*
* ANY USE OF THIS SOFTWARE MUST COMPLY WITH THE
* CREATIVE COMMONS ATTRIBUTION-NONCOMMERCIAL 4.0 INTERNATIONAL LICENSE
* AGREEMENTS
*/

function getBaseLog(x, y) {
  return Math.log(y) / Math.log(x);
}

function parseRulestring(rulestring){
	var res = rulestring.split("/");
	res[0] = res[0].substring(1);
	res[1] = res[1].substring(1);

	var bornString = "";
	if(res[0] == ""){
		bornString = "neighb == -9999";
	}else{
		var commasplit = res[0].split(",");
		for(var i = 0; i < commasplit.length; i++){
			bornString += "neighb == " + commasplit[i];
			if(i+1 < commasplit.length) bornString += " || ";
		}
	}

	var surviveString = "";
	if(res[1] == ""){
		surviveString = "neighb == -9999";
	}else{
		var commasplit = res[1].split(",");
		for(var i = 0; i < commasplit.length; i++){
			surviveString += "neighb == " + commasplit[i];
			if(i+1 < commasplit.length) surviveString += " || ";
		}
	}

	return [bornString, surviveString];
}


cellularautomaton();
function cellularautomaton(){

		const vertexshadersource = `
			attribute vec2 a_vertexdata;
			void main(){
				gl_Position = vec4(a_vertexdata.xy, 0.0, 1.0);
			}
		`;
		const drawfragmentshadersource = `
			precision lowp float;
			uniform sampler2D texture;
			uniform vec2 dimensions;
			void main(){
				gl_FragColor = texture2D(texture, gl_FragCoord.xy/dimensions);
			}
		`;
		const logicfragmentshadersource = `
			precision lowp float;
			uniform sampler2D texture;
			uniform vec2 dimensions;

			int getValue(int x, int y){
				return (texture2D(texture, (gl_FragCoord.xy + vec2(x,y))/dimensions).g) > 0.0 ? 1 : 0;
			}

			void main(){
				int neighb = getValue(1,0) + getValue(1,-1) + getValue(0,-1) + getValue(-1,-1) + getValue(-1, 0) + getValue(-1, 1) + getValue(0, 1) + getValue(1, 1);
				int current = getValue(0,0);
				if(` + parseRulestring($("#cellRuleInput").val())[0] + `){gl_FragColor = vec4(0.1, 0.9, 0.7, 1.0);}
				else if(` + parseRulestring($("#cellRuleInput").val())[1] + `){gl_FragColor = vec4(current, current, current, 1.0);}
				else{gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);}
			}
		`;


	var running = true;
	var scaleofonepixel = 2;

	$("#cellularautomatoncanvas")[0].width = Math.pow(2, Math.floor(getBaseLog(2, $(window).width() * 0.88)));
	var gl = $("#cellularautomatoncanvas")[0].getContext("webgl");
	
	// local copy of library https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
	const r3webgl = {...parentr3webgl};

	r3webgl.gl = gl;

	var programs = {
		drawprogram: r3webgl.createShaderProgram(vertexshadersource, drawfragmentshadersource),
		logicprogram: r3webgl.createShaderProgram(vertexshadersource, logicfragmentshadersource)
	};

	var drawuniformlocations = r3webgl.getUniformLocations(programs.drawprogram, ["dimensions"]);
	var logicuniformlocations = r3webgl.getUniformLocations(programs.logicprogram, ["dimensions"]);

	var textures = {
		front: createTexture(true),	
		back: createTexture(false)
	}

	window.addEventListener("orientationchange", function() {
		toggleRunning();
		setTimeout(function(){
			var newwidth = Math.pow(2, Math.floor(getBaseLog(2, $(window).width() * 0.88)));
			$("#cellularautomatoncanvas")[0].width = newwidth;
			textures.front = createTexture(true);
			textures.back = createTexture(false);
			toggleRunning();
		}, 200);
	});

	function createTexture(noise){
		var tex = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, tex);
		var data = [];
		if(noise){
		for(var i = 0; i < gl.canvas.width*gl.canvas.height; i++){var col = Math.round(Math.random()*Math.random()*0.8)*255; data.push(col, col, col);}
		}else{
			for(var i = 0; i < gl.canvas.width*gl.canvas.height; i++){data.push(0, 0, 0);}
		}
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.canvas.width/scaleofonepixel, gl.canvas.height/scaleofonepixel, 0, gl.RGB, gl.UNSIGNED_BYTE, new Uint8Array(data));
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
		return tex;
	}

	$("#cellToggleButton").click(toggleRunning);
	function toggleRunning(){
		if(running){
			running = false;
			$("#cellToggleButton").html("Start and apply");
		}else{
			running = true;
			const logicfragmentshadersource = `
				precision lowp float;
				uniform sampler2D texture;
				uniform vec2 dimensions;

				int getValue(int x, int y){
					return (texture2D(texture, (gl_FragCoord.xy + vec2(x,y))/dimensions).g) > 0.0 ? 1 : 0;
				}

				void main(){
					int neighb = getValue(1,0) + getValue(1,-1) + getValue(0,-1) + getValue(-1,-1) + getValue(-1, 0) + getValue(-1, 1) + getValue(0, 1) + getValue(1, 1);
					int current = getValue(0,0);
					if(` + parseRulestring($("#cellRuleInput").val())[0] + `){gl_FragColor = vec4(0.1, 0.9, 0.7, 1.0);}
					else if(` + parseRulestring($("#cellRuleInput").val())[1] + `){gl_FragColor = vec4(current, current, current, 1.0);}
					else{gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);}
				}
			`;

			programs.logicprogram = r3webgl.createShaderProgram(vertexshadersource, logicfragmentshadersource);
			drawuniformlocations = r3webgl.getUniformLocations(programs.drawprogram, ["dimensions"]);
			logicuniformlocations = r3webgl.getUniformLocations(programs.logicprogram, ["dimensions"]);
			$("#cellToggleButton").html("Stop");
		}
	}

	$("#cellRandButton").click(function(){
		textures.front = createTexture(true);
	});


	start();
	setTimeout(toggleRunning, 200);
	function start(){

		const quadvert = [1,  1, -1,  1, 1, -1, -1, -1,];
		var quadbuffer = r3webgl.createBuffer(gl.ARRAY_BUFFER, quadvert);
		r3webgl.connectBufferToAttribute(gl.ARRAY_BUFFER, quadbuffer, 0, 2, true);

		var fb = gl.createFramebuffer();

		mainloop();
		function mainloop(){
			if(running){
					draw();
					play();
			}
			requestAnimationFrame(mainloop);
			//setTimeout(mainloop, 30);
		}

		function draw(){
			gl.bindFramebuffer(gl.FRAMEBUFFER, null);
			gl.bindTexture(gl.TEXTURE_2D, textures.front);
			gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
			gl.useProgram(programs.drawprogram);
			gl.uniform2f(drawuniformlocations.dimensions, gl.canvas.width, gl.canvas.height);
			gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
		}

		function play(){
			gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
			gl.viewport(0, 0, gl.canvas.width/scaleofonepixel, gl.canvas.height/scaleofonepixel);
			gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textures.back, 0);
			gl.useProgram(programs.logicprogram);
			gl.uniform2f(logicuniformlocations.dimensions, gl.canvas.width/scaleofonepixel, gl.canvas.height/scaleofonepixel);
			gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
		
			var swap = textures.back;
			textures.back = textures.front;
			textures.front = swap;
		}
	}
}