/**
 * bam.media.relatedContent
 * Loads data for video items, related to a piece of video content (contentId),
 * a video topic (topicId), a search query or some combination of these.
 *
 * @author Aleksandar Kolundzija
 * @version 1.0a
 *
 * Usage:
		bam.media.relatedContent.load({
			contentId        : "",
			topicId          : "",
			ignoreContentIds : [],
			success          : function(){},
			error            : function(){}
		})
 *
 */

bam.media.relatedContent = (function(){
																		 
	// private method for logging messages to console
	function _log(msg){
		if (typeof console!=="undefined" && _self.debugMode){ console.log("bam.media.relatedContent: " + msg); }
	}
	
	// returns true if more item should be added to index (max not reached and is not a dupe), false otherwise.
	function _shouldItemBeAdded(contentId){
		return (!_indexHash[contentId] && (_self.index.length < _self.indexMaxItems) && ($.inArray(contentId, _cfg.ignoreContentIds)===-1));
	}
	
	// Adds passed object to index of related content
	function _addToIndex(obj){
		_self.index.push(obj);
		_indexHash[obj.content_id] = true;
	}

	// returns duration for display
	/**
	 * Converts duration strings HH:MM:SS as follows:
	 * 11:59:00 -> 11:59:00
	 * 01:59:00 -> 1:59:00
	 * 00:59:00 -> 59:00
	 * 00:00:30 -> 00:30	
 	*/
	function _getDurationForDisplay(duration){
		if(!duration) return "";
		var arr= duration.split(":"),	
   			hours = +arr.shift();
   		if(hours*1) arr.unshift(hours);
		return arr && arr.join(":") || function(){
			$("#clipDate").css("border","none");
			return "";
		}();
	}
	
	
	// returns date value for display
	function _getDateForDisplay(dateStr){
		return dateStr && dateStr.substring(5,7)+'/'+dateStr.substring(8,10)+'/'+dateStr.substring(2,4) || "n/a"; 
	}
	
	/**
	 * Utility function for sorting JSON objects based on a property (or sub.property) 
	 * @param {array} objArray
	 * @param {array|string} prop Dot-delimited string or array of (sub)properties
	 * @TODO move this to a lib
	 */
	function _sortJsonArrayByProp(objArray, prop){  
		if (arguments.length<2){
			throw new Error("sortJsonArrayByProp requires 2 arguments");  
		}  
		if (objArray && objArray.constructor===Array){
			var propPath = (prop.constructor===Array) ? prop : prop.split(".");
			objArray.sort(function(a,b){
				for (var p in propPath){
					if (a[propPath[p]] && b[propPath[p]]){
						a = a[propPath[p]];
						b = b[propPath[p]];
					}
				}
				// convert numeric strings to integers
				a = a.match(/^\d+$/) ? +a : a;
				b = b.match(/^\d+$/) ? +b : b;
				return ( (a < b) ? -1 : ((a > b) ? 1 : 0) );
			});
		}
	}
	

	// private configuration obj (populated on initConfig)
	var _cfg       = {},
			_indexHash = {}, // utility var for preventing dupes via quick lookup
	
			/**
			 * Private utility object for generating search query parameters to 
			 * be used for retrieving content related (by keyword) to video clip
			 * @TODO Don't pass parameters with no (empty) values
			 */			 
			_contentKeywordUtil = {
					
				categoryHash: {}, // populated on contentKeywordUtil.init()
		
				init: function(){ 
					$.each(_cfg.searchKeywordCategories, function(i, cat){
						_contentKeywordUtil.categoryHash[cat] = [];
					});
				},
				
				getTagString: function(category){
					var tagStrPrefix = category + "=",
							tagStr       = tagStrPrefix;
					$.each(_contentKeywordUtil.categoryHash[category], function(i, tag){
						tagStr += tag + "%2B";
					});
					return (tagStr!==tagStrPrefix) ? tagStr.replace(/%2B$/,'') : ""; // strip trailing encoded comma, if present
				},
				
				getQueryString: function(){
					var query     = "&",
							curTagStr = "";
					$.each(_cfg.searchKeywordCategories, function(i, cat){
						curTagStr = _contentKeywordUtil.getTagString(cat);
						query += (curTagStr) ? curTagStr + "&" : "";
					});
					return query.replace(/&$/,''); // strip trailing &
				},
				
				loadKeywords: function(contentId){
					_contentKeywordUtil.init();
					// load content keywords and generate search query
					var clipData    = bam.media.getMetaData(contentId),
							curCatType  = "",
							curCatValue = "";
					$.each(clipData.keywords, function(){
						curCatType = $(this).attr("type");
						if (curCatType.indexOf("tax") > -1){ // map "...tax" to "...tax_key" for search
							curCatType += "_key";
						}
						if ($.inArray(curCatType, _cfg.searchKeywordCategories) > -1){
							curCatValue = $(this).attr("value");
							_contentKeywordUtil.categoryHash[curCatType].push(curCatValue);
							_log("_contentKeywordUtil.loadKeywords: added keyword: " + curCatType + " = " + curCatValue);
						}
					});				
					
				}
				
			};


	var _self = {

		// public properties
		debugMode : ~document.location.search.indexOf("debug"), // dis/enables console logging (can be set externally)
		
		topicConfig  : {}, // will hold cleaned up topic config object

		index         : [], // index of retrieved (related) content
		indexMaxItems : 30, // default max value.  should be overriden
		

		/**
		 * Initializes(resets) configuration, called at start of load()
		 */
		initConfig: function(){
			_cfg = {
				contentId               : "",
				mediaMetaDir            : "/gen/multimedia/detail/",		
				topicId                 : "",
				topicConfigDir          : "/gen/multimedia/topic/",
				indexFileOverride       : null,
				ignoreTopicIndex        : false, // use content's keywords to construct search params and don't use topic index
				searchUrl               : "/ws/search/MediaSearchService",
				searchQuery             : "",
				searchKeywordCategories : [], // ex: mlbtax_key, team_id, etc.
				maxItemsOverride        : null, // used to override maxItems value from topic config
				ignoreContentIds        : [], // allows specifying existing items, to prevent dupes
				success                 : null,
				error                   : null
			};
			// clean up variables (for next laod call)
			_self.topicConfig  = {};
			_self.index        = [];
			_indexHash         = {};			
		},


		/**
		 * Returns path to topic config file
		 */
		getPathToTopicXml: function(topicId){
			return _cfg.topicConfigDir + topicId + ".xml";
		},
		

		/**
		 * Loads related content given a config object, and executes
		 * callback provided in that config passing it index and topicConfig
		 * @param cfg Object configuration
		 */
		load: function(cfg){
			_self.initConfig(); // reset config
			$.extend(_cfg, cfg); // load config
			_self.loadTopicConfig(); // will check if _cfg.topicId is set
			_self.indexMaxItems = _cfg.maxItemsOverride || _self.topicConfig.maxItems || _self.indexMaxItems;
			if (_cfg.indexFileOverride){
				_self.loadIndexFromFile(_cfg.indexFileOverride);
			} else {
				if (_cfg.contentId){ // if content id was passed
					if ($.inArray(_cfg.contentId, _cfg.ignoreContentIds) === -1){
						_cfg.ignoreContentIds.push(_cfg.contentId); // prevent dupes
					}
					if (_cfg.useContentKeywords && _cfg.searchKeywordCategories.length){ // if using content keywords and keyword categories were passed
						_contentKeywordUtil.loadKeywords(_cfg.contentId);
						_cfg.searchQuery += _contentKeywordUtil.getQueryString();
						_self.loadSearchResults();
					}
					else {
						_self.loadIndexFromFile(_self.topicConfig.index_url);
						_self.loadSearchResults();
					}
				}
				else { // if content was NOT passed
					_self.loadIndexFromFile(_self.topicConfig.index_url);
					_self.loadSearchResults();
				}
			}
			// execute callback, if set
			if (typeof _cfg.success==="function"){
				_cfg.success(_self.index, _self.topicConfig);
			}
		},
		
		
		/**
		 * Loads XML topic config and exposes it and the topic index URL
		 */
		loadTopicConfig: function(){
			if (!_cfg.topicId){
				_log("loadTopicConfig: topic not specified.");
				return;
			}
			_self.loadTopicConfig._cache = _self.loadTopicConfig._cache || {};
			if (_self.loadTopicConfig._cache[_cfg.topicId]){ // if topic was previously loaded, load from cache
				_log("loadTopicConfig: loading from cache");
				_self.topicConfig = _self.loadTopicConfig._cache[_cfg.topicId].topicConfig;
				return;
			}
			_log("loadTopicConfig: loading new topic data");			
			// load topic config
			var topicConfigXml = _self.getPathToTopicXml(_cfg.topicId);
			$.ajax({
				type     : "GET",
				async    : false,
				url      : topicConfigXml,
				dataType : "xml",
				error    : function(){ _log("loadTopicConfig: ERROR with file: " + topicConfigXml); },
				success  : function(topicConfigData){
					_self.topicConfig = {
						headline     : $("topic headline", topicConfigData).text(),
						index_url    : $("topic video_index", topicConfigData).attr("src"),
						search_query : $("topic search_query", topicConfigData).text(),
						maxItems     : parseInt($("topic", topicConfigData).attr("maxItems"),10),
						images       : {
							top    : $("topic image[type='top']",    topicConfigData).attr("src"),
							bottom : $("topic image[type='bottom']", topicConfigData).attr("src")
						}
					};
					// save to cache
					_self.loadTopicConfig._cache[_cfg.topicId] = {topicConfig: _self.topicConfig};
				}
			});
		},
		
		
		/**
		 * Loads XML topic index and adds items from it to _self.index
		 * Supports both Newsroom and HomeBase XML formats.
		 */
		loadIndexFromFile: function(indexUrl){
			_log("loadIndexFromFile");
			if (!indexUrl || !/[\w\-]+\.\w+$/.test(indexUrl)){
				_log("loadIndexFromFile: index filepath not specified or invalid. exiting.");
				return;
			}
			
			
			/**
			 * converts keywords xml to javascript object
			 * massages data to match search results
			 */
			function getKeywords(keywords){
				var arr = [], out, name, value, keyword;				
				keywords.each(function(){
					out = {};		
					keyword = this;			
					$.each(keyword.attributes || [], function(i, attrib){
						name = attrib.name;
						value = attrib.value;					
						if(name === "key") {
							out.type = value;
							out.keyword = $(keyword).text();
						} else {
							out[name] = value;
						}
  					});
					arr.push(out);
				});
				return arr;
			}
						
			
			/**
			 * Private function for storing formatted index from topic index data
			 * used as ajax success handler and when source data is cached. 
			 */
			function processIndexData(indexData){
				var curContentId, $curItem;				
				$("data item", indexData).each(function(i, curItem){
					$curItem     = $(curItem);
					curContentId = $curItem.attr("content_id");
					if (_shouldItemBeAdded(curContentId)){
						_addToIndex({
							// @VERIFY: that nothing breaks when these data points don't exist
							content_id : curContentId,
							headline   : $curItem.find(">blurb").text() || $curItem.find(">description").text(),
							thumb      : $curItem.find(">pictures picture[type='small-text-graphic'] url").text() || 
														$curItem.find(">images image[type='7']").text() ||
														$curItem.find(">pictures picture[type='medium-thumbnail'] url").text(),
							duration   : _getDurationForDisplay($curItem.find(">duration").text()),
							date_added : _getDateForDisplay($curItem.attr("date")),
							keywords   : getKeywords($curItem.find("keyword")),
							feature_context : $curItem.find(">feature-context").text()																		
						});												
					}
					if (_self.index.length >= _self.indexMaxItems){
						return false; // exit if no more items are needed
					}
				});
				_self.loadIndexFromFile._cache[indexUrl] = indexData;
			}
			_self.loadIndexFromFile._cache = _self.loadIndexFromFile._cache || {};
			if (_self.loadIndexFromFile._cache[_self.topicConfig.index_url]){
				_log("loadIndexFromFile: loading from cache");
				processIndexData(_self.loadIndexFromFile._cache[_self.topicConfig.index_url]);
				return;
			}
			// load topic index
			$.ajax({
				type     : "GET",
				async    : false,
				cache    : true,
				url      : indexUrl,
				dataType : "xml",
				success  : processIndexData,
				error    : function(){ _log("loadIndexFromFile: ERROR loading topic index: " + indexUrl);	}
			});
		},

		
		/**
		 * If max number of index items hasn't been reached, hits search service 
		 * and returns JSON data which is then added to the index.
		 */
		loadSearchResults: function(){
			_log("loadSearchResults");
			var searchQuery = _cfg.searchQuery || _self.topicConfig.search_query;
			if (searchQuery){
				searchQuery += "&hitsPerPage="+_self.indexMaxItems+"&src=vpp";
			}
			if (!_cfg.searchUrl || !searchQuery){
				_log("loadSearchResults: ERROR - exiting since either searchUrl or searchQuery is undefined");
				return;
			}			
			/**
			 * Private function for storing formatted search data, used by success handler
			 * after search query and elsewhere.
			 */
			function processSearchResults(searchData){
				_log("loadSearchResults: storing search results to index");
		
				if (searchData.mediaContent && searchData.mediaContent.constructor===Array){
					// if searchQuery contains sort_type=date, then sort searchData.mediaContent by content_id					
					if (~searchQuery.indexOf("sort_type=date")){
						_sortJsonArrayByProp(searchData.mediaContent, "contentId");
						searchData.mediaContent.reverse();
					}					
					$.each(searchData.mediaContent, function(i, obj){
						if ( obj.contentId && obj.blurb && _shouldItemBeAdded(obj.contentId) ){
							obj.thumbnailSrc = "";
							if (obj.thumbnails && obj.thumbnails.constructor===Array){
								$.each(obj.thumbnails, function(i, thumb){ 
									if (thumb.type == "7"){ 
										obj.thumbnailSrc = thumb.src; // add property for appending to DOM
										return false;
									}
								});
							}
							_addToIndex({
								content_id : obj.contentId,
								headline   : obj.blurb,
								thumb      : obj.thumbnailSrc,
								date_added : obj.date_added.replace(/\d{2}(\d{2})/, "$1"),
								duration   : _getDurationForDisplay(obj.duration),
								keywords   : obj.keywords,
								feature_context : obj.featureContext || ""
							});
							if (_self.index.length >= _self.indexMaxItems){
								return false; // exit if no more items are needed
							}
						}
					});					
				}
				_self.loadSearchResults._cache[sUrl] = searchData;
			}			
			var sUrl = _cfg.searchUrl+"?"+searchQuery;
			_self.loadSearchResults._cache = _self.loadSearchResults._cache || {};
			if (_self.loadSearchResults._cache[sUrl]){
				_log("loadSearchResults: loading from cache");
				processSearchResults(_self.loadSearchResults._cache[sUrl]);
				return;
			}
			
			
			if (_self.index.length < _self.indexMaxItems){ // load search results, if needed						
		
				$.ajax({
					url      : sUrl,
					type     : "GET", 
					async    : false,
					cache    : true, 
					dataType : "json",
					success  : processSearchResults,								
					error    : function(){ _log("loadTopicConfig: ERROR loading topic index: " + _self.topicConfig.index_url); }
				});				
				
			}
			else {
				_log("loadSearchResults: Not loading search results. Index is already maxxed out.");
			}
		}


	};
	
	return _self;
	
})();

