(function($) {
"use strict"

var canv // canvas
var gl // webgl context
var programInfo // vector and fragment shaders compiled into program
var hedge_program // vector and fragment shaders compiled into program

var arrays = {
	position: [-1, -1, 0, 1, -1, 0, -1, 1, 0, -1, 1, 0, 1, -1, 0, 1, 1, 0]
}

var cell = null // array of cells representing the map internally
var mincellside = 60 // minimum cell size so that a finger can fit into it
var cellside = 60 // side of one cell in pixels
var hwratio = 0.15 // halfwall ratio
var halfwall = 0 // half of wall's width in pixels
var mazemap = { tex: null, res: [7, 7] } // map of the maze
var playercell = 0 // index of the cell the player is currently in right now
var prevcell = 0 // index of the previous playercell
var entrycell = 0 // index of the entry cell
var exitcell = 0 // index of exit cell
var difficulty = 0 // don't limit difficulty
var d2side = [0, 7, 10, 12] // difficulty level to square maze's side
var playersprite = $("#playersprite")
var exitsprite = $("#exitsprite")
var scs = 1 // scene scale (a.k.a global font-size)
var textures // textures to be used by webgl
var hedges // map data to be displayed
var hedges_program // compiled webgl shaders
var bufferInfo // buffer to be displayed
var socket // websocket connection
var myid = null // id that will be assigned by the server
var players = [] // players information
var last_render_time = Date.now() // previous render time

function clearmaze() {
	var ncells = mazemap.res[0] * mazemap.res[1];
	for(var i = 0; i < ncells; ++i) {
		cell[i] &= ~240;
	}
	prevcell = entrycell;
	playercell = entrycell;
	cell[playercell] |= 224;
	requestAnimationFrame(render);
};

function nextmaze() {
	mkmap();
};

function mkmaze(cell, res) {
	var ncells = res[0] * res[1];
	var cur = Math.floor(Math.random() * (ncells + 1)); // current cell ind
	var stack = [];
	stack[0] = cur; // add first cell to the stack
	var visitorder = [1, 2, 4, 8]; // order in which to visit neighbors,
	// where 1 is top, 2 is right, 4 is bottom, 8 is left (css order)

	while(stack.length) {
		// try to visit a nearby cell. We randomly an order in which
		// we try to visit the cells by shuffling the visitorder array:
		visitorder.sort(function() { return 0.5 - Math.random() });
		var visited = 0; // have we visited any cell yet?
		for(var i = 0; i < 4; ++i) {
			// try to visit selected cell
			switch(visitorder[i]) {
			case 1:
				// visiting the cell above if possible
				if(cur + res[0] >= ncells ||
						cell[cur + res[0]]) {
					// can't visit cell above
					break;
				}
				cell[cur] |= 1; // remove top wall
				cur += res[0]; // make top cell the current one
//				cell[cur] |= 4; // remove bottom wall
				stack.push(cur); // push current cell
				visited = 1;
				break;
			case 2:
				// visiting the cell to the right
				if(cur % res[0] == res[0] - 1
							|| cell[cur + 1]) {
					break;
				}
				cell[cur] |= 2;
				++cur;
//				cell[cur] |= 8;
				stack.push(cur);
				visited = 1;
				break;
			case 4:
				// visiting bottom cell
				if(cur - res[0] < 0 || cell[cur - res[0]]) {
					// we are in the bottom, no way down
					break;
				}
//				cell[cur] |= 4;
				cur -= res[0];
				cell[cur] |= 1;
				stack.push(cur);
				visited = 1;
				break;
			case 8:
				// visiting left cell
				if(!(cur % res[0]) || cell[cur - 1]) {
					break;
				}
//				cell[cur] |= 8;
				--cur;
				cell[cur] |= 2;
				stack.push(cur);
				visited = 1;
				break;
			}
			if(visited) {
				break;
			}
		}
		if(!visited) {
			// we could not visit any unvisited cell from here,
			// so we need to go to previous cell and start over:
			cur = stack.pop();
		}
	}
}

// set cellside and automatically adjust halfwall:
function setcside(npixels) {
	cellside = npixels
	halfwall = Math.floor(cellside * hwratio);
}

// make map and init the game
function mkmap() {

	// we must recalculate fieldsize after above adjustments
	var fieldsize = mazemap.res[0] * mazemap.res[1];

	cell = new Uint8Array(fieldsize);
	// following the opengl coordinate system, our first cell will be
	// in the bottom left corner.
	// generate the maze now:
//	console.log(mazemap.res)
//	mkmaze(cell, mazemap.res);
	entrycell = Math.floor(mazemap.res[1] * 0.5) * mazemap.res[0];
	exitcell = entrycell - 1;
//	cell[entrycell] |= 232; // mark entry cell as such
//	cell[exitcell] |= 2; // exit cell
	playercell = prevcell = entrycell;

// 32 top->right
// 64 right->bottom
// 96 bottom->left
// 128 left->top
// 160 top->bottom
// 192 left->right
// 224 player's position

	requestAnimationFrame(render);
}

// move player's sprite to where the player currently is on the map
function moveplayersprite() {
}

// creates path on player's position, connecting prevcell and tocell:
function trypath(tocell) {
	if(tocell == playercell) return 0;
	if(tocell == prevcell) { // move back
		cell[playercell] &= ~224;
		return 1;
	}
	var fromdir = prevcell - playercell;
	var todir = tocell - playercell;
	if(fromdir == mazemap.res[0]) { // moving from top
		if(todir == 1) { // top->right:
			if(!(cell[playercell] & 2)) return 0; // wall blocking
			cell[playercell] &= ~240;
			cell[playercell] |= 32;
			return 1;
		} else if(todir == -mazemap.res[0]) { // top->down:
			if(!(cell[playercell] & 4)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 160;
			return 1;
		} else if(todir == -1) { // top->left:
			if(!(cell[playercell] & 8)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 144;
			return 1;
		}
	} else if(fromdir == 1) { // moving from right
		if(todir == mazemap.res[0]) { // right->top
			if(!(cell[playercell] & 1)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 48;
			return 1;
		} else if(todir == -mazemap.res[0]) { // right->bottom
			if(!(cell[playercell] & 4)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 64;
			return 1;
		} else if(todir == -1) { // right->left
			if(!(cell[playercell] & 8)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 208;
			return 1;
		}
	} else if(fromdir == -mazemap.res[0]) { // moving from bottom
		if(todir == mazemap.res[0]) { // bottom->top
			if(!(cell[playercell] & 1)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 176;
			return 1;
		} else if(todir == 1) { // bottom->right
			if(!(cell[playercell] & 2)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 80;
			return 1;
		} else if(todir == -1) { // bottom->left
			if(!(cell[playercell] & 8)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 96;
			return 1;
		}
	} else if(fromdir == -1 || !fromdir) { // moving from left
		if(todir == mazemap.res[0]) { // left->top
			if(!(cell[playercell] & 1)) return 0; // wall blocking
			cell[playercell] &= ~240;
			cell[playercell] |= 128;
			return 1;
		} else if(todir == 1) { // left->right
			if(!(cell[playercell] & 2)) return 0;
			cell[playercell] &= ~240;
			cell[playercell] |= 192;
			return 1;
		} else if(todir == -mazemap.res[0]) { // moving from left down:
			if(!(cell[playercell] & 4)) return 0; // wall blocking
			cell[playercell] &= ~240;
			cell[playercell] |= 112;
			return 1;
		}
	}
	return 0;
}

function ondrag(e) {
	// mouse position relative to corner of the 0th cell:
	var mousepos = [e.px_current_x - canv.offset().left - halfwall,
		e.px_current_y - canv.offset().top - halfwall];
	if(mousepos[0] < 0 || mousepos[1] < 0 ||
		mousepos[0] > mazemap.res[0] * cellside ||
		mousepos[1] > mazemap.res[1] * cellside) {
		return;
	}
	// invert y so that our cell 0 is in the bottom left corner:
	mousepos[1] = mazemap.res[1] * cellside - mousepos[1];
	// determine the index of the cell the mouse is in:
	var mousecell = Math.floor(mousepos[1] / cellside) * mazemap.res[0] +
		Math.floor(mousepos[0] / cellside);
	if(playercell == exitcell) return;
	if(trypath(mousecell)) {
		if(prevcell == mousecell) { // going back
			switch(cell[prevcell] & 240) {
			case 32:
			case 144:
			case 160: prevcell = prevcell + mazemap.res[0]; break;
			case 64:
			case 48:
			case 208: prevcell = prevcell + 1; break;
			case 96:
			case 176:
			case 80: prevcell = prevcell - mazemap.res[0]; break;
			case 128:
			case 192:
			case 112: prevcell = prevcell - 1; break;
			}
			if(mousecell == entrycell) {
				prevcell = entrycell;
			}
		} else {
			prevcell = playercell;
		}
		playercell = mousecell;
		cell[playercell] |= 224;
		requestAnimationFrame(render);
	}
}

function clearhedges() {
	hedges = { position: {
				numComponents: 2,
				data: []
		}, texcoord: {
				numComponents: 2,
				data: []
		}, aspritesel: {
				numComponents: 2,
				data: []
		}, acolorsel: {
				numComponents: 2,
				data: []
		}
	}
}

function addground() {
	var b = [500 * scs, 500 * scs] // brush halfwidth and halfheight
	var pos = [0, 0]
	var ratio = [2 / gl.canvas.width, 2 / gl.canvas.height];
	hedges.position.data.push(
		(pos[0] - b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],

		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1]
	);
	hedges.texcoord.data.push(0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0)
	hedges.aspritesel.data.push(-1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0)
	hedges.acolorsel.data.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}

function addhedge(pos) {
	var b = [25 * scs, 38 * scs] // brush halfwidth and halfheight
	pos[0] = pos[0] - gl.canvas.width * 0.5 + b[0];// offset to get -1 to +1
	pos[1] = pos[1] + b[1] - gl.canvas.height * 0.5;
	var ratio = [2 / gl.canvas.width, 2 / gl.canvas.height];
	hedges.position.data.push(
		(pos[0] - b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],

		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1]
	);
	hedges.texcoord.data.push(0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0);
	hedges.aspritesel.data.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	hedges.acolorsel.data.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}

// position, pose from 0 to 8, shirt and pants colors from 0 to 1.0
function addchar(pos, pose, scol, pcol) {
	var b = [25 * scs, 50 * scs] // brush halfwidth and halfheight
	pos[0] = pos[0] - gl.canvas.width * 0.5 + b[0];// offset to get -1 to +1
	pos[1] = pos[1] + b[1] - gl.canvas.height * 0.5;
	var ratio = [2 / gl.canvas.width, 2 / gl.canvas.height];
	hedges.position.data.push(
		(pos[0] - b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],

		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1]

	);
//	hedges.texcoord.data.push(0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1);
	hedges.texcoord.data.push(0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0);
	hedges.aspritesel.data.push(1, pose, 1, pose, 1, pose, 1, pose,
				1, pose, 1, pose);
	hedges.acolorsel.data.push(scol, pcol, scol, pcol, scol, pcol,
			scol, pcol, scol, pcol, scol, pcol);
}

function addportal(pos) {
	var b = [100 * scs, 100 * scs] // brush halfwidth and halfheight
	pos[0] = pos[0] - gl.canvas.width * 0.5 + b[0];// offset to get -1 to +1
	pos[1] = pos[1] + b[1] - gl.canvas.height * 0.5;
	var ratio = [2 / gl.canvas.width, 2 / gl.canvas.height];
	hedges.position.data.push(
		(pos[0] - b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],

		(pos[0] - b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] - b[1]) * ratio[1],
		(pos[0] + b[0]) * ratio[0],
		(pos[1] + b[1]) * ratio[1]

	);
	hedges.texcoord.data.push(0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0);
	hedges.aspritesel.data.push(2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0);
	hedges.acolorsel.data.push(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}


function mkhedges(dt) {
	clearhedges()
	addground()
	var off = [0, 0]
	if(players[0]) {
		off = [-(players[0][0] * 0.001 - 3) * cellside,
			-(players[0][1] * 0.001 - 3) * cellside]
	}
	for(var i = 0; i < cell.length; ++i) {
		var x = ((i % mazemap.res[0]) - 1) * cellside + off[0]
		var y = (Math.floor(i / mazemap.res[0]) - 1) * cellside + off[1]
		// check if this cell has right wall
		if(!(cell[i] & 2)) {
			addhedge([(x + cellside), y])
			addhedge([(x + cellside), y + cellside * 0.25])
			addhedge([(x + cellside), y + cellside * 0.5])
			addhedge([(x + cellside), y + cellside * 0.75])
			addhedge([(x + cellside), y + cellside * 1.0])
		}
		if(!(cell[i] & 1)) {
			addhedge([x, (y + cellside)])
			addhedge([x + cellside * 0.2, (y + cellside)])
			addhedge([x + cellside * 0.4, (y + cellside)])
			addhedge([x + cellside * 0.6, (y + cellside)])
			addhedge([x + cellside * 0.8, (y + cellside)])
			addhedge([x + cellside * 1.0, (y + cellside)])
		}
		if(cell[i] & 16) {
			// this cell has a portal
			addportal([x + cellside * 0.2, y + cellside * 0.2])
		}
	}
//	addhedge([0, 0])
	// display all players
	for(var i = 0; i < players.length; ++i) {
		var p = players[i]
		var pose = 0
		if(p[2] > 0) {
			if(p[3] > 0) {
				pose = 3
			} else if(p[3] < 0) {
				pose = 1
			} else {
				pose = 2
			}
		} else if(p[2] < 0) {
			if(p[3] > 0) {
				pose = 5
			} else if(p[3] < 0) {
				pose = 7
			} else {
				pose = 6
			}
		} else {
			if(p[3] > 0) {
				pose = 4
			}
		}
		addchar([(p[0] * 0.001 - 1) * cellside + off[0],
				(p[1] * 0.001 - 1) * cellside + off[1]],
			pose, p[4] / 256, p[5] / 256)
	}
//	addportal([300, 350])
}

function render(time) {
	var render_time = Date.now()
	var dt = (render_time - last_render_time) * 0.001
	last_render_time = render_time
	mkhedges(dt)

	var bufinfo = twgl.createBufferInfoFromArrays(gl, hedges);

	gl.enable(gl.BLEND);
	gl.disable(gl.DEPTH_TEST);
	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

	var uniforms = {};

	uniforms = {
		dres: [scs * 1000, scs * 1000],
		fgcolor: [1.0, 1.0, 1.0, 1.0],
		groundtex: textures.ground,
		hedgetex: textures.hedge,
		coribtex: textures.corib,
		coristex: textures.coris,
		coriptex: textures.corip,
		portaltex: textures.portal
	}
	gl.useProgram(hedge_program.program);
	twgl.setBuffersAndAttributes(gl, hedge_program, bufinfo);
	twgl.setUniforms(hedge_program, uniforms);

	twgl.drawBufferInfo(gl, gl.TRIANGLES, bufinfo);

	requestAnimationFrame(render)
}

function resize() {
	var winw = $(window).width()
	var winh = $(window).height()
	var ratio = winh / winw
	var scene_ratio = 1.0 // square scene
	var scw = winw
	var sch = winh
	var scx = 0
	var scy = 0
	if(ratio < scene_ratio) {
		// window is too wide
		scw = winh / scene_ratio
		scx = (winw - scw) * 0.5
		scs = sch / 1000 // height of screen square in "virtual pixels"
	} else {
		sch = winw * scene_ratio
		scy = (winh - sch) * 0.5
		scs = scw / 1000
	}
	cellside = (1000 / 5) * scs
	halfwall = Math.floor(cellside * hwratio);

	$("html").css("font-size", scs)

	twgl.resizeCanvasToDisplaySize(gl.canvas);
	gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

//	requestAnimationFrame(render)
}

function onsocketopen(e) {
//	socket.send("Hello, Server!")

}

function onsocketmessage(e) {
//	console.log("Message from server: ", e.data)

	cell = new Uint8Array(49);

	for(var i = 0; i < 49; ++i) {
		cell[i] = e.data.charCodeAt(i) - 0x20
	}

	players = []
	var charinfo = e.data.substring(49)
	var pstr = charinfo.split(';')
	for(var i = 0; i < pstr.length; ++i) {
		if(!pstr[i].length) {
			break
		}
		players[i] = pstr[i].split(',')
	}
}

function onsocketclose(e) {
	console.log("Socket closed")
}

function onsocketerror(e) {
	console.log("Socket error")
}

function ws_connect() {
//	document.cookie = "groundhog_gardens_pid=" + myid + "; path=/"
	socket = new WebSocket("ws://localhost:50200/?pid=" + myid)
	socket.onopen = onsocketopen
	socket.onclose = onsocketclose
	socket.onmessage = onsocketmessage
	socket.onerror = onsocketerror
}

var keyAllowed = {}

function onkeydown(e) {
	if(keyAllowed[e.which] === false) return
	keyAllowed[e.which] = false
	console.log(e.keyCode, e.which);
	switch(e.keyCode) {
	case 37: socket.send('l'); break
	case 38: socket.send('u'); break
	case 39: socket.send('r'); break
	case 40: socket.send('d'); break
	}
}

function onkeyup(e) {
	keyAllowed[e.which] = true
	switch(e.keyCode) {
	case 37: socket.send('L'); break
	case 38: socket.send('U'); break
	case 39: socket.send('R'); break
	case 40: socket.send('D'); break
	}
}

$(function() {
	canv = $("#c")
	gl = twgl.getWebGLContext(document.getElementById("c"))
	hedge_program = twgl.createProgramInfo(gl, ["vs_hedge", "fs_hedge"])

	textures = twgl.createTextures(gl, {
		ground: { src: "img/ground.jpg" },
		hedge: { src: "img/hedge1.png" },
		corib: { src: "img/corib.png" },
		coris: { src: "img/coris.png" },
		corip: { src: "img/corip.png" },
		portal: { src: "img/portal.png" }
	})

	// what to do if the player taps inside the canvas:
	canv.bind("utap", ondrag)
	canv.bind("udragstart", ondrag).bind("udragmove", ondrag)

	$(window).resize(resize)
	resize()

	$.ajax({
		url: "http://localhost:50200",
		headers: {'canijoinpls': Math.floor(Math.random() * 56535) }
	}).done(function(data) {
		if(data.startsWith("Hello, ")) {
			var newid = parseInt(data.substring(7))
			if(newid > 0) {
				myid = newid
				ws_connect()
			}
		}
	})

	$(document).keydown(onkeydown)
	$(document).keyup(onkeyup)
	$(document).focus(function(e) {
		keyAllowed = {};
	})

	mkmap()
})

})(jQuery)
