var SLIDE_PUZZLE = window.SLIDE_PUZZLE || {};

SLIDE_PUZZLE.VideoView = function(rows, columns) {
	var self = this;
	
	this.FRAME_DELAY = 50;
	
	this._rows = rows;
	this._columns = columns;
	
	this._videoListURL = "video/videolist.json";
	this._videoList = null;
	
	// DOM elements
	this._container = document.getElementById('slidepuzzle-container');
	this._board = document.getElementById('slidepuzzle-board');
	this._video = document.getElementById('slidepuzzle-video');
	this._copyright = document.getElementById('slidepuzzle-copyright');
	this._videoSelect = document.getElementById('slidepuzzle-videolist');
	this._timer = document.getElementById('slidepuzzle-timer');
	this._newGameButton = document.getElementById('slidepuzzle-newgame');
	this._undoButton = document.getElementById('slidepuzzle-undo');
	
	// _origPosition is the unshuffled pieces, used for slicing the video.
	// Its order never changes during a game
	this._origPosition = [];
	
	// _currPosition is the shuffled position of the pieces.
	this._currPosition = [];
	
	this._updateInterval = null;
	
	this._moving = false;
	this._listeners = {
		pieceClicked: [],
		undo: [],
		newGame: [],
		doneMoving: []
	};
	
	// VideoView.moving
	this.__defineGetter__('moving', function() {
		return this._moving;
	});
	
	// VideoView.timerText
	this.__defineGetter__('timerText', function() {
		return this._timer.textContent;
	});
	this.__defineSetter__('timerText', function(value) {
		this._timer.textContent = value;
	});
	
	// VideoView.showEmptyPiece
	this.__defineGetter__('showEmptyPiece', function() {
		document.getElementById('slidepuzzle-empty-piece').classList.contains('hidden');
	});
	this.__defineSetter__('showEmptyPiece', function(value) {
		var classes = document.getElementById('slidepuzzle-empty-piece').classList;
		if (value) {
			classes.remove('hidden');
		} else {
			classes.add('hidden')
		}
	});
	
	// VideoView.canUndo
	this.__defineGetter__('canUndo', function() {
		return !this._undoButton.disabled;
	});
	this.__defineSetter__('canUndo', function(value) {
		this._undoButton.disabled = !value;
	});
	
	// VideoView.canMovePieces
	this.canMovePieces = true;
	
	// Create gameboard cells
	for (var i = 0; i < this._rows; i++) {
		var row = document.createElement('tr');
		for (var j = 0; j < this._columns; j++) {
			row.appendChild(document.createElement('td'));
		}
		this._board.appendChild(row);
	}
	
	// Create puzzle pieces
	for (var i = 0; i < this._rows; i++) {
		this._currPosition.push([]);
		this._origPosition.push([]);
		for (var j = 0; j < this._columns; j++) {
			var piece = document.createElement('canvas');
			piece.dataset.correctRow = i;
			piece.dataset.correctColumn = j;
			if (i === this._rows - 1 && j === this._columns - 1) {
				piece.id = 'slidepuzzle-empty-piece';
			}
			this._currPosition[i].push(piece);
			this._origPosition[i].push(piece);
		}
	}
	
	this._placePieces();
	this.startUpdates();
	
	function makePieceClickHandler(cellIndex) {
		var row = Math.floor(cellIndex / self._columns);
		var column = cellIndex % self._columns;
		var listener = function() {
			self._pieceClicked(row, column);
		}
		return listener;
	}
	
	var cells = this._board.getElementsByTagName("td");
	for (var i = 0; i < cells.length; i++) {
		cells[i].addEventListener('click', makePieceClickHandler(i));
	}
	
	this._videoSelect.addEventListener("change", function() {
		if (self._videoList) {
			console.log("selected video: " + self._videoSelect.value);
			var name = self._videoSelect.value;
			self.loadVideo(name);
		}
	});
	this._loadVideoList();
	
	this._undoButton.addEventListener('click', function() {
		self._dispatchEvent('undo');
	});
	
	this._newGameButton.addEventListener('click', function() {
		self._dispatchEvent('newGame');
	});
};

SLIDE_PUZZLE.VideoView.prototype._loadVideoList = function() {
	// Create an XMLHttpRequest to this._videoListURL.
	// Parse the JSON and store the resulting object as this._videoList.
	var self = this;
	this._videoSelect.textContent = '';
	this._videoList = {};
	var req = new XMLHttpRequest();
	//req.responseType = "json";
	req.overrideMimeType("application/json");
	req.onload = function() {
		console.log("loaded video list");
		var videos = JSON.parse(req.responseText);
		
		for (var i = 0; i < videos.length; i++) {
			var opt = document.createElement('option');
			//opt.value = videos[i].url;
			opt.text = videos[i].name;
			self._videoSelect.appendChild(opt);
			
			self._videoList[videos[i].name] = videos[i];
		}
		
		var changeEvent = document.createEvent("Event");
		changeEvent.initEvent("change", true, true);
		self._videoSelect.dispatchEvent(changeEvent);
	};
	req.onerror = function() {
		console.log("error loading video list");
	};
	req.open("GET", this._videoListURL);
	req.send();
}

SLIDE_PUZZLE.VideoView.prototype.addEventListener = function(type, listener) {
	this._listeners[type].push(listener);
}

SLIDE_PUZZLE.VideoView.prototype.removeEventListener = function(type, listener) {
	for (var i = 0; i < this._listeners[type].length; i++) {
		if (this._listeners[type][i] === listener) {
			this._listeners[type].splice(i, 1);
			break;
		}
	}
}

SLIDE_PUZZLE.VideoView.prototype._dispatchEvent = function(type, detail) {
	if (detail === undefined) {
		detail = null;
		console.log("detail is undefined");
	}
	var event = document.createEvent('CustomEvent');
	event.initCustomEvent(type, false, false, detail);
	for ( var i = 0; i < this._listeners[type].length; i++ ) {
		this._listeners[type][i](event);
	}
}

SLIDE_PUZZLE.VideoView.prototype._pieceClicked = function(row, column) {
	var self = this;
	
	if (!self._moving) {
		var piece = self._currPosition[row][column];
		var detail = {
			row: row,
			column: column,
			correctRow: parseInt(piece.dataset.correctRow, 10),
			correctColumn: parseInt(piece.dataset.correctColumn, 10),
		};
		
		self._dispatchEvent('pieceClicked', detail);
	}
}

SLIDE_PUZZLE.VideoView.prototype.loadVideo = function(name) {
	// We can't reference `this` normally in the closure unless we use bind, which we won't.
	var self = this;
	function setBoardSize() {
		// The board will stretch to match the container.
		self._container.style.height = self._video.videoHeight + "px";
		self._container.style.width = self._video.videoWidth + "px";
		self._board.style.height = self._container.style.height;
		self._board.style.width = self._container.style.width;
		
		for (var row = 0; row < self._rows; row++) {
			for (var col = 0; col < self._columns; col++) {
				var piece = self._currPosition[row][col];
				piece.width = self._video.videoWidth / self._columns;
				piece.height = self._video.videoHeight / self._rows;
			}
		}
	}
	
	this._video.onloadeddata = setBoardSize;
	this._video.src = this._videoList[name].url;
	var copyright = document.createElement('a');
	copyright.href = this._videoList[name].infoUrl;
	copyright.text = this._videoList[name].copyright;
	this._copyright.textContent = '';
	this._copyright.appendChild(copyright);
};

// setBoard arranges the pieces to the given position.
//
// boardPosition is a 2-dimensional array indexed by row, then column.
// The values in boardPosition are numbers representing the pieces'
// order in the solved puzzle, with the highest number being the empty space.
// E.g. a solved 4×4 puzzle is:
// 	[[0,1,2,3],
// 	[4,5,6,7],
// 	[8,9,10,11],
// 	[12,13,14,15]]
// boardPosition must have the proper dimensions and valid piece values,
// or bad things will happen.
// TODO rewrite setBoard to not use _origPosition
SLIDE_PUZZLE.VideoView.prototype.setBoard = function(boardPosition) {
	for (var row = 0; row < this._rows; row++) {
		for (var col = 0; col < this._columns; col++) {
			var pieceNum = boardPosition[row][col];
			var origRow = Math.floor(pieceNum / this._rows);
			var origCol = pieceNum % this._columns;
			
			this._currPosition[row][col] = this._origPosition[origRow][origCol];
		}
	}
	this._placePieces();
}

SLIDE_PUZZLE.VideoView.prototype.movePiece = function(row, column) {
	var self = this;
	if (!self._moving && self.canMovePieces) {
		self._moving = true;
		
		var empty = this._getEmptyPosition();
		var boardRect = this._board.getBoundingClientRect()
		var movingPiece = this._currPosition[row][column];
		var leftDistance = (empty.column - column) * boardRect.width / this._columns;
		var topDistance = (empty.row - row) * boardRect.height / this._rows;
		
		movingPiece.style.left = '0px';
		movingPiece.style.top = '0px';
		
		function animateMove() {
			var moveFactor = 6; // Must be an integer
			var left = parseInt(movingPiece.style.left, 10);
			var top = parseInt(movingPiece.style.top, 10);
			if (Math.abs(left) < Math.abs(leftDistance) && Math.abs(leftDistance - left) > Math.abs(leftDistance / moveFactor)) {
				left += leftDistance / moveFactor;
				movingPiece.style.left = left + "px";
				window.setTimeout(animateMove, self.FRAME_DELAY);
			} else if (Math.abs(top) < Math.abs(topDistance) && Math.abs(topDistance - top) > Math.abs(topDistance / moveFactor)) {
				top += topDistance / moveFactor;
				movingPiece.style.top = top + "px";
				window.setTimeout(animateMove, self.FRAME_DELAY);
			} else {
				self._currPosition[row][column] = self._currPosition[empty.row][empty.column];
				self._currPosition[empty.row][empty.column] = movingPiece;
				movingPiece.style.left = '0px';
				movingPiece.style.top = '0px';
				self._placePieces();
				self._moving = false;
				self._dispatchEvent('doneMoving');
			}
		}
		
		animateMove();
	}
}

SLIDE_PUZZLE.VideoView.prototype.startUpdates = function() {
	var self = this;
	if (this._updateInterval === null) {
		this._updateInterval = window.setInterval(function() {
			self._updatePieces();
		}, this.FRAME_DELAY);
	}
}

SLIDE_PUZZLE.VideoView.prototype.stopUpdates = function() {
	if (this._updateInterval !== null) {
		window.clearInterval(this._updateInterval);
		this._updateInterval = null;
	}
}

SLIDE_PUZZLE.VideoView.prototype._getEmptyPosition = function() {
	var pos = {};
	for (var row = 0; row < this._rows; row++) {
		for (var col = 0; col < this._columns; col++) {
			if (this._currPosition[row][col].id === 'slidepuzzle-empty-piece') return {row:row,column:col};
		}
	}
	throw new Error('no empty position found');
}

SLIDE_PUZZLE.VideoView.prototype._updatePieces = function() {
	// If the pieces' videos end up out of sync, we can optimize
	// this method by first taking a still of the video and then slicing it.
	for (var row = 0; row < this._rows; row++) {
		for (var col = 0; col < this._columns; col++) {
			/*
			var piece = this._origPosition[row][col];
			var ctx = piece.getContext('2d');
			var w = piece.width;
			var h = piece.height;
			var sourceX = col * w;
			var sourceY = row * h;
			ctx.drawImage(this._video, sourceX, sourceY, w, h, 0, 0, w, h);
			*/
			var piece = this._currPosition[row][col];
			var origRow = parseInt(piece.dataset.correctRow, 10);
			var origCol = parseInt(piece.dataset.correctColumn, 10);
			var ctx = piece.getContext('2d');
			var w = piece.width;
			var h = piece.height;
			var sourceX = origCol * w;
			var sourceY = origRow * h;
			ctx.drawImage(this._video, sourceX, sourceY, w, h, 0, 0, w, h);
		}
	}
}

SLIDE_PUZZLE.VideoView.prototype._placePieces = function() {
	for (var row = 0; row < this._rows; row++) {
		for (var col = 0; col < this._columns; col++) {
			var cell = this._board.rows[row].cells[col];
			cell.innerHTML = '';
			var piece = this._currPosition[row][col];
			cell.appendChild(piece);
		}
	}
}
