/**
 * bam.media.vpp: Supporting object for the Video Playback Page
 * @author Aleksandar Kolundzija
 * @version 3.0a
 * @TODO reveal current item by scrolling it into view
 * @TODO add custom events and bind tracking to them
 */

// iPad redirect
if (bam.env && bam.env.client && bam.env.client.isIPad){
	$(function(){
		$("#videoBrowseHead").css("background-image","none");
	});
//	location.href = "/mobile/ipad/";
}

bam.media.vpp = (function(){

	var _IGNORE_KEYWORD_REGEX = new RegExp("^[A-Z_0-9]*$"), // regex for ignoring irrelevant keywords
			_userInitiatedPlay    = false, // flag used to track user requested clips separately from auto plays
			_$event               = $({}), // for custom events
			_isIPad               = (bam.env && bam.env.client && bam.env.client.isIPad);
			
	function _log(msg){
		if (typeof console!=="undefined" && _settings.debugMode){
			console.log("bam.media.vpp: " + msg);
		}
	}

	function _isPlaylistVisible(){
		var isVis = !!$(_self.index.containerId+":visible").length;
		_log("isPlaylistVisible: " + isVis);
		return isVis;
	}

	/**
	 * Private configuration object.  Can be reconfigured using configure()
	 */
	var _settings = {
	
		debugMode  : false,
		playlist   : [],

		contentUrlBase : "/video/play.jsp?content_id=",
		
		autostart          : true,
		autostartTimeoutId : null,
		autostartDelay     : 10000,
		
		index: {
			containerId  : "#vppIndexPlaylist",
			maxItems     : 60,
			itemsPerPage : 12
		},
		
		autoplaylist       : false,
		currentIndex       : 0,
		isPlaylistComplete : false,
				
		psKeys        : ["FLASH_1000K_640X360", "FLASH_800K_640X360", "FLASH_800K_512X288", 
										 "MLB_FLASH_800K_STREAM_VPP", "MLB_FLASH_800K_STREAM", 
										 "MLB_FLASH_1000K_PROGDNLD", "MLB_FLASH_800K_PROGDNLD", 
										 "v2_url"],
		prodPageUrl   : "http://mlb.mlb.com/video/play.jsp", // used for Facebook link
		shareVideoUrl : "/media/email/send.jsp",
		searchPageUrl : "/search/media.jsp",
		
		searchServiceUrl : "", // unique per property (loaded on init)
		searchParams     : "type=json&src=vpp&start=0",
		
		disableComments      : false,		
		commentsAreDisplayed : false,
		commenterProfileUrl  : "/mlb/community/profile/index.jsp?ipid=",
		
		curClipData : {}, // will hold data for currently playing clip
		
		// @TODO set these per property externally
		keywordTaxTypes : {
			"mlbtax"    : "mlbtax_key",
			"yes_tax"   : "yes_tax_key",
			"tiger_tax" : "tiger_tax_key",
			"mlstax"    : "mlstax_key",
			"sny_tax"   : "sny_tax_key"
		},
		
		keywordCategories : ["mlbtax_key", "team_id", "player_id", "game_pk"],
		
		// configuration for instance of bam.FlvPlayer
		playerCfg : {
			self           : "bam.media.vpp.flashPlayer", // used by SWF to refer to player instance
			playerSkin     : "/flash/video/y2009/skins/mlb_compact_full_feature.swf",			
			videoAlign     : "top",
			videoScaleMode : "noScale",
			autoHideSkin   : false,
			elemId         : "flashPlayer",
			containerId    : "flvContainer",
			endPosterPath  : "/images/000000.gif",
			companionSize  : "300x60",			
			w              : "640",
			h              : "360",
			volume         : 65
		}

	};

	var _self = {	 
		
		flashPlayer: null, // will become instance of bam.FlvPlayer

		bind: function(/* eventName, dataOrFn, fnOrUndefined */){
			_$event.bind.apply(_$event, arguments);
		},
		
		one: function(/* eventName, dataOrFn, fnOrUndefined */){
			_$event.one.apply(_$event, arguments);
		},
		
		unbind: function(/* eventName, fn */){
			_$event.unbind.apply(_$event, arguments);
		},
			
			
		index: { // support for playlist index
		
			contentIds    : [], // array of playlist content_ids
//			curPage       : 1,
//			totalPages    : 1,
//			loadedPages   : [],
//			pageNavInited : false,
//			$items        : null,		
			
			/**
			 * Displays selected page by hiding all pages, then showing selected page.
			 * @param p {integer} Number of page to show
			 * @TODO Don't hide all first, instead explore using :gt():lt():
			 * @TODO Consider using $().data("thumb",value) instead of the thumb attr
			showPage: function(p){ // pagination handler
				var curIndex = null,				
						$curItem,
						perPage      = _settings.index.itemsPerPage,
						curPageStart = _self.index.currentPage*perPage,
						curPageEnd   = (_self.index.currentPage+1)*perPage,						
						pageStart,
						pageEnd;

				if (p!==_self.index.currentPage && p>=0 && p<Math.ceil(_self.index.$items.length/_settings.index.itemsPerPage)){
					pageStart = p*_settings.index.itemsPerPage,
					pageEnd   = (p+1)*_settings.index.itemsPerPage;
					
					_self.index.$items.find(":gt("+curPageStart+"):lt("+curPageEnd+")").hide(); // hide items from already displayed page
					var $curPageItems = _self.index.$items.find(":gt("+pageStart+"):lt("+pageEnd+")").show(); // show items from current page
					if (!_self.index.loadedPages[p]){
						$curPageItems.find("img").each(function(){ 
							$(this).attr({src:$(this).attr("thumb")}); // load thumbnails
						});
					}					
//					_self.index.$items.hide().each(function(i, curItem){
//						$curItem = $(curItem);
//						curIndex = parseInt($curItem.attr("index"));
//						if ( pageStart <= curIndex && curIndex < pageEnd){
//							if (!_self.index.loadedPages[p]){ // if page wasn't previously displayed, load thumbs for items
//								$curItem.find("img").attr({src:$curItem.attr("thumb")}).css({visibility:"visible"});
//							}
//							$curItem.show();
//						}
//					});
					$(_settings.index.containerId + " button").removeClass("current");
					$(_settings.index.containerId + " button:eq("+p+")").addClass("current");
					_self.index.currentPage    = p;
					_self.index.loadedPages[p] = true;
				}
			},
			 */			
			
			/**
			 * Initializes sub-page nav for video index
			 * @TODO use custom event for tracking stuff.
			initPageNav: function(){
				_self.index.$items = $(_settings.index.containerId + " li");
				try { _self.index.totalPages = Math.ceil(_self.index.$items.length/_settings.index.itemsPerPage); }
				catch (err) { _log("initPageNav: couldn't calculate total pages"); }
				_log("initPageNav: _self.index.totalPages: " + _self.index.totalPages);
				var $pageLink;
				if (_self.index.totalPages > 1){
					for (var i=0; i<_self.index.totalPages; i++){
						$pageLink = $("<button>"+(i+1)+"</button>").click(function(buttonPage){ 
							return function(){ 
								bam.tracking.track({
									async:{
										isDynamic    : false, 
										compName     : "Video Playback Playlist", 
										compActivity : "Video Playlist Page " + (buttonPage+1), 
										actionGen    : true
									}
								});
								if (_self.index.currentPage != buttonPage){
									_self.index.showPage(buttonPage);
								}
							}; 
						}(i));
						$("#vppIndexNav").append($pageLink); // @TODO add this div!
					}
				}
				_self.index.pageNavInitialized = true;
			},
			 */						
			
			/**
			 *
			goToPageWithIndex: function(index){
				if (_self.index.pageNavInitialized){
					var newPage = _self.index.currentPage || 0;
					for (var p=0; p<_self.index.totalPages; p++){
						if ( (index >= p*_settings.index.itemsPerPage) && (index < (p+1)*_settings.index.itemsPerPage) ){
							newPage = p;
							break;
						}
					}
					_log("goToPageWithIndex: index " + index + " is on page: " + newPage);
					_self.index.showPage(newPage);
				}
				else {
					_log("goToPageWithIndex: page nav has not been initialized yet. doing nothing");
				}
			},
			 */			
			
			
			/**
			 * Hits the search service with passed/default parameters and loads unique content
			 * by appending video clip to the DOM, and adding it to contentIds.
			 * @param props Object Configuration object
			 * @TODO revisit the concept of initially collapsing (hiding) not exposed items
			 */
			loadSearchResults: function(props){
				var curExistingContentId = "",
						callback = function(){ 
							if (typeof props.callback==="function"){
								props.callback();
							}
						};
				if (!_settings.searchServiceUrl){
					_log("loadSearchResults: exiting since searchServiceUrl is blank!!!");
					return;
				}
				_log("storing existing contentIds from index or request...");
				$(_settings.index.containerId + " li").each(function(i, curLi){
					curExistingContentId = $(curLi).find("a").attr("rel");
					if (curExistingContentId){
						_log("..." + curExistingContentId);
						_self.index.contentIds.push(curExistingContentId);
					}
				});
				if (_self.index.contentIds.length >= _settings.index.maxItems){
					_log(_self.index.contentIds.length + " results already displayed.  search not required.  executing callback and exiting.");
					callback();
					return;  // don't show more items than maxItems
				}
				else {
					_log("need more results from search...");
				}

				if (props.useContentKeywords){
					_settings.searchParams = "";
				}
				else {
					if (props.searchParams)                                 { _settings.searchParams += "&"+props.searchParams; }
					if (_settings.searchParams.indexOf("sort=") === -1)     { _settings.searchParams += "&sort=desc"; }
					if (_settings.searchParams.indexOf("sort_type=") === -1){ _settings.searchParams += "&sort_type=custom"; }
				}
				_settings.searchParams += "&src=vpp";
				_log("loadSearchResults: searchParams = "+ _settings.searchParams);
				// load related content (param-based search only so not passing contentId or topicId)
				_log("about to call load from vpp.searchResults");				
				bam.media.relatedContent.load({
					contentId               : props.contentId,
					useContentKeywords      : props.useContentKeywords || false,
					ignoreContentIds        : _self.index.contentIds,
					searchKeywordCategories : _settings.keywordCategories,
					searchQuery             : _settings.searchParams,
					maxItemsOverride        : _settings.index.maxItems,
					success : function(index, topicConfig){
						var html              = "",
								existingItemCount = _self.index.contentIds.length,
								isCollapsed       = false,
								collapsedClass    = "";
						$.each(index, function(i, curItem){
							if (existingItemCount>=_settings.index.itemsPerPage && !isCollapsed){
								isCollapsed    = true;
								collapsedClass = ' class="collapsed"';
							}
							html += '' + 
								'<li index="'+existingItemCount+'" '+collapsedClass+'>' + 
									'<a href="/video/play.jsp?content_id='+curItem.content_id+'" rel="'+curItem.content_id+'">' + 
										'<img src="'+( !isCollapsed ? curItem.thumb : '/images/nothing.gif' )+'"' + ( !isCollapsed ? '' : ' thumb="'+curItem.thumb+'"' ) + ' />' + 
										'<div class="playBtn"></div>' + 
										'<p>'+curItem.headline+'</p>' + 
										'<div class="dateAdded">Added: '+curItem.date_added+'</div>' + 
										'<div class="duration">Duration: '+curItem.duration+'</div>' + 
									'</a>' + 
								'</li>';
							_self.index.contentIds.push(curItem.contentId);
							existingItemCount++;
						});
						$(_settings.index.containerId).append(html);						
					}
				});
				callback();
			},
			
			
			/**
			 * Will show current item if not displayed, and if on current playlist (not browse playlist).
			 * @TODO fix this
			 */
//			revealPlaylistItem: function(index){
//				if (!!$(_settings.index.containerId+" li:eq("+(index+1)+"):visible").length){ // if current item is not visible
//					_log("revealPlaylistItem: current item is not visible");
//					// show its page
//					var p       = 0, // pseudo-page
//							itemPos = 0; // will hold top of current playlist item
//					do { p++; } while (index > p*_self.index.itemsPerpage); // get page of item
//					// render all items on that page
//					_log("revealPlaylistItem: current item is on page " + p + " ...showing all items on that page");
//					$(_settings.index.containerId+" li.collapsed:lt("+p*_self.index.itemsPerPage+")").removeClass("collapsed");
//					// scroll into view
//					itemPos = $(_settings.index.containerId+" li:eq("+index+")").position();
//					_log("revealPlaylistItem: scrolling item into view, to pos: " + itemPos.top);
//					$(_settings.index.containerId).parent().animate({scrollTop:itemPos.top}, "normal");
//				}
//			},
			
			
			/**
			 * Scrolls to the currently playing video (which may not be visible)
			 */			
			scrollIntoView: function(index){
				// @TODO implement this
			}
			
		},
		
		
		/**
		 * Initializes the page by initializing the playlist, creating flash player 
		 * and starting playback.
		 */
		initialize: function(){
			_self.initPlaylist();
			_self.createFlvPlayer();
			// for iPad, force DC ad
			if (_isIPad){
				$.ajax({
				  url      : "/shared/scripts/bam/widget/dcRefreshableAd/dcRefreshableAd-1.0.0.min.js",
				  dataType : "script",
					async    : false
				});
				$("#videoAreaSecondaryAd").empty().dcRefreshableAd({width:300, height:250});
				_isIPad.adCount = 0;
			}
			// reveal hidden thumbs after a delay
			setTimeout(function(){
				$(_settings.index.containerId).find("li.collapsed").each(function(){
					var $curImg = $(this).removeClass("collapsed").find("img");
					$curImg.attr("src", $curImg.attr("thumb"));
				});
			}, 4000);
//			_self.index.initPageNav();
//			_self.index.showPage(0);
		},
		
		
		/**
		 * Creates an instance of flash player and assigns handlers for flash events.
		 * The first triggered event will be onFlvPlayerLoaded which is configured to
		 * start video playback.
		 */
		createFlvPlayer: function(){
			_log("createFlvPlayer");
			_self.flashPlayer = new bam.FlvPlayer({
				hideControls       : false,
				autoHideSkin       : _settings.playerCfg.autoHideSkin,
				skin               : _settings.playerCfg.playerSkin, // "/flash/video/y2009/skins/mlb_media_landing_full.swf",
				endPosterPath      : _settings.playerCfg.endPosterPath,
				self               : _settings.playerCfg.self,
				width              : _settings.playerCfg.w,
				height             : _settings.playerCfg.h,
				elemId             : _settings.playerCfg.elemId,
				containerId        : _settings.playerCfg.containerId,
				volume             : _settings.playerCfg.volume,
				videoScaleMode     : _settings.playerCfg.videoScaleMode,
				videoAlign         : _settings.playerCfg.videoAlign,
				debugMode          : _settings.debugMode,
				onPlayerLoaded     : _self.onFlvPlayerLoaded,
				contentStarted     : _self.onContentStarted,
				onPlaylistComplete : _self.handlePlayComplete,
				companionSize      : _settings.playerCfg.companionSize
			});
		},


		/**
		 * Handler for "onFlvPlayerLoaded" event.  Starts video playback.
		 */
		onFlvPlayerLoaded: function(){
			_log("onFlvPlayerLoaded: autostart is: " + _settings.autostart);		
			if (_settings.autostart){
				$("#flvContainer, #clipData").removeClass("collapsed");
				_self.playCurrentItem();
			}
			else {
				_settings.autostartTimeoutId = setTimeout(function(){
						_log("onFlvPlayerLoaded: Delayed start");
						$("#buttonWatch").hide();					
						$("#flvContainer, #clipData").removeClass("collapsed");	
						_self.playCurrentItem();
						_self.clearAutostartTimeoutId();
					}, 
					_settings.autostartDelay);
			}
		},
		
		
		
		/**
		 * Handler for event broadcasted by flash player when 
		 * actual content (not preroll) starts playing
		 */
		onContentStarted: function(){
			_log("onContentStarted: tracking...");
			bam.tracking.track({
				async_media:{
					mediaID        : _settings.curClipData.content_id+"|"+_settings.curClipData.playbackScenario,
					playerType     : "Flash",
					playerContext  : "Video Playback Page",
					playerFlavor   : _settings.topicName,
					contextVersion : "3.0",
					streamType     : "Progressive Download",
					bitRate        : _settings.curClipData.bitRate,
					actionGen      : _userInitiatedPlay
				}
			});
		},
		
		
		/**
		 * Clears delayed start timeout. 
		 */
		clearAutostartTimeoutId: function(){
			_log("clearAutostartTimeoutId()");
			clearTimeout(_settings.autostartTimeoutId); // clear id for delayed start
		},
		

		/**
		 * Resets highlighted item, then adds class "current" to DOM elemenent representing
		 * video that's currently playing.
		 */
		highlightCurrentItem: function(){
			_self.resetHighlight();
			$(_settings.index.containerId+" li:eq("+_settings.currentIndex+")").addClass("current");
			// _self.index.revealPlaylistItem(_settings.currentIndex);
		},
	

		/**
		 * Resets highlighted video item by removing class "current" from its DOM element.
		 */
		resetHighlight: function(){
			$(_settings.index.containerId + " li.current").removeClass("current");
		},
	
	
		/**
		 * Returns next (now current) playlist item. If at end of playlist, calls endPlaylist()
		 */
		getCurrentPlaylistItem: function(){
			if (_settings.currentIndex < _settings.playlist.length){
				return _settings.playlist[_settings.currentIndex];
			} 
			else {
				_self.endPlaylist();
				return false;
			}
		},
		
		
		/**
		 * Sets isPlaylistComplete to true.
		 */
		endPlaylist: function(){
			_self.isPlaylistComplete = true;
		},
	
		
		/**
		 * Clears playlist and resets currentIndex
		 */
		clearPlaylist: function(){
			_log("clearPlaylist: Setting currentIndex to 0");
			_settings.playlist = [];
			_settings.currentIndex = 0;
		},
		
		
		/**
		 * Highlights current (next, since it will have been incremented) video item in playlist,
		 * loads clip data and starts playing it or its preroll (if preroll platform is DC).
		 */
		playCurrentItem: function(){
			if (_settings.commentsAreDisplayed){
				return;
			}
			_log("playCurrentItem: curIndex=" + _settings.currentIndex);
			_self.highlightCurrentItem();
			var dataLoaded     = true,
					dataIsGood     = true,
					curItemMediaId = _self.getCurrentPlaylistItem();
			_$event.trigger("playCurrentItem", [curItemMediaId]);
			if (curItemMediaId !== _settings.curClipData.content_id){
				_log("playCurrentItem: loading data for item " + curItemMediaId);
				dataLoaded = _self.loadClipData(curItemMediaId);
				dataIsGood = _self.displayClipData();
			}
			else {
				_log("playCurrentItem: curClipData is already loaded");
			}
			if (!(dataLoaded && dataIsGood)){
				_log("playCurrentItem: data is bad. exiting");
				return; // data is bad, so exit
			}
			if (curItemMediaId){ // if there are more items in playlist
				_log("playCurrentItem: showing clip...");
				_self.playClip(curItemMediaId);
				_log("playCurrentItem: clearing curClipData");
//				_settings.curClipData = {}; // clear this.
			}
			else {
				_log("playCurrentItem: End of Playlist.");
			}
		},
		
		
		/**
		 * Handler for "playlistComplete" event from the swf.
		 * Increments playlist pointer and either calls playCurrentItem()
		 * or endPlaylist()
		 */
		handlePlayComplete: function(){
			_log("handlePlayComplete: tracking and incrementing currentIndex");
			bam.tracking.track({videoComplete:{playerContext:"Video Playback Page"}});
			_settings.currentIndex++;
			if (_settings.autoplaylist){
				_log("handlePlayComplete: calling playCurrentItem()");
				_self.playCurrentItem();
			}
			else {
				_log("handlePlayComplete: stoping playback.");
				_self.flashPlayer.execute("exitFullScreen");				
				_self.endPlaylist();
			}
		},


		/**
		 * Initializes (resets) the playlist.
		 */
		initPlaylist: function(){
			_log("initPlaylist()");
			_self.clearPlaylist();
			_self.resetHighlight();
			_settings.playlist = []; // reset playlist since we'll repopulate it fully below
			$(_settings.index.containerId + " li a").each(function(i, curItem){
				_settings.playlist.push($(curItem).attr("rel"));
			});
				// _log("assigning handler to playlist item: " + $(curItem).attr("rel") + " at index: " + i);
//				$(curItem).unbind("click").click(function(evt){
//					evt.preventDefault();
//					_log("Playlist item " + $(this).attr("rel") + " selected.  Setting index to " + i);
//					_settings.currentIndex = i; // _settings.prerollIndex = i;
//					_self.playCurrentItem();
//					_userInitiatedPlay = true; // user initiated play - set flag
//					bam.tracking.track({
//						async:{
//							isDynamic    : false, 
//							compName     : "Video Playback Playlist", 
//							compActivity : "Video Playback Thumbnail " + (_settings.currentIndex+1), 
//							actionGen    : true
//						}
//					});					
//					return false;
//				});
//			});	
		},

		
		/**
		 * Plays clip (not preroll) by callling startPlaylist on the flvPlayer.
		 * @param content_id String
		 * @TODO Use flv trigger "contentStarted" to track the click
		 */
		playClip: function(content_id){
			if (typeof _settings.curClipData.curVideoFlashUrl==="undefined"){
				_log("playClip("+content_id+") url is undefined. NO PLAYBACK!");
				return;
			}
			_log("playClip("+content_id+") startingPlaylist: " + _settings.curClipData.curVideoFlashUrl);
			_self.flashPlayer.startPlaylist([{
				type       : 'video',
				path       : _settings.curClipData.curVideoFlashUrl,
				content_id : content_id,
				duration   : bam.media.getDurationInSeconds(_settings.curClipData.duration)
			}]);
			// for iPad, refresh DC ad
			if (_isIPad){
				if (_isIPad.adCount!==0){ // prevents double loading initially
					$("#videoAreaSecondaryAd").dcRefreshableAd("refresh");
				}
				else {
					_isIPad.adCount++;
				}
			}			
			_userInitiatedPlay = false; // reset switch
		},

		
		/**
		 * Displays information for the current clip and assigns handlers
		 * for social links (comments, share utils)
		 * @TODO Assign share/social handlers using live?
		 */
		displayClipData: function(){
			_log("displayClipData()");
			if ( 
					(typeof _settings.curClipData.content_id==="undefined") || 
					(typeof _settings.curClipData.curVideoFlashUrl==="undefined") 
					){ // bad data. handle display and exit
				_log("bad data. (content_id or URL undefined)");
				$("#clipTitle").text("This video is temporarily unavailable");
				$("#clipBlurb").text("Please make another selection.");
				$("#clipDate span,#clipDuration").text("n/a");
				$("#clipTags div").html("");

				$("#clipShare a").unbind("click");
				return false;
			}
			// data is ok
			$("#clipTitle").text(_settings.curClipData.headline);
			$("#clipDate span").text(_self.formatDate(_settings.curClipData.date));
			$("#clipDuration").text(_self.formatDuration(_settings.curClipData.duration));
			$("#clipThumb").html("<img src='"+_settings.curClipData.thumb+"'/>");
			$("#clipBlurb").text(_settings.curClipData.bigBlurb);
			_self.loadKeywords(_settings.curClipData);
			/**
			 * Private function for tracking distribution clicks
			 */
			function trackDistribution(type){
				bam.tracking.track({
					async:{
						isDynamic    : false, 
						compName     : "Video Playback Distribution/Comments", 
						compActivity : "Video Playback " + type + " Link Click", 
						actionGen    : true
					}
				});
			}
			// set share link
			$("#share").unbind().bind("click", function(clipData){
				return function(){
					trackDistribution("Email Video");
					_self.shareVideo(clipData.content_id, clipData.content_id, clipData.headline);
					return false;
				};
			}(_settings.curClipData));
			
								
			// set facebook link
			$("#facebook").unbind().bind("click", function(content_id){ 
				return function(){
					trackDistribution("Facebook");
					window.open("http://www.facebook.com/sharer.php?u="+encodeURIComponent(_settings.prodPageUrl+"?affiliateId=CommentWidget&affiliateId=facebook_share&content_id="+content_id)+"&t=","vppsharefb","width=626,height=436");
					return false;
				};
			}(_settings.curClipData.content_id));
			
			// set twitter link
			$("#twitter").unbind().bind("click", function(content_id,title){ 
				return function(){
					trackDistribution("Twitter");
					window.open("http://twitter.com/home?status="+title+" "+encodeURIComponent(_settings.prodPageUrl+"?content_id="+content_id)); 
					return false;
				};
			}(_settings.curClipData.content_id, _settings.curClipData.headline));			
			
			// set digg link
			$("#digg").unbind().bind("click", function(content_id, title, bodytext){
				return function(){
					trackDistribution("Digg");
					var diggUrl = "http://digg.com/submit?url="+encodeURIComponent(_settings.prodPageUrl +"?affiliateId=digg_share&content_id="+content_id)+"&title="+encodeURIComponent(title)+"&bodytext="+encodeURIComponent(bodytext);
					window.open(diggUrl,"vppsharedigg");
					return false;
				};
			}(_settings.curClipData.content_id, _settings.curClipData.headline, _settings.curClipData.bigBlurb));
			
			
			if (!_settings.disableComments){
				// enable/disable comments based on data
				$("#comment").find("a").html( (_settings.curClipData.commentsAreClosed ? "<span class='closed'>Comments closed</span>" : "Comment") );
				// _self.showLastComment();
			}
			
			return true;
		},
		
		
		/**
		 * Displays most recent comment in clip info area. Handles no comment 
		 * and closed comments scenarios. Called by displayClipData()
		 * (No memoization since we want fresh stuff)
		 */
		showLastComment: function(){
			_log("showLastComment()");
			var cBy    = "",
					cById  = "",
					cBody  = "",
					cDate  = "",
					cCount = 0,
					$commentCnt  = $("#clipTotalComments"),
					$commentBy   = $("#clipCommentBy"),
					$commentBody = $("#clipCommentBody"),
					$commentDate = $("#clipCommentDate"),
					$comment     = $("#clipComment"),
					_cId         = _settings.curClipData.content_id;
			$commentBy.addClass("disabled");					
			$commentBody.text("").addClass("disabled").addClass("msg");
			$commentDate.text("");
			$commentCnt.text("").hide();			
			/**
			 * Private utility function for rendering last comment
			 */
			function renderLastComment(comments, rawData){
				if (comments && comments[0]){
					cBy    = bam.object.getDeepValue(comments[0], "Author.DisplayName") || "";
					cById  = bam.object.getDeepValue(comments[0], "Author.UserKey.Key") || "";
					cBody  = comments[0].CommentBody  || "";
					cDate  = comments[0].PostedAtTime || "";
					cCount = bam.object.getDeepValue(rawData, "Responses.0.CommentPage.NumberOfComments") || "";	
					$commentBy.removeClass("disabled").find("a").text(cBy).attr("href",_settings.commenterProfileUrl+cById);
					$commentBody.removeClass("disabled msg").text(cBody);
					$commentDate.text(cDate);
					$commentCnt.text(cCount + " Comment" + ((parseInt(cCount,10)===1)?"":"s")).show();
				}
				else {
					$commentBody.text("Be the first to comment.");
					$commentCnt.text("0 Comments");
				}
			}
			$commentCnt.hide();
			if (!_settings.curClipData.commentsAreClosed){
				if (!bam.comments_sub){ // initialize
					bam.loadSync("/shared/scripts/bam/bam.comments_sub.js");
					bam.comments_sub.init({
						beta                : !document.location.host.match(/^(?:www|web|mlb)/),
						cache               : true,
						prefix              : "v_",
						maxCommentPageLinks : 1
					});
				}
				bam.comments_sub.fetch(_cId, 0, bam.comments_sub.cfg().cache, function(){
					renderLastComment(arguments[0], arguments[1]);
				});
			}
			else {
				$commentBody.text("Comments are closed for this video.");
				$commentCnt.text("").hide();
			}
			// handle no response from comment api
			setTimeout(function(){
				if ($commentBody.text()===""){
					$commentBody.text("Unable to load most recent comment at the moment.");
				}
			}, 2000);
		},


		/**
		 * Calls bam.media.getMetaData() to load data for passed content_id, 
		 * then finds playback scenario that's the best match and stores its data.
		 * @param {string} content_id 
		 * @return boolean True on success, false if there was a problem with data
		 */
		loadClipData: function(content_id){
			_settings.curClipData = bam.media.getMetaData(content_id);
			if (_settings.curClipData && typeof _settings.curClipData.urls!=="undefined"){
				_log("loadClipData: Populated curClipData. Going through URLs");
				var urlNode = {},
						psMap   = {};
				$.each(_settings.curClipData.urls, function(i, url){ // load available playback scenarios into a hash for easy reference
					if (url.getAttribute("playback_scenario")){
						psMap[url.getAttribute("playback_scenario")] = url;
					}
					else if (url.getAttribute("speed")=="800" && url.getAttribute("type")=="flash-video"){
						psMap.v2_url = url;
					}
				});
				$.each(_settings.psKeys, function(i, psKey){
					if (psMap[psKey]){
						urlNode = psMap[psKey]; // get the first match and exit
						return false;
					}
				});
				if (urlNode && urlNode.firstChild && urlNode.firstChild.nodeValue){
					_settings.curClipData.curVideoFlashUrl = urlNode.firstChild.nodeValue;
					_settings.curClipData.curVideoFlashId  = urlNode.getAttribute("id");
					_settings.curClipData.playbackScenario = urlNode.getAttribute("playback_scenario");
					var bitRateArr = _settings.curClipData.playbackScenario.match(/(\d+K)/);
					_settings.curClipData.bitRate = (bitRateArr) ? bitRateArr[1] : "";
					_log("loadClipData: Found URL [" + _settings.curClipData.playbackScenario + "] URL: " + _settings.curClipData.curVideoFlashUrl);
				}
				else {
					_log("loadClipData: PlaybackScenario (or v2 data) is missing.");
					return false;
				}
			}
			else {
				_log("loadClipData: Meta data is not available or bad. Returning false.");
				return false;
			}
			return true;
		},
		

		/**
		 * Generates keyword list and appends it to DOM. Each link is assigned
		 * click tracking.
		 * @param {object} videoData
		 */
		loadKeywords: function(videoData){
			_settings.curClipData.commentsAreClosed = false; // custom keyword for disabling commenting
			if ($("#clipTags").length){ // if elem #clipTags exists
				var type         = "",
						display      = "", 
						value        = "", 
						param        = "", 
						keywordsHtml = "";
				$("#clipTags div").empty();
				$.each(videoData.keywords, function(i, keyword){
					type = keyword.getAttribute("type");
					if (_settings.keywordTaxTypes[type]){
						param = _settings.keywordTaxTypes[type];						
						type  = "tax";
					}
					switch (type){
						case "tax" : 
							display = keyword.getAttribute("displayName").replace(/\"/g,"");
							if ( !(_IGNORE_KEYWORD_REGEX.test(display)) ){
								value = keyword.getAttribute("value");
							}
							break;
						case "team_id"       :
						case "mlb_team_id"   :
						case "player_id"     :
						case "mlb_player_id" :
							param   = type;
							value   = keyword.getAttribute("value");
							display = keyword.getAttribute("displayName");
							break;
						case "game_pk" :
							param   = "game_pk";
							value   = keyword.getAttribute("value");
							display = "More From This Game";
							break;
						/**
						* Hack for hiding the comments when video doesn't support them.
						* Its here, rather than in displayClipData() since we don't want
						* to iterate keywords twice.
						*/
						case "mmtax" :
							if (keyword.getAttribute("value")==="comments_closed"){
								_settings.curClipData.commentsAreClosed = true;
							}
							break;
					}
					if (param!=="" && value!=="" && display!==""){
						keywordsHtml += "<a href='"+_settings.searchPageUrl+"?"+param+"="+escape(value)+"'>"+ display +"</a>, ";
					}
					param = ""; // reset parameter
				});
				if (keywordsHtml.length > 0){
					$("#clipTags div").html(keywordsHtml.substring(0, keywordsHtml.length-2)); // remove trailing comma and space
				}
			}
		},
		
		
		/**
		 * Returns clip duration in desired format.
		 * @param {string} duration Expected format: HH:MM:SS
		 * @return {string} Format: MM:SS
		 */
		formatDuration: function(duration){
			var minSec = "";
			if (typeof(duration)==="string" && duration.length > 7){
				minSec = duration.substring(3,8);
			}
			return minSec;
		},
	
		
		/**
		 * Converts YYYY-MM-DDTHH:MM:SS-???? to MM/DD/YY
		 * @param {string} dateLong Date string in format: YYYY-MM-DDTHH:MM:SS-...
		 * @returns {string} Format: MM/DD/YY
		 */
		formatDate: function(dateLong){
			var displayDate = "";
			if (typeof(dateLong)==="string" && dateLong.length > 8){
				displayDate = dateLong.substring(5,7) + "/" + dateLong.substring(8,10) + "/" + dateLong.substring(2,4);
			}
			return displayDate;
		},


		/**
		 * Displays the comments module amd hides the video index.
		 * @TODO Add logic for blocking this during video ads (based on custom events)
		 */
		showComments: function(){
			if (_settings.isPrerollPlaying){
				return;
			}
			var curId = $(_settings.index.containerId + " li.current a").attr("rel");
			bam.comments_init();
			bam.comments.overrideLoginLinkParams({content_id:curId, topic_id:_settings.topicId});
			bam.comments.getComments(curId);
			$("#videoBrowseWrap").hide();
			$("#comments").show();
			_settings.commentsAreDisplayed = true;
		},
		
		
		/**
		 * Public setter for commentsAreDisplayed flag used when overriding showComments method.
		 */
		setCommentsAreDisplayed: function(boolValue){
			_settings.commentsAreDisplayed = boolValue;
		},
		
		
		/**
		 * Collapses the comments module and reveals the video index.
		 */
		closeComments: function(){
			$("#comments").hide();
			$("#videoBrowseWrap").show();
			_settings.commentsAreDisplayed = false;
		},


		/**
		 * Launches the Email Link popup.
		 */
		shareVideo: function(content_id, metaId, title){
			var url    = _settings.shareVideoUrl + "?content_id=" + content_id + "&metaId=" + metaId + "&title=" + escape(title);
			var width  = 500;
			var height = 470;
			if (width > screen.availWidth-12){ width = screen.availWidth-12; }
			if (height > screen.availHeight-48){ height = screen.availHeight-48; }
			var left = (screen.availWidth - width - 12) / 2;
			var top  = (screen.availHeight - height - 48) / 2;
			window.open(url, name, "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
		},
		
		
		/**
		 * Expose private settings, strictly for debugging
		 * @TODO Consider removing this.
		 */
		getSettings: function(prop){
			if (prop && _settings.prop){
				return _settings.prop;
			}
			else {
				return _settings;
			}
		},

		
		/**
		 * Configures VPP.
		 * @param {object} props New configuration object
		 */
		configure: function(props){
			$.extend(true, _settings, props);
		}
		
	};

	/////////////////////////////////////////////////////////////////////
	/////////////////////// Constructor stuff ///////////////////////////
	/////////////////////////////////////////////////////////////////////

	/**
	 * Assign handler for playlist items
	 * @TODO potential problem here is that _settings.index.containerId might be changed via configure()
	 */
	$(_settings.index.containerId + " li a").live("click", function(evt){
		if (evt.button !== 0){
			return true; // ignore right-click (firefox bug)
		}
		evt.preventDefault();
		var $this = $(this),
				i     = $this.parent("li").attr("index");
		_log("Playlist item " + $this.attr("rel") + " selected. Setting index to " + i);
		_settings.currentIndex = i;
		_self.playCurrentItem();
		_userInitiatedPlay = true; // user initiated play - set flag
		bam.tracking.track({
			async:{
				isDynamic    : false, 
				compName     : "Video Playback Playlist", 
				compActivity : "Video Playback Thumbnail " + (_settings.currentIndex+1),
				actionGen    : true
			}
		});					
	});


	// track tag clicks
	$("#clipTags div a").live("click", function(){
		bam.tracking.track({
			async:{
				isDynamic    : false, 
				compName     : "Video Search Option", 
				compActivity : "Video Playback Search Tag Click", 
				actionGen    : true
			}
		});
	});


	// handle/track comments click
	$("#comment").live("click", function(evt){
		evt.preventDefault();
		if (!_settings.curClipData.commentsAreClosed){
			_self.showComments();
		}
		bam.tracking.track({
			async:{
				isDynamic    : false, 
				compName     : "Video Playback Distribution/Comments", 
				compActivity : "Video Playback Comment Link Click", 
				actionGen    : true
			}
		});
	});

	
	return _self;
	
})();

bam.vpp = bam.media.vpp; // for backwards support


