/**
 * The Page object controls all functionallity on the site
 * which relates to the navigation/transition between pages,
 * the storing of user/cache data and the displaying of notifications.
 *
 * @module FrancesTwiddy
 * @class Utils
 * @author Dan Whitfield
 */
var Utils = 
{
	/**
	 * Debut output control.
	 *
	 * @property _showDebug
	 * @type Bool
	 * @private
	 */
	_showDebug: false,
	
	
	/**
	 * List of completed notifications.
	 *
	 * @property _completedNotifications
	 * @type Array
	 * @private
	 */
	_completedNotifications: [],
	
	
	/**
	 * Status of all the activity indicators.
	 *
	 * @property _activityIndicatorStatus
	 * @type Object
	 * @private
	 */
	_activityIndicatorStatus:
	{
		body: false
	},
	
	
	/**
	 * API end point.
	 *
	 * @property _apiEndPoint
	 * @type String
	 * @private
	 */
	_apiEndPoint: "api.html",
	
	
	/**
	 * Object that contains all the key mappings needed for keyboard interaction.
	 *
	 * @property keyMappings
	 * @type Object
	 * @public
	 */
	keyMappings:
	{
		"27": "esc",
		"37": "left",
		"39": "right"
	},
	
	
	/**
	 * Use the videoembed.html template to generate embed code for
	 * a video with the set params.
	 *
	 * @method showVideo
	 * @public
	 * @param {Object} params
	 */
	showVideo: function(params)
	{	
		if(typeof params.controller === "undefined")
		{
			params.controller = "true";
		}
		
		if(typeof params.autoplay === "undefined")
		{
			params.autoplay = "true";
		}
		
		var that = this;
		
		this.debugConsole("params.url: [" + params.url + "]");
		this.debugConsole("params.width: [" + params.width + "]");
		this.debugConsole("params.height: [" + params.height + "]");
		this.debugConsole("params.controller: [" + params.controller + "]");
		this.debugConsole("params.autoplay: [" + params.autoplay + "]");
		
		var cachedHTML = Cache.get(params.title);
		
		if(cachedHTML)
		{
			this._showMediaOverlay(params.title, cachedHTML);
		}
		else
		{
			var videoEmbedTemplate = "templates/videoembed.html";
			
			// Is HTML 5 video supported?
			if(Modernizr.video && Modernizr.video.h264)
			{
				params.url = params.url.replace(".mov", "-desktop.m4v");
				this.debugConsole("html5 video available switched to m4v: [" + params.url + "]");
				videoEmbedTemplate = "templates/videoembedhtml5.html";
			}
			
			var cachedRawTemplateHTML = Cache.get(videoEmbedTemplate);
			
			if(cachedRawTemplateHTML)
			{
				// Use the cached raw template.
				this._parseVideoTemplate(cachedRawTemplateHTML, params);
			}
			else
			{
				// Fetch the default video embed template.
				this.makeRequest(videoEmbedTemplate, function(rawTemplateHTML)
				{
					that._parseVideoTemplate(rawTemplateHTML, params);
					Cache.store(videoEmbedTemplate, rawTemplateHTML);
				});
			}
		}
	},
	
	
	/**
	 * Take the raw video template and inject specific video details.
	 *
	 * @method _parseVideoTemplate
	 * @private
	 * @param {String} templateHTML
	 * @param {Object} params
	 */
	_parseVideoTemplate: function(templateHTML, params) {
		// Replace all the tags with the correct video values.
		templateHTML = templateHTML.replace(/MOVIE_FILE/g, params.url);
		templateHTML = templateHTML.replace(/MOVIE_WIDTH/g, params.width);
		templateHTML = templateHTML.replace(/MOVIE_HEIGHT/g, params.height);
		templateHTML = templateHTML.replace(/MOVIE_CONTROLLER/g, params.controller);
		templateHTML = templateHTML.replace(/MOVIE_AUTOPLAY/g, params.autoplay);
		
		this.debugConsole("param tags replaced calling _showMediaOverlay");
		
		this._showMediaOverlay(params.title, templateHTML);
		
		Cache.store(params.title, templateHTML);
	 },
	
	
	/**
	 * Use the photoalbum.html template to show an album's photos in a popup.
	 *
	 * @method showPhotoAlbum
	 * @public
	 * @param {String} albumName
	 * @param {String} albumDisplayName
	 */
	showPhotoAlbum: function(albumName, albumDisplayName)
	{	
		var that = this;
		var albumInfo = null;
		
		var cachedHTML = Cache.get(albumName);
		
		if(cachedHTML)
		{
			this._showMediaOverlay(albumDisplayName, cachedHTML);
		}
		else
		{
			this.debugConsole("about to request album info for: " + albumName);
			
			var requestUrl = this.buildRequestUrl(
			{
				ajax: 1,
				albuminfo: 1,
				albumname: albumName
			});
			
			// Fetch the album info object.
			this.makeRequest(requestUrl, function(data)
			{
				albumInfo = data;
				
				var cachedRawTemplateHTML = Cache.get("photo_album_raw_template");
				
				if(cachedRawTemplateHTML)
				{	
					// Use cached raw template.
					that._parsePhotoTemplate(cachedRawTemplateHTML, albumInfo, albumName, albumDisplayName);
				}
				else
				{
					// Fetch the default photo album template.
					that.makeRequest("templates/photoalbum.html", function(rawTemplateHTML)
					{
						that.debugConsole("photoalbum.html template received");
						that._parsePhotoTemplate(rawTemplateHTML, albumInfo, albumName, albumDisplayName);
						Cache.store("photo_album_raw_template", rawTemplateHTML);
					});
				}
			});
		}
	},
	
	
	/**
	 * Take the raw photo album template and inject specific album details.
	 *
	 * @method _parsePhotoTemplate
	 * @private
	 * @param {String} templateHTML
	 * @param {Object} albumInfo
	 * @param {String} albumName
	 * @param {String} albumDisplayName
	 */
	 _parsePhotoTemplate: function(templateHTML, albumInfo, albumName, albumDisplayName)
	 {
	 	templateHTML = templateHTML.replace("IMAGE_TAG_SRC", albumInfo.imagepath + albumInfo.firstimage);
		
		var imageThumbList = "";
		var albumImages = albumInfo.imagethumbs;
		var totalAlbumImages = albumImages.length;
		var thumbClass = " class='currentlySelected'";
		
		for(var i = 0; i < totalAlbumImages; i++)
		{
			imageThumbList += "<li" + thumbClass + "><img src='" + albumInfo.imagepath + albumImages[i] + "' alt='' /></li>";
			thumbClass = "";
		}
		
		templateHTML = templateHTML.replace("IMAGE_THUMB_LIST", imageThumbList);
		
		this._showMediaOverlay(albumDisplayName, templateHTML);
		
		Cache.store(albumName, templateHTML);
	},
	
	
	/**
	 * Open photos/video in a popup from either cache or request.
	 *
	 * @method _showMediaOverlay
	 * @private
	 * @param {String} title
	 * @param {String} html
	 */
	_showMediaOverlay: function(title, html)
	{
		this.debugConsole("showing media overlay for: " + title);
		
		Page._navigationLock = true;
		
		// Pass a callback function which will display the overlay.
		this.showOverlay(title, "<div>" + html + "</div>", function()
		{
			// After the photo overlay has closed then unlock the navigation.
			Page._navigationLock = false;
		});
	},
	
	
	/**
	 * This is for any function that provides a callback argument,
	 * they can actually call the callback using this function and
	 * then they won't have to always check if the callback param
	 * is of type 'function'.
	 *
	 * @method executeCallback
	 * @public
	 * @param {Function} callback
	 * @param {Unknown} param
	 */
	executeCallback: function(callback, param)
	{
		if(typeof callback === "function")
		{
			if(typeof param != "undefined")
			{
				callback(param);
			}
			else
			{
				callback();
			}
		}
	},
	
	
	/**
	 * Any notification elements with the class of 'pageNotifications' that
	 * are on the page in the correct format (H3 for the title and a P tag
	 * for the content) will be looped and appear one after the other on page load.
	 *
	 * @method displayNotifications
	 * @public
	 */
	displayNotifications: function()
	{
		var requestUrl = this.buildRequestUrl(
		{
			ajax: 1,
			notifications: 1
		});
			
		this.makeRequest(requestUrl, function(data)
		{
			for(var key in data.notifications)
			{	
				var id      = data.notifications[key]["message_id"];
				var title   = data.notifications[key]["title"];
				var message = data.notifications[key]["message"];
				
				if(!Utils._completedNotifications[id])
				{
					Utils.showOverlay(title, message, null, true);
					Utils._completedNotifications[id] = "complete";
					Utils.debugConsole("Utils._completedNotifications[" + id + "]: ", Utils._completedNotifications[id]);
				}
			}
		});
	},
	
	
	/**
	 * Show overlays using the boxy jQuer plugin.
	 * You can also choose to set a cookie so the
	 * overlays do not appear more than once each.
	 *
	 * @method showOverlay
	 * @public
	 * @param {String} title
	 * @param {String} content
	 * @param {Function} callback
	 * @param {Bool} cookieSetGet
	 * @param {Int} cookieExpiryDays
	 */
	showOverlay: function(title, content, callback, cookieSetGet, cookieExpiryDays)
	{
		var displayOverlay = true;
		
		if(typeof cookieExpiryDays === "undefined")
		{
			cookieExpiryDays = 3600;
		}
		
		this.debugConsole("Checking for overlay cookie");
		
		if(typeof cookieSetGet != "undefined" && cookieSetGet === true)
		{
			var getCookie = this.getCookie(title);
			
			if(getCookie != null && getCookie != "")
			{
				displayOverlay = false;
				
				this.debugConsole("Overlay cookie found");
				
				if(typeof callback === "function")
				{
					this.debugConsole("executing overlay callback");
					this._executeCallback(callback);
				}
			}
			else
			{
				this.debugConsole("No overlay cookie found, setting cookie and showing overlay");
				this.setCookie(title, content, cookieExpiryDays);
			}
		}
		
		if(displayOverlay)
		{
			new Boxy(content,
			{
				title: title,
				draggable: false,
				unloadOnHide: true,
				afterShow: function()
				{
					if(Utils._activityIndicatorStatus.body)
					{
						Utils.toggleActivityIndicator();
					}
				},
				afterHide: function()
				{
					Utils.executeCallback(callback);
				}
			});
		}
	},
	
	
	/**
	 * Any images that have a class of 'imgCaption' will have a caption
	 * displayed inside their parent with the text from the image's alt attribute.
	 *
	 * @method captionSpecifiedImages
	 * @public
	 */
	captionSpecifiedImages: function()
	{
		var allImages = $(".imgCaption");
		var totalImages = allImages.length;

		for(var i = 0; i < totalImages; i++)
		{
			var altText = $(allImages[i]).attr("alt");
			$(allImages[i]).parent().append("<span class='imageCaptionBg'>" + altText + "</span>");
		}
		
		$(".imageCaptionBg").fadeIn("slow");
	},
	
	
	/**
	 * Sets cookies and makes sure that the cookie name is
	 * lower case with spaces being replaced by '_' and non
	 * alphanumeric characters being replaced by nothing.
	 *
	 * @method setCookie
	 * @public
	 * @param {String} c_name
	 * @param {String} value
	 * @param {Int} expiredays
	 */
	setCookie: function(c_name, value, expiredays)
	{
		c_name = c_name.replace(/\s/g, "_").replace(/\W/g, "").toLowerCase();
		var exdate = new Date();
		exdate.setDate(exdate.getDate() + expiredays);
		document.cookie = c_name + "=" + escape(value) + ((expiredays==null) ? "" : ";expires=" + exdate.toUTCString());
		this.debugConsole("Cookie set: ", c_name);
	},
	
	
	/**
	 * Getting cookies with the same naming conventions as setCookie.
	 *
	 * @method getCookie
	 * @public
	 * @param {String} c_name
	 */
	getCookie: function(c_name)
	{
		c_name = c_name.replace(/\s/g, "_").replace(/\W/g, "").toLowerCase();
		
		this.debugConsole("Get cookie: ", c_name);
		
		if(document.cookie.length > 0)
	  	{
	  		c_start=document.cookie.indexOf(c_name + "=");
	  		
	  		if(c_start != -1)
	    	{
	    		this.debugConsole("Cookie found: ", c_name);
	    		c_start = c_start + c_name.length + 1;
	    		c_end = document.cookie.indexOf(";", c_start);
	    		if(c_end == -1) c_end = document.cookie.length;
	    		return unescape(document.cookie.substring(c_start, c_end));
	    	}
	  	}
	  	
		return "";
	},
	
	
	/**
	 * Show and hide the activity indicator.
	 *
	 * @method toggleActivityIndicator
	 * @public
	 * @param {String} element
	 */
	toggleActivityIndicator: function(element)
	{
		// By default show the activity indicator in the body.
		if(typeof element === "undefined")
		{
			element = "body";
		}
		
		// Give each activity indicator a unique ID which is related to the element it is shown within.
		var activityIndicatorId = element.replace(/\#|\./g, "") + "ActivityIndicatorElement";
		
		// Used to store the current activity indicator's status in this._activityIndicatorStatus.
		var activityIndicatorStatusId = element.replace(/\#|\./g, "");
		
		if(this._activityIndicatorStatus[activityIndicatorStatusId] && this._activityIndicatorStatus[activityIndicatorStatusId] === true)
		{
			this.debugConsole("Removing activity indicator: " + activityIndicatorId);
			$(element + " #" + activityIndicatorId).remove();
			this._activityIndicatorStatus[activityIndicatorStatusId] = false;
		}
		else
		{
			this.debugConsole("Showing activity indicator: " + activityIndicatorId);
			$(element).append("<div id='" + activityIndicatorId + "' class='activityIndicator'><div></div></div>");
			this._activityIndicatorStatus[activityIndicatorStatusId] = true;
		}
	},
	
	
	/**
	 * All debugMessages come through here, if a console is
	 * present then it is used, else alerts are used.
	 * This is also controled by this._showDebug.
	 *
	 * @method debugConsole
	 * @public
	 * @param {String} message
	 * @param {Object} object
	 */
	debugConsole: function(message, object)
	{
		if(this._showDebug === true)
		{
			if(typeof object === "undefined")	
			{
				object = "";
			}
				
			if(window.console && window.console.firebug)
			{
				console.log(message, object);
			}
			else
			{
				if(!$(".debug_console_output").length)
				{
					new Boxy("<div class='debug_console_output_container'><ul class='debug_console_output'></ul><div>",
					{
						title: "Debug console <a class='clear_debug_console_output' href='#'>[clear]</a>"
					});
					
					// Register the clear event.
					$(".clear_debug_console_output").live("click", function(e)
					{
						$(".debug_console_output").html("");
						e.preventDefault();
					});
				}
				
				if($(".debug_console_output").length)
				{
					$(".debug_console_output").prepend("<li>" + message + " " + object + "</li>");
				}
			}
		}
	},
	
	
	/**
	 * Central place to handle all API requests.
	 *
	 * @method makeRequest
	 * @public
	 * @param {String} url
	 * @param {Function} callback
	 */
	makeRequest: function(url, callback)
	{
		var that = this;
		
		this.debugConsole("making API call: " + url);
		
		$.get(url, function(data)
		{
			that.executeCallback(callback, data);
		});
	},
	
	
	/**
	 * Build an API request url from an object.
	 *
	 * @method buildRequestUrl
	 * @public
	 * @param {Object} params
	 */
	buildRequestUrl: function(params)
	{
		var apiUrl = this._apiEndPoint.replace(/(\?)$/, "") + "?";
		
		if(typeof params !== "undefined")
		{
			for(var key in params) {
				apiUrl += key + "=" + params[key] + "&";
			}
		}
		
		apiUrl = apiUrl.replace(/(&)$/, "");
		
		return apiUrl;
	},
	
	
	/**
	 * Check the support of certain element attributes.
	 *
	 * @method elementSupportsAttribute
	 * @public
	 * @param {String} element
	 * @param {String} attribute
	 */
	elementSupportsAttribute: function(element, attribute)
	{
		var isSupported = false;
		var testElement = document.createElement(element);
		
		if(attribute in testElement)
		{
			isSupported = true;
		}
		
		return isSupported;
	},
	
	
	/**
	 * Capitalise the first character of a string.
	 *
	 * @method capitaliseFirstLetter
	 * @public
	 * @param {String} string
	 */
	capitaliseFirstLetter: function(string)
	{
	    return string.charAt(0).toUpperCase() + string.slice(1);
	}
};
