/**
 * YouTube.js
 * 
 * @version 1.0
 * @author Maurice Snip <mauricesnip at hotmail dot com>
 * @uses swfobject.js <http://code.google.com/p/swfobject/>
 */
function YouTubePlayer(el, options) {
	this.API_PLAYER_URL = 'http://www.youtube.com/apiplayer?enablejsapi=1&version=3&playerapiid=';
	this.VIDEO_ID_REGEX = /[^=]*.(.{11}).*/;
	this.el = $(el);
	this.options = options;
	this.player = null;
	this.playerId = '';
	this.playerCanvas = null;
	this.playerObject = null;
	this.playing = false;
	this.interval = null;
	this.ready = false;
	this.init();
}

YouTubePlayer.prototype = {
	
	/**
	 * Initialize
	 */
	init: function() {
		if(this.el.length && !/ipod|iphone|ipad/i.test(navigator.userAgent)) {
			var self = this;
			
			this.options = $.extend({
				template: '<div class="player"><div class="player-canvas"><div class="player-video"></div></div><div class="player-controls"><div class="player-play"></div><div class="player-stop"></div><div class="player-mute"></div><div class="player-full-screen"></div><div class="player-progress"><div class="player-buffered"></div><div class="player-elapsed"></div><div class="player-handle"></div></div></div></div>',
				autoPlay: false,
				startMuted: false,
				autoSize: true,
				aspectRatio: 16/9,
				width: 480,
				height: 270,
				hideLink: true,
				hideControls: false,
				playerParams: {
					allowScriptAccess: 'always',
					bgcolor: '#000000',
					wmode: 'transparent'
				},
				onReady: function() {},
				onStateChange: function() {},
				onEnd: function() {},
				onPlay: function() {},
				onPause: function() {},
				onBuffering: function() {},
				onCue: function() {},
				onMute: function() {},
				onUnMute: function() {},
				onFullScreen: function() {},
				onFramed: function() {},
				onError: function() {},
				onInvalidParameterError: function() {},
				onNotFoundError: function() {},
				onNoEmbedError: function() {}
			}, this.options || {});
			
			self.createPlayer();
		}
	},
	
	/**
	 * Create a player from template
	 */
	createPlayer: function() {
		this.playerId = 'player' + Math.floor(Math.random() * 1000);
		
		this.player = $(this.options.template);
		this.playerCanvas = this.player.find('.player-canvas');
		
		this.player.find('.player-video').attr('id', this.playerId);
		this.player.insertAfter(this.el);
		
		if(this.options.hideLink === true) {
			this.el.hide();
		}
		
		if(this.options.autoSize === true) {
			this.playerCanvas.height(this.calculateAspectRatioHeight());
		}
		else {
			this.playerCanvas.height(this.options.height);
		}
		
		this.bindPlayerEventHandlers();
		
		// Embed the player object
		swfobject.embedSWF(this.API_PLAYER_URL + this.playerId, this.playerId, '100%', '100%', '8', null, null, this.options.playerParams, { id: this.playerId });
	},
	
	/**
	 * Bind the player's event handlers
	 */
	bindPlayerEventHandlers: function() {
		var self = this;
		
		/**
		 * When the player is ready
		 */
		function onYouTubePlayerReadyHandler(playerId) {
			if(playerId === self.playerId) {
				self.playerObject = document.getElementById(playerId);
				
				self.playerObject.addEventListener('onStateChange', playerId + 'onStateChange');
				self.playerObject.addEventListener('onError', playerId + 'onError');
				
				window[playerId + 'onStateChange'](200);
			}
		}
		
		/**
		 * When the player's state changes
		 */
		function onStateChangeHandler(state) {
			self.fireCustomEvent('onStateChange', state);
			
			switch(state) {
				case 0:
					self.handlePlayerEnd();
					break;
				case 1:
					self.handlePlayerPlay();
					break;
				case 2:
					self.handlePlayerPause();
					break;
				case 3:
					self.handlePlayerBuffering();
					break;
				case 5:
					self.handlePlayerCue();
					break;
				case 50:
					self.handlePlayerMute();
					break;
				case 51:
					self.handlePlayerUnMute();
					break;
				case 52:
					self.handlePlayerFullScreen();
					break;
				case 53:
					self.handlePlayerFramed();
					break;
				case 200:
					self.handlePlayerReady(self.playerId);
					break;
			}
		}
		
		/**
		 * When the player fires an error
		 */
		function onErrorHandler(error) {
			self.fireCustomEvent('onError', error);
			
			switch(error) {
				case 2:
					self.handleInvalidParameter();
					break;
				case 100:
					self.handleNotFound();
					break;
				case 101:
				case 150:
					self.handleNoEmbed();
					break;
			}
		}
		
		if(typeof window.onYouTubePlayerReady === 'function') {
			var oldOnYouTubePlayerReady = window.onYouTubePlayerReady;
			
			window.onYouTubePlayerReady = function(playerId) {
				oldOnYouTubePlayerReady(playerId);
				onYouTubePlayerReadyHandler(playerId);
			};
		}
		else {
			window.onYouTubePlayerReady = onYouTubePlayerReadyHandler;
		}
		
		window[this.playerId + 'onStateChange'] = onStateChangeHandler;
		window[this.playerId + 'onError'] = onErrorHandler;
	},
	
	/**
	 * Bind the player's event handlers
	 */
	bindEventHandlers: function() {
		var self = this;
		
		$('.player-controls', this.player).click(function(e) {
			var target = e.target;
			
			switch($(target).attr('class')) {
				case 'player-play':
					self.getPlayerState() === 1 ? self.pauseVideo() : self.playVideo();
					break;
				case 'player-stop':
					if(self.getPlayerState() === 1) {
						self.stopVideo();
					}
					break;
				case 'player-mute':
					self.isMuted() ? self.unMute() : self.mute();
					break;
				case 'player-full-screen':
					self.isFullScreen() ? self.framed() : self.fullScreen();
					break;
				case 'player-progress':
				case 'player-buffered':
				case 'player-elapsed':
					self.seekToPercentage(((e.layerX || e.offsetX) * 100) / self.player.find('.player-progress').width());
					break;
			}
		});
		
		$(document).keyup(function(e) {
			if(e.keyCode === 27 && self.isFullScreen()) {
				self.framed();
			}
		});
	},
	
	/**
	 * Get the player DOM reference
	 * 
	 * @return {array} A jQuery array containing the player DOM reference
	 */
	getPlayer: function() {
		return this.player;
	},
	
	/**
	 * Get the player HTMLObjectElement
	 * 
	 * @return {object} The HTMLObjectElement
	 */
	getPlayerObject: function() {
		return this.playerObject;
	},
	
	/**
	 * Set the player HTMLObjectElement
	 * 
	 * @param {object} playerObject The HTMLObjectElement
	 */
	setPlayerObject: function(playerObject) {
		this.playerObject = playerObject;
	},
	
	/**
	 * Fires a custom event
	 * 
	 * @param {string} event The event name
	 */
	fireCustomEvent: function(event) {
		if(this.options.hasOwnProperty(event)) {
			var args = [this.player, this.playerObject].concat(Array.prototype.slice.call(arguments, 1));
			this.options[event].apply(this, args);
		}
	},
	
	/**
	 * Seek to a percentage
	 * 
	 * @param {Number} percentage The percentage to seek to
	 */
	seekToPercentage: function(percentage) {
		if(this.playing) {
			this.seekTo((this.getDuration() * percentage) / 100, false);
		}
	},
	
	/**
	 * Calculate the aspect ratio height
	 */
	calculateAspectRatioHeight: function() {
		return Math.round(this.playerCanvas.width() / this.options.aspectRatio);
	},
	
	/**
	 * Switch to full screen mode
	 */
	fullScreen: function() {
		window[this.playerId + 'onStateChange'](52);
		this.playerCanvas.width('100%').height('100%');
	},
	
	/**
	 * Switch to framed mode
	 */
	framed: function() {
		window[this.playerId + 'onStateChange'](53);
		this.playerCanvas.removeAttr('style').height(this.calculateAspectRatioHeight());
	},
	
	/**
	 * Returns true if the player is full screen, false if not
	 */
	isFullScreen: function() {
		return this.player.hasClass('full-screen');
	},
	
	/**
	 * Updates the player's progress bar
	 */
	updateProgress: function() {
		this.player.find('.player-buffered').css('width', (this.getVideoBytesLoaded() * 100) / this.getVideoBytesTotal() + '%');
		this.player.find('.player-elapsed').css('width', (this.getCurrentTime() * 100) / this.getDuration() + '%');
	},
	
	/**
	 * Set the progress interval
	 */
	setProgressInterval: function() {
		if(!this.playing) {
			var self = this;
			
			this.interval = setInterval(function() {
				self.updateProgress();
			}, 100);
		}
	},
	
	/**
	 * Clear the progress interval
	 */
	clearProgressInterval: function() {
		if(this.playing) {
			clearInterval(this.interval);
			this.interval = null;
		}
	},
	
	
	/**
	 * Event handlers
	 */
	
	/**
	 * When the player is ready
	 */
	handlePlayerReady: function(playerId) {
		/**
		 * Skip this part if the player was ready before.
		 * Needed for Firefox due to the reflow bug (https://bugzilla.mozilla.org/show_bug.cgi?id=90268)
		 */
		if(!this.ready) {
			this.fireCustomEvent('onReady');
			this.bindEventHandlers();
			this.ready = true;
		}
			
		var videoId = this.el.attr('href').replace(this.VIDEO_ID_REGEX, '$1');
		
		if(this.options.autoPlay) {
			this.loadVideoById(videoId);
		}
		else {
			this.cueVideoById(videoId);
		}
		
		if(this.options.startMuted) {
			this.mute();
		}
	},
	
	/**
	 * When a video ends
	 */
	handlePlayerEnd: function() {
		this.fireCustomEvent('onEnd');
		this.player.removeClass('playing');
		this.clearProgressInterval();
		this.playing = false;
	},
	
	/**
	 * When the player starts playing
	 */
	handlePlayerPlay: function() {
		this.fireCustomEvent('onPlay');
		this.player.addClass('playing');
		this.setProgressInterval();
		this.playing = true;
	},
	
	/**
	 * When the player is paused
	 */
	handlePlayerPause: function() {
		this.fireCustomEvent('onPause');
		this.player.removeClass('playing');
		this.clearProgressInterval();
		this.playing = false;
	},
	
	/**
	 * When the player is buffering
	 */
	handlePlayerBuffering: function() {
		this.fireCustomEvent('onBuffering');
	},
	
	/**
	 * When a video is cued
	 */
	handlePlayerCue: function() {
		this.fireCustomEvent('onCue');
	},
	
	/**
	 * When the player is muted
	 */
	handlePlayerMute: function() {
		this.fireCustomEvent('onMute');
		this.player.addClass('muted');
	},
	
	/**
	 * When the player is unmuted
	 */
	handlePlayerUnMute: function() {
		this.fireCustomEvent('onUnMute');
		this.player.removeClass('muted');
	},
	
	/**
	 * When the player goes to full screen mode
	 */	
	handlePlayerFullScreen: function() {
		this.fireCustomEvent('onFullScreen');
		this.player.addClass('full-screen');
	},
	
	/**
	 * When the player goes to framed mode
	 */	
	handlePlayerFramed: function() {
		this.fireCustomEvent('onFramed');
		this.player.removeClass('full-screen');
	},
	
	/**
	 * When a request contains an invalid parameter
	 */
	handleInvalidParameter: function() {
		this.fireCustomEvent('onInvalidParameterError');
	},
	
	/**
	 * When the video requested is not found
	 */
	handleNotFound: function() {
		this.fireCustomEvent('onNotFoundError');
	},
	
	/**
	 * When the video requested does not allow playback in the embedded players
	 */
	handleNoEmbed: function() {
		this.fireCustomEvent('onNoEmbedError');
	},
	
	
	/**
	 * Player API
	 */
	
	/**
	 * Loads the specified video's thumbnail and prepares the player to play the video by ID.
	 * 
	 * @param {String} videoId The YouTube Video ID of the video to be played
	 * @param {Number} startSeconds The time from which the video should start playing
	 * @param {String} suggestedQuality The suggested playback quality for the video
	 */
	cueVideoById: function(videoId, startSeconds, suggestedQuality) {
		this.playerObject.cueVideoById(videoId, startSeconds, suggestedQuality);
	},
	
	/**
	 * Loads and plays the specified video by ID.
	 * 
	 * @param {String} videoId The YouTube Video ID of the video to be played
	 * @param {Number} startSeconds The time from which the video should start playing
	 * @param {String} suggestedQuality The suggested playback quality for the video
	 */
	loadVideoById: function(videoId, startSeconds, suggestedQuality) {
		this.playerObject.loadVideoById(videoId, startSeconds, suggestedQuality);
	},
	
	/**
	 * Loads the specified video's thumbnail and prepares the player to play the video by URL.
	 * 
	 * @param {String} mediaContentUrl A fully qualified YouTube player URL (http://www.youtube.com/e/VIDEO_ID)
	 * @param {Number} startSeconds The time from which the video should start playing
	 */
	cueVideoByUrl: function(mediaContentUrl, startSeconds) {
		this.playerObject.cueVideoByUrl(mediaContentUrl, startSeconds);
	},
	
	/**
	 * Loads and plays the specified video by URL.
	 * 
	 * @param {String} mediaContentUrl A fully qualified YouTube player URL (http://www.youtube.com/e/VIDEO_ID)
	 * @param {Number} startSeconds The time from which the video should start playing
	 */
	loadVideoByUrl: function(mediaContentUrl, startSeconds) {
		this.playerObject.loadVideoByUrl(mediaContentUrl, startSeconds);
	},
	
	/**
	 * Plays the currently cued/loaded video.
	 */
	playVideo: function() {
		this.playerObject.playVideo();
	},
	
	/**
	 * Pauses the currently playing video.
	 */
	pauseVideo: function() {
		this.playerObject.pauseVideo();
	},
	
	/**
	 * Stops and cancels loading of the current video.
	 */
	stopVideo: function() {
		this.playerObject.stopVideo();
	},
	
	/**
	 * Seeks to a specified time in the video.
	 * 
	 * @param {Number} seconds 
	 * @param {Boolean} allowSeekAhead 
	 */
	seekTo: function(seconds, allowSeekAhead) {
		this.playerObject.seekTo(seconds, allowSeekAhead);
	},
	
	/**
	 * Mutes the player.
	 */
	mute: function() {
		this.playerObject.mute();
		window[this.playerId + 'onStateChange'](50);
	},
	
	/**
	 * Unmutes the player.
	 */
	unMute: function() {
		this.playerObject.unMute();
		window[this.playerId + 'onStateChange'](51);
	},
	
	/**
	 * Returns true if the player is muted, false if not.
	 */
	isMuted: function() {
		return this.playerObject.isMuted();
	},
	
	/**
	 * Returns the player's current volume.
	 */
	getVolume: function() {
		return this.playerObject.getVolume();
	},
	
	/**
	 * Sets the volume.
	 * 
	 * @param {Number} volume An integer between 0 and 100
	 */
	setVolume: function(volume) {
		this.playerObject.setVolume(volume);
	},
	
	/**
	 * Sets the size in pixels of the player.
	 * 
	 * @param {Number} width The width in pixels
	 * @param {Number} height The height in pixels
	 */
	setSize: function(width, height) {
		this.playerObject.setSize(width, height);
	},
	
	/**
	 * Returns the number of bytes loaded for the current video.
	 */
	getVideoBytesLoaded: function() {
		return this.playerObject.getVideoBytesLoaded();
	},
	
	/**
	 * Returns the size in bytes of the currently loaded/playing video.
	 */
	getVideoBytesTotal: function() {
		return this.playerObject.getVideoBytesTotal();
	},
	
	/**
	 * Returns the number of bytes the video file started loading from.
	 */
	getVideoStartBytes: function() {
		return this.playerObject.getVideoStartBytes();
	},
	
	/**
	 * Returns the state of the player.
	 */
	getPlayerState: function() {
		return this.playerObject.getPlayerState();
	},
	
	/**
	 * Returns the elapsed time in seconds since the video started playing.
	 */
	getCurrentTime: function() {
		return this.playerObject.getCurrentTime();
	},
	
	/**
	 * Returns the actual video quality of the current video.
	 */
	getPlaybackQuality: function() {
		return this.playerObject.getPlaybackQuality();
	},
	
	/**
	 * Sets the suggested video quality for the current video.
	 * 
	 * @param {String} suggestedQuality The suggested playback quality for the video
	 */
	setPlaybackQuality: function(suggestedQuality) {
		this.playerObject.setPlaybackQuality(suggestedQuality);
	},
	
	/**
	 * Returns the set of quality formats in which the current video is available.
	 */
	getAvailableQualityLevels: function() {
		return this.playerObject.getAvailableQualityLevels();
	},
	
	/**
	 * Returns the duration in seconds of the currently playing video.
	 */
	getDuration: function() {
		return this.playerObject.getDuration();
	},
	
	/**
	 * Returns the YouTube.com URL for the currently loaded/playing video.
	 */
	getVideoUrl: function() {
		return this.playerObject.getVideoUrl();
	},
	
	/**
	 * Returns the embed code for the currently loaded/playing video.
	 */
	getVideoEmbedCode: function() {
		return this.playerObject.getVideoEmbedCode();
	}
};
