/*
 * 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 = true,
		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
			)
		);
	};

	//log = function(message, target) // Whats the difference between this and that?
	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(autohide)
		{
			if(typeof animateOutFunction != UNDEFINED)
			{
				animateOutFunction();
			}
			else
			{
				$(".skin").css('cursor', 'none');
				$(".bar").animate({opacity: 0.0, top: video.height}, 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(); });
	}

	function play(event)
	{
		log("Landlord::play");

		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("SWFOBJECT");
				sendMessage("videoPlayer", "playVideo", "{}");
				//log("FLASH METHOD");
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}			

	}

	function playByName(videoPlayerID, videoName)
	{
		log("Landlord::playByName");

		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, "playVideoByName", videoName);
				//log("FLASH METHOD");
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}			

	}
	
	function pause(event)
	{
		//log("Landlord::stop");
		switch(true)
		{
			case(HTML5 && JQUERY):
				video.pause();
				break;
			case(SWFOBJECT):
				log("FLASH METHOD");
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}			
	}
	
	function pauseVideo(videoPlayerID)
	{
		//log("Landlord::stop");
		switch(true)
		{
			case(HTML5 && JQUERY):
				video.pause();
				break;
			case(SWFOBJECT):
				log("FLASH METHOD");
				sendMessage(videoPlayerID, "stopVideo", "{}");
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}			
	}

	function playpause(event)
	{
		//log("Landlord::stop");
		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");
		var localCoordinates = $(event.target).globalToLocal(event.pageX, event.pageY);
		video.currentTime = video.duration * (localCoordinates.x / $(event.target).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, top: video.height - $(".bar").height()}, 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.length == 0) return;
		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: playlist,
					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;

		/* return; */
	}
	
	/*
	 * 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");
		
		switch(true)
		{
			case(HTML5 && JQUERY):
				$(video).children("source:first").nextAll("source").remove();
				$(video).children("source").replaceWith(generateVideoSource);
				video.load();
				lasttimeupdate = 0; //video.currentTime;
				break;
			case(SWFOBJECT):
				log("Landlord::loadVideo (SWFOBJECT / FLASH)");
				// TODO: Make this more dynamic
				//sendMessage("videoPlayer", "loadJSON", JSON.stringify({"type": "json", "playlist": JSON.stringify(playlist)}));
				break;
			default:
				/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
		}			

		/* 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 + '"';
			
			// 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 == 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 {
		/* Public Interface */
		loadVideoPlayer: function(config)
		{
			//log("Landlord::loadVideoPlayer");
			log(JSON.stringify(config));
			
			// TODO: Fix this, doesn't work
			//if(typeof config.settings.forceflash != UNDEFINED) { FORCEFLASH = config.settings.forceflash; }
			
			if(JAVASCRIPT && JQUERY) { $(".nojavascript").hide(); $(".novideo").show(); }
			switch(true)
			{
				case(HTML5 && JQUERY):
					//log("HTML5 && JQUERY");
					$("#" + config.settings.html.id + " div.novideo").hide();
					replace = config.settings.html.id;
					width = config.settings.html.width;
					height = config.settings.html.height;
					loadPlaylist(config.playlist);
					setTimeoutInterval = setTimeout(hideSkinTimeout, 2000);
					break;
				case(SWFOBJECT):
					//log("SWFOBJECT");
					$("#" + config.settings.html.id + " div.novideo").hide();
					//log(_flashvars);
					//swfobject.embedSWF(_swf, _replace, _width, _height, _swfversion, _swfinstall, _flashvars, _params, _attributes, _callback);
					// TODO: Implement callback parameter
					f = config.settings.flash;
					
					swfobject.embedSWF(f.swfUrl, f.id, f.width, f.height, f.version, f.expressInstallSwfurl, {"type": f.flashvars.type, "playlist": f.flashvars.file}, f.params, f.attributes);
					break;
				default:
					/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
			}			
		},
		loadConfig: function(videoPlayer, p_config)
		{
			//log("videoPlayer: " + videoPlayer);
			//log("playlist...");
			//log(playlist);
			switch(true)
			{
				case(HTML5 && JQUERY):
					playlist = p_config.playlist.videos;
					loadVideo();
					break;
				case(SWFOBJECT):
					//log("Landlord::loadVideo (SWFOBJECT / FLASH)");
					// TODO: Make this more dynamic
					//console.log("xxx:" + p_config.playlist);
					sendMessage("videoPlayer", "loadJSON", JSON.stringify(p_config.playlist));
					break;
				default:
					/* log("You will need either HTML5 and JQUERY or FLASH and SWFOBJECT"); */
			}			
		},
		loadPlaylist: function(videoPlayer, p_playlist)
		{
			//log("videoPlayer: " + videoPlayer);
			//log("playlist...");
			//log(playlist);
			playlist = p_playlist;
			loadVideo();
		},
		play: function()
		{
			//log("Landlord::play (external)");
			//this.play(); // Would call this method
			play();
		},
		playByName: function(videoPlayerID, videoName)
		{
			//log("Landlord::play(" + videoName + ")");
			//this.play(); // Would call this method
			playByName(videoPlayerID, videoName);
		},
		pause: function(videoPlayerID)
		{
			pauseVideo(videoPlayerID);
		}
	}
}();

// @thanks http://www.sitepoint.com/blogs/2009/08/19/javascript-json-serialization/
/*var JSON = JSON || {};

// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {
	var t = typeof (obj);
	if (t != "object" || obj === null) {
		// simple data type
		if (t == "string") obj = '"'+obj+'"';
		return String(obj);
	}
	else {
		// recurse array or object
		var n, v, json = [], arr = (obj && obj.constructor == Array);
		for (n in obj) {
			v = obj[n]; t = typeof(v);
			if (t == "string") v = '"'+v+'"';
			else if (t == "object" && v !== null) v = JSON.stringify(v);
			json.push((arr ? "" : '"' + n + '":') + String(v));
		}
		return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
	}
};

// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
	if (str === "") str = '""';
	eval("var p=" + str + ";");
	return p;
};
*/
