/*
 * Javascript Module Help
 * @thanks http://www.wait-till-i.com/2007/08/22/again-with-the-module-pattern-reveal-something-to-the-world/
 * HTML5 Help
 * @thanks http://dev.opera.com/articles/view/everything-you-need-to-know-about-html5-video-and-audio/
 * @thanks http://diveintohtml5.org/video.html
 * @thanks http://henriksjokvist.net/archive/2009/2/using-the-html5-video-tag-with-a-flash-fallback
 */
var Landlord = function()
{
	var UNDEFINED = "undefined",
		OBJECT = "object",
		STRING = "string",
		CONSOLE = false,
		DEBUG = true,
		HTML5 = false,
		JAVASCRIPT = true,			/* This is pretty obvious */
		FORCEFLASH = false,
		SWFOBJECT = false,
		JQUERY = false,
		
		video,
		replace,
		width = 1,
		height = 1,

		playlist = {},
		isPlaying = false,			/* c'mon, is this really not built in? */
		lasttimeupdate = 0.0,
		
		currentVideo = 0,
		
		setTimeoutInterval = 0,
		autohide = true,
		animating = false,
		animateInFunction,
		animateOutFunction,
		
		cuepoints = [],
		currentCuePoint = 0//,		/* currentCuePoint is the next expected cue point */
					
		init = function() {
			CONSOLE = (typeof console == UNDEFINED)? false: true;
			//log("Landlord::init");
			
			/*if(FORCEFLASH) {
				HTML5 = false;
			}
			else {
				var v = document.createElement("video");
				HTML5 = ( v.play )? true: false;
			}*/

			SWFOBJECT = (typeof swfobject == UNDEFINED)? false: true;
			JQUERY = (typeof jQuery == UNDEFINED)? false: true;
		}();

	// @thanks http://www.bennadel.com/blog/1871-Translating-Global-jQuery-Event-Coordinates-To-A-Local-Context.htm
	$.globalToLocal = function( context, globalX, globalY )
	{
		var position = context.offset();
		return({
			x: Math.floor( globalX - position.left ),
			y: Math.floor( globalY - position.top )
		});
	}

	$.fn.globalToLocal = function( globalX, globalY )
	{
		return(
			$.globalToLocal(
				this.first(),
				globalX,
				globalY
			)
		);
	};

	function log(message, target) {
		if(DEBUG) {
			if(typeof target != UNDEFINED) {
				$(target).text($(target).text() + "\n" + message);
			}
			else if(CONSOLE) {
				console.log(message);
			}
			else {
				// ???
			}
		}
	}
	
	/*
	 * TODO: Make this work with multiple videos per page
	 */
	function updateUI() {
		//log("Landlord::updateUI");
		if(video.paused) {
			//log("paused");
			$('.play').removeClass("on"); $('.play').addClass("off");
			$('.pause').removeClass("off"); $('.pause').addClass("on");
			$('.playpause').removeClass("on"); $('.playpause').addClass("off");
		}
		// @todo, this isn't correct, check other variables in the player
		if(!video.paused) {
			//log("playing");
			$('.play').removeClass("off"); $('.play').addClass("on");
			$('.pause').removeClass("on"); $('.pause').addClass("off");
			$('.playpause').removeClass("off"); $('.playpause').addClass("on");
		}
		
		//$('.seekbarfilling').width( $('.seekbar').width() * (video.currentTime / video.duration) );
	}
	
	function hideSkinTimeout() {
		log("Landlord::hideSkinTimeout");
		if(!video.paused) {
			if(autohide) {
				if(typeof animateOutFunction != UNDEFINED) {
					animateOutFunction();
				}
				else {
					$(".skin").css('cursor', 'none');
					$(".bar").animate({
						opacity: 0.0
					},
					500,
					function(){});
				}
			}
		}
	}
	
	// Browser Event Handlers
	function bindBrowserEvents() {
		$(video).bind("progress", progress);
		$(video).bind("error", error);
		$(video).bind("timeupdate", timeUpdate);
		
		/*
		loadstart
		suspend
		abort
		emptied
		stalled
		play
		pause
		loadedmetadata
		loadeddata
		waiting
		playing
		canplay
		canplaythrough
		seeking
		seeked
		timeupdate
		ended
		ratechange
		durationchange
		volumechange
		*/
	}

	function progress(event) {
		//$('.seekbarloading').width( $('.seekbar').width() * (video.currentTime / video.duration) );
		log("Landlord::progress");
	}

	function error(event) {
		switch (event.target.error.code) {
			case event.target.error.MEDIA_ERR_ABORTED:
				log('You aborted the video playback.');
				break;
			case event.target.error.MEDIA_ERR_NETWORK:
				log('A network error caused the video download to fail part-way.');
				break;
			case event.target.error.MEDIA_ERR_DECODE:
				log('The video playback was aborted due to a corruption problem or because the video used features your browser did not support.');
				break;
			case event.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
				log('The video could not be loaded, either because the server or network failed or because the format is not supported.');
				break;
			default:
				log('An unknown error occurred.');
				break;
		}
	}

	function timeUpdate(event) {
		//log("Landlord::timeUpdate");
		$('.seekbarfilling').width( $('.seekbar').width() * (video.currentTime / video.duration) );
		
		if( (currentCuePoint < cuepoints.length) && (lasttimeupdate <= cuepoints[currentCuePoint].time) && (cuepoints[currentCuePoint].time < video.currentTime) ) {
			eval(cuepoints[currentCuePoint].parameters.method + "('" + cuepoints[currentCuePoint].parameters + "');");
			currentCuePoint++;
		}
		lasttimeupdate = video.currentTime;
	}
	
	function progress(event) {
		//log("Landlord::progress");
	}
	
	// Custom Event Handlers
	function cuepoint() {
		//must be playing
	}
	
	// UI Event Handlers
	
	function bindUIEvents() {
		$('.play').live("click", function(event) { play(event); updateUI(); });
		$('.pause').live("click", function(event) { pause(event); updateUI(); });
		$('.playpause').live("click", function(event) { playpause(event); updateUI(); });
		//$('.stop').live("click", function(event) { pause(event); updateUI(); });
		$('.next').live("click", function(event) { next(event); updateUI(); });
		$('.previous').live("click", function(event) { previous(event); updateUI(); });
		$('.seekbar').live("click", function(event) { seek(event); updateUI(); });
		$('.skin').live("mousemove", function(event) { mousemove(event); updateUI(); });
		//$('.fullscreen').live("click", function(event) { fullscreen(event); updateUI(); });
		$('.mute').live("click", function(event) { mute(event); updateUI(); });
	}

	function play(event) {
		//log("Landlord::play");
		video.play();
		// Note: We check for and process any cue points between currentTime = 0 and the first time timeUpdate fires
		//if()
		//console.log("video.currentTime (play): " + video.currentTime);
	}
	
	/*function fullscreen(event) {
		log("Landlord::fullscreen");
		video.pause();
		$('body').append('<div style="background-color:#000000; width:100%;height:100%;position:fixed; top:0; left:0;z-index:1000;"></div>');		
		$(video).parent().move().appendTo('body').css(
			{
				position: 'absolute',
				top: 0,
				left: 0,
				zIndex:1500
			}
		);
		$(video).width = '100%';
		//video.play();
	}*/
	
	function mute(event) {
		//log("Landlord::mute");
		if ($(event.currentTarget).is('.off')) {
			$(event.currentTarget).addClass('on').removeClass('off');
			video.volume = 0;
		}
		else {
			$(event.currentTarget).addClass('off').removeClass('on');
			video.volume = 1;
		}
	}
	
	function playVideoByName(videoPlayerID, videoName)
	{
		log("Landlord::playVideoByName");

		switch(true)
		{
			case(HTML5 && JQUERY):
				//video.play();
				// Note: We check for and process any cue points between currentTime = 0 and the first time timeUpdate fires
				//if()
				//console.log("video.currentTime (play): " + video.currentTime);
				break;
			case(SWFOBJECT):
				//log("FLASH METHOD");
				sendMessage(videoPlayerID, "playVideoByName", videoName);
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}
	}

	function playByIndex(videoPlayerID, videoIndex)
	{
		log("Landlord::playByIndex");

		switch(true)
		{
			case(HTML5 && JQUERY):
				//video.play();
				// Note: We check for and process any cue points between currentTime = 0 and the first time timeUpdate fires
				//if()
				//console.log("video.currentTime (play): " + video.currentTime);
				break;
			case(SWFOBJECT):
				sendMessage(videoPlayerID, "playVideoByIndex", videoIndex);
				//log("FLASH METHOD");
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}
	}
	
	function pause(event) {
		video.pause();
	}

	function playpause(event) {
		if(video.paused) {
			video.play();
		}
		else {
			video.pause();
		}
	}
	
	function next(event)
	{
		//log("Landlord::next");

		currentVideo = (currentVideo < playlist.videos.length - 1)? currentVideo + 1: 0;
		var isPlaying = !video.paused;
		if(isPlaying) video.pause();
		loadVideo();
		if(isPlaying) video.play();
	}

	function previous(event)
	{
		//log("Landlord::previous");

		currentVideo = (0 < currentVideo)? currentVideo - 1: playlist.videos.length - 1;
		var isPlaying = !video.paused;
		if(isPlaying) video.pause();
		loadVideo();
		if(isPlaying) video.play();
	}	
	
	function seek(event) {
		//log("Landlord::seek");
		seekbar = ($(event.target).is('.seekbarfilling')) ? $(event.target).parent() : $(event.target);
		var localCoordinates = seekbar.globalToLocal(event.pageX, event.pageY);
		video.currentTime = video.duration * (localCoordinates.x / seekbar.width());
	}
	
	function mousemove(event) {
		if(autohide) {
			if(!animating) {
				animating = true;
				if(typeof animateInFunction != UNDEFINED) {
					animateInFunction();
				}
				else {
					$(".skin").css('cursor', 'auto');
					$(".bar").animate({
						opacity: 1.0
					}, 250, function(){
						animating = false;
						clearInterval(setTimeoutInterval);
						setTimeoutInterval = setTimeout(hideSkinTimeout, 2000);
					});
				}
			}
		}
	}
	
	function addCuePoint(time, name, parameters)
	{
		log("Landlord::addCuePoint");
		
		var cuePoint = new Object();
		cuePoint.time = time;
		cuePoint.name = name;
		cuePoint.parameters = parameters;
		cuepoints.push(cuePoint);
	}
	
	function loadCuePoints()
	{
		log("Landlord::loadCuePoints");

		cuepoints = [];
		currentCuePoint = 0;
		if(playlist.videos[currentVideo].cuepoints)
		{
			for(var i = 0; i < playlist.videos[currentVideo].cuepoints.length; i++)
			{
				var minutesseconds = playlist.videos[currentVideo].cuepoints[i].time.split(".")[0];
				var seconds = parseInt(minutesseconds.split(":")[0]) * 60 + Number(minutesseconds.split(":")[1]);
				var milliseconds = parseInt(playlist.videos[currentVideo].cuepoints[i].time.split(".")[1]);
		
				var cueName = playlist.videos[currentVideo].cuepoints[i].name;
				var cueTime = seconds + milliseconds / 100;
				var cueParameters = (playlist.videos[currentVideo].cuepoints[i].parameters) ? playlist.videos[currentVideo].cuepoints[i].parameters : new Object();
				
				addCuePoint(cueTime, cueName, cueParameters);
			}
		}
	}
	
	function loadPlaylist(source) {
		log("Landlord::loadPlayist");
		
		switch(typeof source) {
			case OBJECT:
				loadPlaylistCallback(source);
				break;				
			case STRING:
				$.ajax({
					url: source,
					dataType: 'json',
					data: null,
					success: function(data) { loadPlaylistCallback(data); }
				});
				break;				
			default:
				loadPlaylistCallback({});
				break;				
		}
	}
	
	/*
	 * initPlaylist takes JSON data and prepares a playlist in a format that the rest of the
	 * application expects.
	 */
	function initPlaylist(data) {
		log("Landlord::initPlaylist");
		playlist = data;

		if(typeof playlist.videos == UNDEFINED) playlist.videos = [];
		if(typeof playlist.settings.volume == UNDEFINED) playlist.settings.volume = 1.0;
		if(typeof playlist.settings.autostart == UNDEFINED) playlist.settings.autostart = false;
	}
	
	/*
	 * initSettings sets up the player based on initial settings specified in the playlist object
	 * A video element should already be setup before calling this (private method maybe?)
	 */
	function initSettings()
	{
		log("Landlord::initSettings");
		video.volume = playlist.settings.volume;
		autohide = (playlist.settings.autohide == UNDEFINED)? false: playlist.settings.autohide;
		
		if(playlist.settings.autostart) play();
	}
	
	
	/*
	 * loadVideo can only be called if there is a valid video object
	 * loading the video in a separate function allows this step to be part of many different methods later.
	 * This includes hitting next, previous, choosing a video from a list or even loading a whole new playlist
	 */
	function loadVideo()
	{
		log("Landlord::loadVideo");
		
		$(video).children("source:first").nextAll("source").remove();
		$(video).children("source").replaceWith(generateVideoSource);
		video.load();
		lasttimeupdate = 0; //video.currentTime;

		

		/* log("lasttimeupdate: " + lasttimeupdate); */
	}
	
	/*
	 * The playlist file is stored in JSON, so no extra parsing is required
	 */
	function loadPlaylistCallback(data) {
		log("Landlord::loadPlaylistCallback");
		
		initPlaylist(data);

		if(typeof video == UNDEFINED) {
			createPlayer();
		}
		loadCuePoints();
		loadVideo();
		initSettings();		
	}
	
	function generateVideoSource()
	{
		log("Landlord::generateVideoSource");

		html = '';
		if(playlist.videos.length == 0)
		{
			// Empty source tags cause errors.
			// html += '<source />';
		}
		else
		{
			for(format in playlist.videos[currentVideo].formats)
			{
				// TODO:Test required format against browser and fallback to Flash if not present
				html += '<source src="' + playlist.videos[currentVideo].formats[format] + '" type="video/' + format + '" />';
			}
		}
		return html;
	}

	function generatePlayerSource() {
		log("Landlord::generatePlayerSource");
		// src, loop, preload

		var videoHTML = "";
			videoHTML += ' id="' + replace + '_video"';
			
			// NOTE: Autostart is handled by the class as there's more setup to be done
			
			videoHTML += ' width="' + width + '"';
			videoHTML += ' height="' + height + '"';
			if (playlist.settings.controls) {
				videoHTML += ' controls';
			}
			videoHTML += ' poster="'+playlist.videos[0].poster+'"';
			
			//if(playlist.settings.controls == true) video += ' controls="controls"';
	
		var html = '';
			html += '<video ' + videoHTML + '>';
			// Use empty source tags for now, these will get replaced in a bit
			html += '<source />';
			html += '</video>';
		return html;
	}
	
	/*
	 * createPlayer only gets called once per page load per player
	 */
	function createPlayer() {
		log("Landlord::createPlayer");

		$("#" + replace + " div.skin").show();		
		$("#" + replace + " div.skin div.video").replaceWith(generatePlayerSource);
		video = $("#" + replace + " video")[0]; // replace should be done with from now on...

		bindBrowserEvents();
		bindUIEvents();
		updateUI();// Should this be called here?
	}
	
	return {
		loadVideoPlayer: function(config) {
			if(typeof config.settings.forceflash != UNDEFINED) {
				FORCEFLASH = config.settings.forceflash;
			}
			
			if(FORCEFLASH) {
				HTML5 = false;
			}
			else {
				var v = document.createElement("video");
				HTML5 = ( v.play )? true: false;
			}
			
			if(JAVASCRIPT && JQUERY) {
				$(".nojavascript").hide(); 
				$(".novideo").show();
			}
			
			switch(true) {
				case(HTML5 && JQUERY): //if using HTML 5 video player
					replace = config.settings.html.id;
					$("#" + replace + " div.novideo").hide();
					width = config.settings.html.width;
					height = config.settings.html.height;
					loadPlaylist(config.playlist);
					setTimeoutInterval = setTimeout(hideSkinTimeout, 2000);
					break;
				case(SWFOBJECT):
					//$("#" + replace + " div.novideo").hide();
					f = config.settings.flash;
					
					var flashvars = f.flashvars;
					var params = f.params;
					var attributes = f.attributes;
					swfobject.embedSWF(f.swfUrl, f.id, f.width, f.height, f.version, f.expressInstallSwfurl, flashvars, params, attributes);
					
					break;
				default:
					/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
			}			
		},
		play: function() {
			//log("Landlord::play (external)");
			//this.play(); // Would call this method
			//play();
		},
		playVideoByIndex: function(videoPlayerID, videoIndex) {
			//log("Landlord::play(" + videoName + ")");
			//this.play(); // Would call this method
			//playByName(videoPlayerID, videoName);
		},
		playVideoByName: function(videoPlayerID, videoName) {
			//log("Landlord::play(" + videoName + ")");
			//this.play(); // Would call this method
			playVideoByName(videoPlayerID, videoName);
		}
	}
}();
