/* These settings at the top can be adjusted. */
var volume = 0.2;
var typeColors = ["#FF2222", "#2244DD", "#22BB22", "#FFFF22", "#FF22FF", "#22FFFF"];
var unsetColor = "#D0D0D0";
var lockedColor = "#202020";
var typeImagePaths = ["Soju.png","Wine.png","Mojito.png","Beer.png","Water.png","Champagne.png"];
var typeLockedImagePaths = ["match3_type0_locked.png","match3_type1_locked.png","match3_type2_locked.png","match3_type3_locked.png","match3_type4_locked.png","match3_type_locked5.png"];

/* Code below this line should not need to be adjusted. */

// Set up the body
var body = $(".match3")[0];
//var body = document.body;
body.style.width = "100%"
body.style.height = "100%"

// Set up the canvas
var canvasWidth;
var canvasHeight;
var canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.width = 960;
canvas.height = 640;
canvas.style.backgroundColor = "rgba(0,0,0,.5)";
body.appendChild(canvas);

var originalWidth = canvas.width;
var originalHeight = canvas.height;

var context = canvas.getContext('2d');

// Define the variables used by the game

// Manipulable variables
var playArea;
var rows;
var columns;
var types;
var padding;
var spacing;
var delay;
var victoryType;
var matchesToWin;
var typesToWin;
var endGame;
var updateRate;
var moveSpeed;
var removalDelay;
//var soundDirectory = "snd/";
//var imageDirectory = "img/";
var soundDirectory = "data/sound/";
var imageDirectory = "data/image/";

// Nonmanipulable variables
var running = true;
var awaitingCombo = false;
var inputLocked = false;
var attempedSwap = null;
var retryButton = null;
var confirmRetryButton = null;
var cancelRetryButton = null;
var pieces = null;
var puzzle = null;
var hoverCell = null;
var dragCell = null;
var typeImages = null;
var typeLockedImages = null;
var waitTime = 0;
var pieceWidth = 0;
var pieceHeight = 0;
var dragX = 0;
var dragY = 0;
var mouseX = 0;
var mouseY = 0;
var delayTimer = 0;
var matched = {};
var toMatch = {};
var usedShapes = {};
var shapes = [];
var shapePool = [];
var typeImages = {};
var typeLockedImages = {};
var frame = 0;
var hasWon = false;

function loop()
{
	update();
	draw();

	if (running)
	{
		setTimeout(loop, updateRate);
	}
}

function or(a, b)
{
	if (a != null)
	{
		return a;
	}
	return b;
}

function initialize()
{
	playArea = or(playArea,
	{});
	playArea.width = or(playArea.width, 500);
	playArea.height = or(playArea.height, 500);
	rows = or(rows, 9);
	columns = or(columns, 9);
	types = or(types, 5);
	spacing = or(spacing, 8);
	padding = or(padding, 8);
	matchesToWin = or(matchesToWin, 5);
	victoryType = or(victoryType, "type");
	endGame = or(endGame, function () {});
	updateRate = or(updateRate, 16);
	delay = or(delay, 4);
	removalDelay = or(removalDelay, 30);
	moveSpeed = or(moveSpeed, 10);

	document.addEventListener('mousemove', mouseMove, false);
	document.addEventListener('mousedown', mouseDown, false);
	document.addEventListener('touchstart', touchStart, false);
	document.addEventListener('touchmove', touchMove, false);
	document.addEventListener('touchend', touchEnd, false);
	document.addEventListener('mouseup', mouseUp, false);

	playArea.x = Math.floor(canvas.width / 2 - playArea.width / 2)
	playArea.y = Math.floor(canvas.height / 2 - playArea.height / 2)
	cellWidth = (playArea.width - padding) / columns;
	cellHeight = (playArea.height - padding) / rows;
	pieceWidth = (playArea.width - spacing * (columns - 1) - padding * 2) / columns;
	pieceHeight = (playArea.height - spacing * (rows - 1) - padding * 2) / rows;

	// Image loading
	{
		typeImages = {};
		for (var i = 0; i < typeImagePaths.length; i++)
		{
			var img = document.createElement("img");
			img.type = i;

			/* Ensure that if the file is not found it will be set to null */
			img.onerror = function (event)
			{
				typeImages[event.target.type] = null;
			}
			img.src = imageDirectory + typeImagePaths[i];
			typeImages[i] = img;
		}

		typeLockedImages = {};
		for (var i = 0; i < typeLockedImagePaths.length; i++)
		{
			var img = document.createElement("img");
			img.type = i;

			/* Ensure that if the file is not found it will be set to null */
			img.onerror = function (event)
			{
				typeLockedImages[event.target.type] = null;
			}
			img.src = imageDirectory + typeLockedImagePaths[i];
			typeLockedImages[i] = img;
		}
	}
	// Retry buttons
	{
		var retryButton = document.createElement("button")
		retryButton.appendChild(document.createTextNode("Retry"))
		body.appendChild(retryButton)

		retryButton.style.position = "fixed";
		retryButton.style.top = (canvas.getBoundingClientRect().top + playArea.y) + "px";
		retryButton.style.left = (canvas.getBoundingClientRect().left + playArea.x + playArea.width) + "px";
		retryButton.style.display = "inline";
		retryButton.style.width = "100px";
		retryButton.style.height = "32px";
		retryButton.style.border = "none";
		retryButton.style.background = "rgba(0,0,0,.7)";
		retryButton.style.color = "#FFF";
		retryButton.style.fontSize = "24px";
		body.appendChild(retryButton);

		retryButton.onclick = function ()
		{
			confirmRetryButton.style.display = "inline";
			cancelRetryButton.style.display = "inline";
		}
		retryButton.ontouchend = function ()
		{
			confirmRetryButton.style.display = "inline";
			cancelRetryButton.style.display = "inline";
		}

		var confirmRetryButton = document.createElement("button")
		confirmRetryButton.appendChild(document.createTextNode("Confirm"))
		confirmRetryButton.style.position = "fixed";
		confirmRetryButton.style.top = (canvas.getBoundingClientRect().top + playArea.y + 32) + "px";
		confirmRetryButton.style.left = (canvas.getBoundingClientRect().left + playArea.x + playArea.width) + "px";
		confirmRetryButton.style.width = "100px";
		confirmRetryButton.style.height = "32px";
		confirmRetryButton.style.border = "none";
		confirmRetryButton.style.background = "rgba(0,0,0,.7)";
		confirmRetryButton.style.color = "#FFF";
		confirmRetryButton.style.fontSize = "24px";
		confirmRetryButton.style.display = "none";
		confirmRetryButton.onclick = function ()
		{
			retry();
			confirmRetryButton.style.display = "none";
			cancelRetryButton.style.display = "none";
		}
		confirmRetryButton.ontouchend = function ()
		{
			retry();
			confirmRetryButton.style.display = "none";
			cancelRetryButton.style.display = "none";
		}
		body.appendChild(confirmRetryButton);

		var cancelRetryButton = document.createElement("button")
		cancelRetryButton.appendChild(document.createTextNode("Cancel"))
		cancelRetryButton.style.position = "fixed";
		cancelRetryButton.style.top = (canvas.getBoundingClientRect().top + playArea.y + 64) + "px";
		cancelRetryButton.style.left = (canvas.getBoundingClientRect().left + playArea.x + playArea.width) + "px";
		cancelRetryButton.style.width = "100px";
		cancelRetryButton.style.height = "32px";
		cancelRetryButton.style.border = "none";
		cancelRetryButton.style.background = "rgba(0,0,0,.7)";
		cancelRetryButton.style.color = "#FFF";
		cancelRetryButton.style.fontSize = "24px";
		cancelRetryButton.style.display = "none";
		cancelRetryButton.onclick = function ()
		{
			confirmRetryButton.style.display = "none";
			cancelRetryButton.style.display = "none";
		}
		cancelRetryButton.ontouchend = function ()
		{
			confirmRetryButton.style.display = "none";
			cancelRetryButton.style.display = "none";
		}
		body.appendChild(cancelRetryButton);
	}

	retry();
}

function drawShape(shape, shapeX, shapeY, width, height, color, spacing, padding)
{
	var columns = 0;
	var rows = shape.data.length;
	for (y = 0; y < shape.data.length; y++)
	{
		for (x = 0; x < shape.data[y].length; x++)
		{
			if (x > columns)
			{
				columns = x;
			}
		}
	}
	var square = Math.max(columns + 1, rows + 1);
	var pieceWidth = (width - padding * 2 - spacing * square) / square;
	var pieceHeight = (height - padding * 2 - spacing * square) / square;
	var offsetX = (width - (pieceWidth + spacing) * columns) / 2;
	var offsetY = (height - (pieceWidth + spacing) * rows) / 2;
	context.fillStyle = color;

	context.save();
	context.translate(padding + offsetX, padding + offsetY);
	var rectX = 0;
	var rectY = 0;
	for (y = 0; y < square; y++)
	{
		for (x = 0; x < square; x++)
		{
			if (shape.data.length > y)
			{
				if (shape.data[y].length > x)
				{
					if (shape.data[y][x] == 1)
					{
						context.fillRect(shapeX + rectX, shapeY + rectY, pieceWidth, pieceHeight);
					}
				}
			}
			rectX += pieceWidth + spacing;
		}
		rectY += pieceHeight + spacing;
		rectX = 0;
	}
	context.restore();
}

function retry()
{
	pieces = {};
	puzzle = {};
	typesToWin = {};
	for (var i = 0; i < types; i++)
	{
		typesToWin[i] = 0;
	}
	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			var cellKey = x + "," + y;
			puzzle[cellKey] = {
				x: x,
				y: y,
				type: -1,
				locked: false,
				rest: true,
				delay: 0,
				removalTimer: removalDelay,
				inCombo: false
			}
			pieces[cellKey] = piece.new(playArea.width/2, playArea.height/2, cellKey);

		}
	}

	for (i = 0; i < shapes.length; i++)
	{
		usedShapes[i] = 0;
	}

	var maxAttempts = 100;
	var needed = matchesToWin;
	while (maxAttempts-- > 0 && needed > 0)
	{
		var x = Math.floor(Math.random() * columns);
		var y = Math.floor(Math.random() * rows);
		var chosen = Math.floor(Math.random() * shapes.length);
		if (applyShape(shapes[chosen], x, y))
		{
			usedShapes[chosen]++;
			needed--;
		}
	}

	var hasCombos = true;
	maxAttempts = 100;
	while (maxAttempts-- > 0 && hasCombos)
	{
		scrambleBoard();
		hasCombos = false;
		for (var y = 0; y < rows; y++)
		{
			for (var x = 0; x < columns; x++)
			{
				var pieceKey = x + "," + y;
				if (getConnected(puzzle[pieceKey], []).length > 0)
				{
					hasCombos = true;
					break;
				}
			}
			if (hasCombos)
			{
				break;
			}
		}
	}

	maxAttempts = 100;
	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			var pieceKey = x + "," + y;
			if (puzzle[pieceKey].type == -1)
			{
				puzzle[pieceKey].type = Math.floor(Math.random() * types);
				while (maxAttempts-- > 0 && getConnected(puzzle[pieceKey], []).length > 0)
				{
					puzzle[pieceKey].type = Math.floor(Math.random() * types);
				}
			}
		}
	}
	updatePieceMovement();
	for (var i = 0; i < types; i++)
	{
		typesToWin[i] = neededtoWIN;
	}
	playSound("Game_Start_Retry.wav");
}

function updatePieceMovement()
{
	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			var pieceKey = x + "," + y;
			pieces[pieceKey].vx = (puzzle[pieceKey].x * cellWidth - pieces[pieceKey].x) / moveSpeed;
			pieces[pieceKey].vy = (puzzle[pieceKey].y * cellHeight - pieces[pieceKey].y) / moveSpeed;
			pieces[pieceKey].moveTime = moveSpeed;
		}
	}
}

function cleanup()
{
	document.removeEventListener('mousemove', mouseMove);
	document.removeEventListener('mousedown', mouseDown);
	document.removeEventListener('touchstart', touchStart);
	document.removeEventListener('touchmove', touchMove);
	document.removeEventListener('touchend', touchEnd);
	document.removeEventListener('mouseup', mouseUp);
	if (window.jQuery)
	{
		$("#canvas").remove();
		$("#retryButton").remove();
		$("#confirmRetryButton").remove();
		$("#cancelRetryButton").remove();
		$(".match3").remove();
	}
	running = false;
	endGame();
}

function scrambleBoard()
{
	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			var pieceKey = x + "," + y;
			if (getConnected(puzzle[pieceKey], []).length > 0)
			{
				shiftPiece(pieceKey);
			}
		}
	}
}

function shiftPiece(pieceKey)
{
	var shift = [
		[-1, 0],
		[1, 0],
		[0, -1],
		[0, 1]
	];
	var piece = puzzle[pieceKey]
	if (piece != null)
	{
		while (shift.length > 0)
		{
			var index = intRandom(0, shift.length);
			var swapCell = null;
			var swapX = piece.x + shift[index][0];
			var swapY = piece.y + shift[index][1];
			swapCell = swapX + "," + swapY;
			if (swapCell != pieceKey && puzzle[swapCell] != null && !puzzle[swapCell].locked && !puzzle[pieceKey].locked)
			{
				swapCells(pieceKey, swapCell);
			}
			shift.splice(index);
		}
	}
}

function intRandom(min, max)
{
	return Math.floor(Math.random() * (max - min) + min);
}

function playSound(filePath)
{
	var audio = new Audio(soundDirectory + filePath);
	audio.volume = volume;
	audio.play();
}

function applyShape(shape, shapeX, shapeY)
{
	var canApply = true;
	for (var y = 0; y < shape.data.length; y++)
	{
		for (var x = 0; x < shape.data[y].length; x++)
		{
			if (shape.data[y][x] == 1)
			{
				var cellX = x - shape.anchor[0] + shapeX;
				var cellY = y - shape.anchor[1] + shapeY;
				if (puzzle[cellX + "," + cellY] == null || puzzle[cellX + "," + cellY].type != -1)
				{
					canApply = false;
					break;
				}
			}
		}
	}
	if (canApply)
	{
		var type = Math.floor(Math.random() * types);
		for (var y = 0; y < shape.data.length; y++)
		{
			for (var x = 0; x < shape.data[y].length; x++)
			{
				if (shape.data[y][x] == 1)
				{
					var cellX = x - shape.anchor[0] + shapeX;
					var cellY = y - shape.anchor[1] + shapeY;
					puzzle[cellX + "," + cellY].type = type;
					typesToWin[type]++;
				}
			}
		}
	}
	return canApply;
}

function getHoverCell()
{
	var puzzleMouseX = Math.floor((mouseX - playArea.x - padding / 2) / cellWidth);
	var puzzleMouseY = Math.floor((mouseY - playArea.y - padding / 2) / cellHeight);
	puzzleMouseX = Math.min(Math.max(puzzleMouseX, 0), columns - 1);
	puzzleMouseY = Math.min(Math.max(puzzleMouseY, 0), rows - 1);
	hoverCell = puzzleMouseX + "," + puzzleMouseY;
}

function update(dt)
{
	getHoverCell();
	var soundToPlay = null;
	var isInputLocked = false;

	var unrest = false;
	var needPieceUpdate = false;
	for (var y = rows-1; y >= 0; y--)
	{
		for (var x = 0; x < columns; x++)
		{
			var belowX = x;
			var belowY = y + 1;

			if ( y < rows - 1)
			{
				if ( puzzle[belowX+","+belowY].type == -1 && puzzle[x+","+y].delay <= 0 && puzzle[belowX+","+belowY].delay <= 0 && puzzle[x+","+y].type != -1 )
				{
					if ( puzzle[belowX+","+belowY].removalTimer <= 0 )
						{
						swapCells(x+","+y,belowX+","+belowY);
						needPieceUpdate	= true;
						puzzle[belowX+","+belowY].rest = false;
						puzzle[x+","+y].rest = false;
						puzzle[belowX+","+belowY].delay = moveSpeed;
					}
				}
				if ( puzzle[belowX+","+belowY].rest )
				{
					puzzle[x+","+y].rest = true;
				}
			}
			if ( y == rows-1)
			{
				puzzle[x+","+y].rest = true;
			}
			else if ( y == 0 && puzzle[x+","+y].type == -1 )
			{
				puzzle[x+","+y].type = intRandom(0,types);
				pieces[x+","+y].y = -pieceHeight;
				puzzle[x+","+y].delay = moveSpeed;
				needPieceUpdate = true;
			}
			if (!puzzle[x+","+y].rest || puzzle[x+","+y].delay > 0 || puzzle[x+","+y].removalTimer > 0)
			{
				unrest = true;
			}
		}
	}

	if ( !unrest )
	{
		for (var y = 0; y < rows; y++)
		{
			for (var x = 0; x < columns; x++)
			{
				var pieceKey = x + "," + y;
				var connected = getConnected(puzzle[pieceKey], []);
				if (connected.length > 0)
				{
					for (var j = 0; j < shapes.length; j++)
					{
						if (compareShapes(shapes[j].data, getShapeFromConnected(connected)))
						{
							if (usedShapes[j] > 0)
							{
								usedShapes[j]--;
							}
							break;
						}
					}
					for (var i = 0; i < connected.length; i++)
					{
						if (typesToWin[connected[i].type] > 0)
						{
							typesToWin[connected[i].type]--;
						}
						puzzle[connected[i].x +","+connected[i].y].inCombo = true;
						puzzle[connected[i].x +","+connected[i].y].removalTimer = removalDelay;
					}
					soundToPlay = "Match.wav";
					awaitingCombo = false;
					victoryCheck();
				}
				if (puzzle[pieceKey].type == -1 || puzzle[pieceKey].delay > 0 || puzzle[pieceKey].inCombo  )
				{
					isInputLocked = true;
				}
			}
		}
	}
	else
	{
		isInputLocked = true;
	}

	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			var key = x+","+y;
			var piece = pieces[key];
			if (puzzle[key] != null)
			{
				/*if ( (piece.x != puzzle[key].x || piece.y != puzzle[key].y)  )
				{
					piece.vx = (puzzle[key].x * cellWidth - piece.x) / moveSpeed;
					piece.vy = (puzzle[key].y * cellHeight - piece.y) / moveSpeed;
					piece.moveTime = moveSpeed;
				}*/
				//piece.x += (puzzle[key].x * cellWidth - piece.x) / 2;
				//piece.y += (puzzle[key].y * cellHeight - piece.y) / 2;
				if ( piece.moveTime > 0 )
				{
					piece.x += piece.vx;
					piece.y += piece.vy;
					piece.moveTime--;
				}
				if ( puzzle[key].delay > 0)
				{
					puzzle[key].delay--;
				}
				if ( puzzle[key].removalTimer > 0)
				{
					puzzle[key].removalTimer--;
					if ( puzzle[key].removalTimer == 0)
					{
						if ( puzzle[key].inCombo )
						{
							puzzle[key].type = -1;
							puzzle[key].inCombo = false;
						}
					}
				}
			}
		}
	}

	if ( awaitingCombo )
	{
		if ( !unrest && delayTimer <= 0 && waitTime <= 0 )
		{
			inputLocked = false;
			performSwap( attemptedSwap.hoverCell, attemptedSwap.dragCell );
			needPieceUpdate	= true;
			dragCell = null;
			awaitingCombo = false;
			delayTimer = removalDelay;
		}
		isInputLocked = true;
	}

	if (delayTimer > 0)
	{
		delayTimer--;
		isInputLocked = true;
	}

	inputLocked = isInputLocked;

	if ( needPieceUpdate)
	{
		updatePieceMovement();
	}
	if (soundToPlay != null)
	{
		playSound(soundToPlay);
	}
	frame++;
}

function performSwap(forceDragCell, forceHoverCell)
{
	if ( forceDragCell == null && forceHoverCell == null)
	{
		getHoverCell();
	}
	else
	{
		dragCell = forceDragCell;
		hoverCell = forceHoverCell;
	}
	// Swap Logic

	if ( !inputLocked )
	{
		if (dragCell != null && hoverCell != null && dragCell != hoverCell && puzzle[hoverCell] != null && puzzle[dragCell] != null)
		{
			attemptedSwap = {dragCell:dragCell,hoverCell:hoverCell};
			var drag = puzzle[dragCell];
			var hover = puzzle[hoverCell];
			if (drag.x == hover.x && drag.y != hover.y)
			{
				if ( drag.y > hover.y )
				{
					for (var y = drag.y; y >= hover.y; y-- )
					{
						puzzle[drag.x+","+y].delay = moveSpeed;
						swapCells(drag.x+","+drag.y, drag.x+","+y);
					}
				}
				else if ( hover.y > drag.y )
				{
					for (var y = drag.y; y <= hover.y; y++ )
					{
						puzzle[drag.x+","+y].delay = moveSpeed;
						swapCells(drag.x+","+drag.y, drag.x+","+y);
					}
				}
				awaitingCombo = true;
				delayTimer = removalDelay;
				soundToPlay = "Move.wav";
			}
			else if (drag.x != hover.x && drag.y == hover.y)
			{
				if ( drag.x > hover.x )
				{
					for (var x = drag.x; x >= hover.x; x-- )
					{
						puzzle[x+","+drag.y].delay = moveSpeed;
						swapCells(x+","+drag.y, drag.x+","+drag.y);
					}
				}
				else if ( hover.x > drag.x )
				{
					for (var x = drag.x; x <= hover.x; x++ )
					{
						puzzle[x+","+drag.y].delay = moveSpeed;
						swapCells(x+","+drag.y, drag.x+","+drag.y);
					}
				}
				awaitingCombo = true;
				delayTimer = removalDelay;
				soundToPlay = "Move.wav";
			}
		}
	}

}

function victoryCheck()
{
	if ( !hasWon )
	{
		hasWon = true;
		if (victoryType == "shape")
		{
			for (var i = 0; i < shapes.length; i++)
			{
				if (usedShapes[i] != 0)
				{
					hasWon = false;
				}
			}
		}
		else if (victoryType == "type")
		{
			for (var i = 0; i < types; i++)
			{
				if (typesToWin[i] != 0)
				{
					hasWon = false;
				}
			}
		}
		if (hasWon)
		{
			//playSound("Victory.wav");
			canvas = null;
			cleanup();
		}
	}
}

function compareShapes(aData, bData)
{
	if (aData.length != bData.length)
	{
		return false;
	}
	for (var y = 0; y < aData.length; y++)
	{
		if (aData[y].length != bData[y].length)
		{
			return false;
		}
		for (var x = 0; x < aData[y].length; x++)
		{
			if (aData[y][x] != bData[y][x])
			{
				return false;
			}
		}

	}
	return true;
}


function draw()
{
	context.clearRect(0, 0, canvas.width, canvas.height);
	context.save()
	context.fillStyle = "rgba(0,0,0,0.7)";
	context.translate(playArea.x, playArea.y);
	context.fillRect(0, 0, playArea.width, playArea.height);
	context.translate(padding, padding);
	for (var y = 0; y < rows; y++)
	{
		for (var x = 0; x < columns; x++)
		{
			drawPiece(x+","+y);
		}
	}

	if (victoryType == "shape")
	{
		var shapeIndex = 0;
		for (i = 0; i < shapes.length; i++)
		{
			if (usedShapes[i] > 0)
			{
				context.fillStyle = "rgba(0,0,0,0.7)";
				context.fillRect(-100 - padding, shapeIndex * 50 + 10, 100, 50);
				drawText(usedShapes[i], -90, 50 * shapeIndex + 35, "#FFF", 24);
				drawShape(shapes[i], -55 - padding, 50 * shapeIndex, 50, 50, "#FFF", 2, 2);
				shapeIndex++;
			}
		}
	}
	else if (victoryType == "type")
	{
		var typeIndex = 0;
		for (i = 0; i < types; i++)
		{
			if (typesToWin[i] > 0)
			{
				context.fillStyle = "rgba(0,0,0,0.7)";
				context.fillRect(-100 - padding, typeIndex * 50, 100, 50);
				drawText(typesToWin[i], -90, 50 * typeIndex + 35, "#FFF", 24);
				drawPieceImage(i, -55, 50 * typeIndex + 5, 40, 40, false, typeColors[i]);
				typeIndex++;
			}
		}
	}
	context.restore()
}

function getShapeFromConnected(connected)
{
	var lowX = 999;
	var lowY = 999;
	var highX = 0;
	var highY = 0;
	for (var i = 0; i < connected.length; i++)
	{
		var cell = connected[i];
		if (lowX > cell.x)
		{
			lowX = cell.x;
		}
		if (highX < cell.x)
		{
			highX = cell.x;
		}
		if (lowY > cell.y)
		{
			lowY = cell.y;
		}
		if (highY < cell.y)
		{
			highY = cell.y;
		}
	}
	var columns = highX - lowX + 1;
	var rows = highY - lowY + 1;
	var data = [];
	for (var y = 0; y < rows; y++)
	{
		data[y] = [];
		for (var x = 0; x < columns; x++)
		{
			data[y][x] = 0;
			for (var i = 0; i < connected.length; i++)
			{
				if (connected[i].x - lowX == x && connected[i].y - lowY == y)
				{
					data[y][x] = 1;
					break;
				}
			}
		}
	}
	return data;
}

function getConnected(cell, connected)
{
	var test = [];
	test.push(cell);
	for (var i = 0; i < test.length; i++)
	{
		var testCell = test[i];
		if (testCell != null && !testCell.locked)
		{
			var left = puzzle[(testCell.x - 1) + "," + (testCell.y)];
			var up = puzzle[(testCell.x) + "," + (testCell.y - 1)];
			var right = puzzle[(testCell.x + 1) + "," + (testCell.y)];
			var down = puzzle[(testCell.x) + "," + (testCell.y + 1)];
			if (canCombo(testCell, left))
			{
				if (test.indexOf(left) == -1)
				{
					test.push(left);
				}
			}
			if (canCombo(testCell, right))
			{
				if (test.indexOf(right) == -1)
				{
					test.push(right);
				}
			}
			if (canCombo(testCell, left) && canCombo(testCell, right))
			{
				if (connected.indexOf(left) == -1)
				{
					connected.push(left);
					getConnected(left, connected)
				}
				if (connected.indexOf(right) == -1)
				{
					connected.push(right);
					getConnected(right, connected)
				}
				if (connected.indexOf(testCell) == -1)
				{
					connected.push(testCell);
				}
			}
			if (canCombo(testCell, up))
			{
				if (test.indexOf(up) == -1)
				{
					test.push(up);
				}
			}
			if (canCombo(testCell, down))
			{
				if (test.indexOf(down) == -1)
				{
					test.push(down);
				}
			}
			if (canCombo(testCell, up) && canCombo(testCell, down))
			{
				if (connected.indexOf(up) == -1)
				{
					connected.push(up);
					getConnected(up, connected)
				}
				if (connected.indexOf(down) == -1)
				{
					connected.push(down);
					getConnected(down, connected)
				}
				if (connected.indexOf(testCell) == -1)
				{
					connected.push(testCell);
				}
			}
		}
	}
	return connected;
}

function swapCells(a, b)
{
	var tempX = puzzle[a].x;
	var tempY = puzzle[a].y;
	var tempKey = puzzle[a];
	puzzle[a].x = puzzle[b].x;
	puzzle[a].y = puzzle[b].y;
	puzzle[b].x = tempX;
	puzzle[b].y = tempY;
	puzzle[a] = puzzle[b];
	puzzle[b] = tempKey;
	dragPiece = null;
	hoverPiece = null;
	dragPiece = pieces[a];
	hoverPiece = pieces[b];
	pieces[b] = dragPiece;
	pieces[a] = hoverPiece;
}

function moveCell(a,x,y)
{
	var key = x+","+y;
	if (puzzle[a] != null)
	{
		puzzle[key] = piece.new(puzzle[a].x,puzzle[a].y);
	}
}

function canCombo(comboTest, cell)
{
	var comboable = true;
	if (cell == null || cell.locked || cell.type == -1 || cell.type != comboTest.type || !cell.rest || !comboTest.rest || comboTest.delay > 0 || cell.delay > 0 || cell.inCombo )
	{
		comboable = false;
	}
	return comboable
}

var piece = {}

piece.new = function (x, y)
{
	var newPiece = {}
	newPiece.x = x;
	newPiece.y = y;
	newPiece.vx = 0;
	newPiece.vy = 0;
	newPiece.moveTime = 0;
	newPiece.color = "#FFF";
	return newPiece
}

function drawPiece(key)
{
	if ( puzzle[key] != null && puzzle[key].type != -1 )
	{
		if ( !inputLocked )
		{
			if (puzzle[key].locked)
			{
				context.fillStyle = lockedColor;
				context.fillRect(pieces[key].x - 2, pieces[key].y - 2, pieceWidth + 4, pieceHeight + 4);
			}

			else if (dragCell == key)
			{
				context.fillStyle = "#FFFF00";
				context.fillRect(pieces[key].x - 2, pieces[key].y - 2, pieceWidth + 4, pieceHeight + 4);
			}
			else if (hoverCell == key)
			{
				context.fillStyle = "#FFFFFF";
				context.fillRect(pieces[key].x - 2, pieces[key].y - 2, pieceWidth + 4, pieceHeight + 4);
			}
		}
		if (puzzle[key].inCombo)
		{
			context.fillStyle = "#FFFFFF";
			context.fillRect(pieces[key].x - 2, pieces[key].y - 2, pieceWidth + 4, pieceHeight + 4);
		}
		if (puzzle[key].type == -1)
		{
			context.fillStyle = unsetColor;
		}
		drawPieceImage(puzzle[key].type, pieces[key].x, pieces[key].y, pieceWidth, pieceHeight, puzzle[key].locked, typeColors[puzzle[key].type]);
	}

}

function drawPieceImage(type, x, y, width, height, locked)
{
	if (typeImages[type] != null)
	{
		if (locked)
		{
			context.drawImage(typeLockedImages[type], x, y, width, height);
		}
		else
		{
			context.drawImage(typeImages[type], x, y, width, height);
		}
	}
	else
	{
		if (locked)
		{
			context.fillStyle = lockedColor;
		}
		else if (type >= 0)
		{
			context.fillStyle = typeColors[type];
		}
		context.fillRect(x, y, width, height);
	}
}

function drawText(text, x, y, color, size)
{
	context.fillStyle = or(color, "#FFF");
	context.font = "bold " + or(size, 16) + "px Arial";
	context.fillText(text, x, y);
}

/* Input */
{
	function setMousePosition(x, y)
	{
		var ratio = Math.min(canvas.getBoundingClientRect().width / originalWidth, canvas.getBoundingClientRect().height / originalHeight);
		mouseX = x / ratio;
		mouseY = y / ratio;
	}

	function mouseMove(event)
	{
		setMousePosition(event.clientX - canvas.getBoundingClientRect().left,
			event.clientY - canvas.getBoundingClientRect().top);
	}

	function touchMove(event)
	{

		setMousePosition(event.touches[0].clientX - canvas.getBoundingClientRect().left,
			event.touches[0].clientY - canvas.getBoundingClientRect().top);
		event.preventDefault();
	}

	function touchStart(event)
	{
		setMousePosition(event.touches[0].clientX - canvas.getBoundingClientRect().left,
			event.touches[0].clientY - canvas.getBoundingClientRect().top)
		getHoverCell();
		if (hoverCell != null)
		{
			dragCell = hoverCell;
		}
		event.preventDefault();
	}

	function touchEnd(event)
	{
		performSwap();
		updatePieceMovement();
		dragCell = null;
		event.preventDefault();
	}

	function mouseDown()
	{
		if (hoverCell != null)
		{
			dragCell = hoverCell;
		}
	}

	function mouseUp()
	{
		performSwap();
		updatePieceMovement();
		dragCell = null;
	}
}

/* Shapes */
{
	shapes.push(
	{
		anchor: [1, 0],
		data: [
			[1, 1, 1]
		]
	});
	shapes.push(
	{
		anchor: [0, 1],
		data: [
			[1],
			[1],
			[1]
		]
	});

	shapes.push(
	{
		anchor: [1, 0],
		data: [
			[1, 1, 1, 1]
		]
	});

	shapes.push(
	{
		anchor: [0, 1],
		data: [
			[1],
			[1],
			[1],
			[1]
		]
	});

	shapes.push(
	{
		anchor: [2, 0],
		data: [
			[1, 1, 1, 1, 1]
		]
	});

	shapes.push(
	{
		anchor: [0, 1],
		data: [
			[1],
			[1],
			[1],
			[1],
			[1]
		]
	});

	shapes.push(
	{
		anchor: [1, 1],
		data: [
			[1, 1, 1],
			[0, 1, 0],
			[0, 1, 0]
		]
	});

	shapes.push(
	{
		anchor: [1, 1],
		data: [
			[1, 0, 0],
			[1, 0, 0],
			[1, 1, 1]
		]
	});
}

initialize();
setTimeout(loop, updateRate);