var DrPepper = DrPepper || {};
DrPepper.About = DrPepper.About || {};

DrPepper.About.Timeline = DrPepper.About.Timeline || {};


DrPepper.About.Timeline.Grid = function(p_sImageURL)
{
	if (DrPepper.About.Timeline.Grid.instance) {
		throw("DrPepper.About.Timeline.Grid objects are singletons");
	}

	DrPepper.About.Timeline.Grid.instance = this;
	
	if (p_sImageURL) {
		DrPepper.About.Timeline.Grid.URL_IMAGES = p_sImageURL;		
	}
	this.init();
}

/**
 *	Back-end URLs for AJAX calls
 **/
DrPepper.About.Timeline.Grid.URL_TIMELINE_MANAGER = "/about/timeline/manager/";
DrPepper.About.Timeline.Grid.URL_IMAGES = "/_fileuploads/timeline/image/";


/**
 *	These parameters can be tweaked based on preference
 **/
DrPepper.About.Timeline.Grid.SPLASH_ITEM_PAGING_COUNT	= 5;	// number of items to fetch for each "splash" request
DrPepper.About.Timeline.Grid.MORE_ITEM_PAGING_COUNT		= 5;	// number of items to fetch for each "more" request
DrPepper.About.Timeline.Grid.DECADE_PAGING_COUNT		= 4;	// number of decades to fetch for scroll updates


/**
 *	Types for grid items
 **/
DrPepper.About.Timeline.Grid.ITEMTYPE_TERMINATOR		= -4;
DrPepper.About.Timeline.Grid.ITEMTYPE_MORE				= -3;
DrPepper.About.Timeline.Grid.ITEMTYPE_HEADER			= -2;
DrPepper.About.Timeline.Grid.ITEMTYPE_ITEM				= -1;
DrPepper.About.Timeline.Grid.ITEMTYPE_IMAGE				= 0;
DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_YOUTUBE		= 1;
DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_MYSPACE		= 2;
DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_VIMEO		= 3;

/**	Time [ms] it takes to fade in an element image
 */
DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY			= 250;

DrPepper.About.Timeline.Grid.SWF_CONTAINER_ID			= "timeline-swf-container";

DrPepper.About.Timeline.Grid.DETAIL_SWF_ID				= "timeline-detail-swf";
DrPepper.About.Timeline.Grid.DETAIL_SWF_WRAPPER_ID		= DrPepper.About.Timeline.Grid.DETAIL_SWF_ID + "-wrapper";
DrPepper.About.Timeline.Grid.DETAIL_SWF_URL				= "/swf/com/drpepper/about/timeline/TimelineDetailModule.swf";
DrPepper.About.Timeline.Grid.DETAIL_SWF_WIDTH			= 756;
DrPepper.About.Timeline.Grid.DETAIL_SWF_HEIGHT			= 633;

DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID			= "timeline-contribute-swf";
DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WRAPPER_ID	= DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID + "-wrapper";
DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_URL			= "/swf/com/drpepper/about/timeline/TimelineAddYourOwn.swf";
DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WIDTH		= 864;
DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_HEIGHT		= 633;

/**	Begin updating content if user scrolls past 50% of the document height
 */
DrPepper.About.Timeline.Grid.CONTENT_LOADING_THRESHOLD = .5;

/** Replacement patterns for thumbnails and images
 */
DrPepper.About.Timeline.Grid.FILENAME_PATTERN_THUMBNAIL = "t___HASH__.jpg";
DrPepper.About.Timeline.Grid.FILENAME_PATTERN_IMAGE = "f___HASH__.jpg";


/**
 *	Open the blocking overlay to surface either the detail or the contribute box.
 **/
DrPepper.About.Timeline.Grid.openOverlay = function()
{
	// If the overlay is open, the grid will not respond to scroll events
	DrPepper.About.Timeline.Grid._bOverlayOpen = true;

	// Open blocking overlay
	$.blockUI({ message: $("#" + DrPepper.About.Timeline.Grid.SWF_CONTAINER_ID) });
}


/**
 *	Close blocking overlay. Called from within either the detail or the contribute SWF.
 * 	onUnblock handlers will have been set up beforehand to handle any cleanup.
 *
 *	@param	p_nScrollToDecade	Decade to scroll to after overlay has closed
 **/
DrPepper.About.Timeline.Grid.closeOverlay = function(p_nScrollToDecade)
{
    // Once the blocking overlay is closed, hide the overlay SWF
    $.blockUI.defaults.onUnblock = function() {
    	$("#" + DrPepper.About.Timeline.Grid.DETAIL_SWF_WRAPPER_ID).css("display", "none");
    	$("#" + DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WRAPPER_ID).css("display", "none");
		
		swfobject.removeSWF("#" + DrPepper.About.Timeline.Grid.DETAIL_SWF_ID);
		swfobject.removeSWF("#" + DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID);

		DrPepper.About.Timeline.Grid._bOverlayOpen = false;
		
		if (p_nScrollToDecade) {
//			DrPepper.About.Timeline.Grid.instance.scrollToDecade(p_nScrollToDecade);
		}
    };

	$.unblockUI({ fadeOut: 200 }); 
}


DrPepper.About.Timeline.Grid.setSignedIn = function(p_bIsSignedIn)
{
	$("#main-menu-item-my-account").css("display", p_bIsSignedIn ? "block" : "none");
	$("#signin-link").css("display", p_bIsSignedIn ? "none" : "block");
	$("#signout-link").css("display", p_bIsSignedIn ? "block" : "none");
}


DrPepper.About.Timeline.Grid.prototype =
{
	init : function()
	{
		var l_xThis = this;

		this._xDomElement = $("#timeline-grid");
		this._xItemsByID = {};
		
		this._xDetailDisplay = new DrPepper.About.Timeline.DetailDisplay();
		
		// Safari/Mac does not continue tracking the *window* scroll bar once we reach the bottom of the document.
		// However, it will do so for the scroll bar of any HTML element inside the document.
		// For this reason, the entire content of the <body> element must be copied into a wrapper element, which
		// is then placed inside the <body> element in lieu of the original content.
		//
		if ($.browser.safari) {
			this._xScrollContainer = $("<div />").attr("id", "body-wrapper");
			$("body").addClass("safari").append(this._xScrollContainer.append($("body").children()));
			$("html").css("overflow", "hidden");
			
			this._xScrollContainer.append($("<div />").attr("id", "gradient-backdrop"));
		}
		else {
			//
			// All other browsers play nicely and may use the window scroll bar.
			//
			this._xScrollContainer = $(window);		
			$("body").append($("<div />").attr("id", "gradient-backdrop"));
		}
		
		$("#backdrop-corner-left, #backdrop-corner-right").insertBefore("#main-menu");
		$("#body-content").css("background", "url(/image/about/timeline/page-title.jpg) 50% 0 no-repeat");


		// IE6 "position: fixed" replacement
		//
		if ($.browser.msie && $.browser.version < 7) {
			var l_nMenuTop = parseInt($("#timeline-menu").offset().top);
			var l_bIsScrolling = false;
			$(window).scroll(function () {
				l_bIsScrolling = true;
			});
			setInterval(function() {
				l_bIsScrolling = false;
				setTimeout(function() {
					if (!l_bIsScrolling) {
						$("#timeline-menu").stop().animate({ top: l_nMenuTop + $(document).scrollTop() }, 500);
					}
				}, 100);
			}, 800);
		}
		
		// Set up quick references and page parameters
		//
		this._xTerminatorItem = this.createItem(DrPepper.About.Timeline.Grid.ITEMTYPE_TERMINATOR).appendTo(this._xDomElement);

		this._xHighlightDomElement			= $("<div />").attr("id", "timeline-grid-highlight");
		this._xHighlightTimeDomElement		= $("<div />").addClass("time");
		this._xHighlightTitleDomElement		= $("<div />").addClass("title");
		this._xContributeButtonDomElement	= $("#timeline-menu-contribute").get(0);
		
		this._xHighlightDomElement
			.append(this._xHighlightTimeDomElement)
			.append(this._xHighlightTitleDomElement)
			.appendTo(this._xDomElement);

		this.ITEM_WIDTH = this._xTerminatorItem.width();
		this._nVisibleItemCount	= 0;
		
		
		// Handle slideout of JUMP TO DECADE menu, including z-index fix for IE6
		//
		var l_xJumpMenu = $("#timeline-menu-jump");
		var l_nJumpMenuLeft = $(l_xJumpMenu).css("left");
		$(l_xJumpMenu).hover(
			function() {
				$(this).stop().css("zIndex", 20).animate({ left: -1 }, 100);
			},
			function() {
				$(this).stop().animate({ left: l_nJumpMenuLeft }, 100, "swing", function() { l_xJumpMenu.css("zIndex", -10); });
			}
		);
		
		// Menu click handlers
		$("#timeline-menu").click(function(p_xEvent) { l_xThis.menuClickHandler(p_xEvent); });


		// Initialize overlay
		//
	    $.blockUI.defaults.css.border = "none";
    	$.blockUI.defaults.css.backgroundColor = "transparent";
		$.blockUI.defaults.overlayCSS.background = (!$.browser.msie || !$.browser.version > 6) ? "url(/image/about/timeline/backdrop_overlay.png) no-repeat 50% 50% #000" : "#000";
		$.blockUI.defaults.overlayCSS.cursor = "normal";
	    $.blockUI.defaults.css.cursor = "normal"; 
	    $.blockUI.defaults.css.top = "50%";
	    $.blockUI.defaults.css.left = "50%";
	    $.blockUI.defaults.css.marginLeft = "-432px";
	    $.blockUI.defaults.css.marginTop = "-335px";
	    $.blockUI.defaults.css.width = "864px";
	    $.blockUI.defaults.css.height = "670px";
		$.blockUI.defaults.css.cursor = "normal";

		$.blockUI.defaults.fadeIn = 0;
	
	
		DrPepper.Util.disableSelection($(this._xDomElement).attr("id"));


		// Preloading of highlight frame and time sprite
		//
		this._aImagePreloaders = [];

		var l_xImagePreloader1 = new Image();
		l_xImagePreloader1.src = "/image/about/timeline/sprite_item_highlight_time.png";
		this._aImagePreloaders.push(l_xImagePreloader1);

		var l_xImagePreloader2 = new Image();
		l_xImagePreloader2.src = "/image/about/timeline/sprite_item_highlight_frame" + ($.browser.msie && $.browser.version < 7 ? "_ie6" : "") + ".png";
		this._aImagePreloaders.push(l_xImagePreloader2);



		// Hijack sign-in main menu item
		//
		$("#signin-link").attr("href", "javascript:void(0);");
		$("#signin-link").click(function() { l_xThis.showContributeOverlay(); });


		// Delayed loading of first set of items (wait until rest of page has rendered to avoid flicker as much as possible)
		//
 		$.ajax({
 			type:		"GET",
 			cache:		false,
 			dataType:	"json",
 			url:		DrPepper.About.Timeline.Grid.URL_TIMELINE_MANAGER,
 			data:		"action=getitemcountbydecade",
 			success:	loadSuccessHandler,
 			error:		loadErrorHandler	
 		});
 		function loadSuccessHandler(p_xResult) {
 			
 			p_xItemCountByDecade = p_xResult;
 			
			l_xThis._xDecades = {};
			l_xThis._nLatestDecade = null;
			l_xThis._nEarliestDecade = null;
			for (var l_sDecade in p_xItemCountByDecade) {

				// Find latest and earliest decade
				// (also note that we cannot count on the order in which the JS engine will traverse the associative decades array)	
				if (!l_xThis._nLatestDecade || l_xThis._nLatestDecade < l_sDecade) {
					l_xThis._nLatestDecade = parseInt(l_sDecade);
				}
				if (!l_xThis._nEarliestDecade || l_xThis._nEarliestDecade > l_sDecade) {
					l_xThis._nEarliestDecade = parseInt(l_sDecade);
				}
	
				l_xThis._xDecades[l_sDecade] = {
					totalItemCount:		p_xItemCountByDecade[l_sDecade],
					currentItemCount:	0,
					referenceHash:		null
				}
			}
			if (l_xThis._nLatestDecade) {
				l_xThis._nCurrentMinDecade = null;
	
				$(l_xThis._xDomElement).css("visibility", "visible");
				l_xThis.loadSplashItems();
				
				// Deep link to a detail view?
				//
				var l_sHash = location.hash;
				if (l_sHash && l_sHash != "" && l_sHash != "#") {
					
					if (l_sHash != "#home" && l_sHash != "home") {
						if (l_sHash.substr(0, 1) == "#") {
							l_sHash = l_sHash.substr(1);
						}
						l_xThis.showDetailOverlay(null, l_sHash);
					}
				}

				// General scroll and resize handlers, will load new elements as needed, and adjust item terminator
				l_xThis._xScrollContainer.scroll(function(p_xEvent) { if (!DrPepper.About.Timeline.Grid._bOverlayOpen) { l_xThis.scrollContainerUpdateHandler(p_xEvent); } });
				$(window).resize(function(p_xEvent) { l_xThis.scrollContainerUpdateHandler(p_xEvent); });
		
				// Hover handler for highlight box
				$(l_xThis._xDomElement).bind("mousemove", function(p_xEvent) { l_xThis.mouseMoveHandler(p_xEvent); });
				$(l_xThis._xDomElement).hover(
					function() { },
					function() { l_xThis.highlightItem(null); }
				);
				
				// Click handler for all items
				$(l_xThis._xDomElement).click(function(p_xEvent) { l_xThis.mouseClickHandler(p_xEvent); });
			}
			else {
				// No data returned: do nothing else
	 			l_xThis._nEarliestDecade = l_xThis._nCurrentMinDecade = l_xThis._nLatestDecade = 0;
			}
 		}
 		function loadErrorHandler() {
			// No data returned: do nothing else
 			l_xThis._nEarliestDecade = l_xThis._nCurrentMinDecade = l_xThis._nLatestDecade = 0;
 		}
	},


	/**
	 *	Handle clicks on the Timeline menu (jump to decade / add your own).
	 *
	 *	@param	p_xEvent	Click event
	 **/
	menuClickHandler : function(p_xEvent)
	{
		var l_xTarget = p_xEvent.target;

		// Respond to a click only if mouse pointer is over an item
		//
		if (l_xTarget == this._xContributeButtonDomElement) {
			this.showContributeOverlay();
		}
		else if ($(l_xTarget).hasClass("item"))
		{
			// Item text determines decade
			var l_nTargetDecade = parseInt($(l_xTarget).text());

			if (this._xDecades && this._xDecades[l_nTargetDecade]) {
				this.scrollToDecade(l_nTargetDecade);
			}
		}
	},


	/**
	 *	Scroll to the specified decade. If specified decade is not yet being displayed, it will be loaded into memory
	 *	first.
	 *
	 *	@param	p_nDecade	Decade to scroll to
	 **/
	scrollToDecade : function(p_nDecade)
	{
		// If target decade has not yet been displayed, load the splash items for all
		// decades between currently oldest and target decade
		//
		if (p_nDecade < this._nCurrentMinDecade) {
			this.loadSplashItems(this._nCurrentMinDecade - 10, p_nDecade);
		}
		
		// Slide page content at about 2000px per second, i.e. (offset_top / 1000) * 1000ms
		// However, ensure that duration is always between 250 and 1000ms
		//
		var l_nDuration =  Math.abs($("#decade-" + p_nDecade).offset().top) / 1000 * 1000;
		l_nDuration = Math.min(Math.max(250, l_nDuration), 1000);
		$(this._xScrollContainer).stop().scrollTo("#decade-" + p_nDecade, { duration: l_nDuration, easing: "swing" });
	},


	/**
	 *	Respond to the user scrolling through the grid or resizing the browser window. This loads new splash elements
	 *	as needed, and ensures that the last item row is always left-aligned with a terminator item.
	 *
	 *	@param	p_xEvent	Event from a scroll or resize action
	 **/	
	scrollContainerUpdateHandler : function(p_xEvent)
	{
		// If the user scrolls past the content loading threshold, load more items at the bottom.
		//
		if (this._xScrollContainer.scrollTop() > this._xDomElement.height() * DrPepper.About.Timeline.Grid.CONTENT_LOADING_THRESHOLD) {
			this.loadSplashItems();
		}

		this.terminateLastRow();
	},


	/**
	 *	Respond to the user moving their mouse to update the highlight frame.
	 *	NOTE: Ideally, this would be handled by a mouseover/out or hover handler, but due to issues in individual
	 *	browsers, this yields the best results.
	 *
	 *	@param	p_xEvent	Mouse move event
	 **/
	mouseMoveHandler : function(p_xEvent)
	{
		var l_xNewHoverTarget = $(p_xEvent.target);
		var l_xNewHoverItem = l_xNewHoverTarget.closest("a")[0];

		if ($(l_xNewHoverItem).hasClass("loaded")) {
			this.highlightItem(l_xNewHoverItem);
		}
		else if (!l_xNewHoverTarget.closest("#timeline-grid-highlight").length) {
			this.highlightItem(null);
		}
	},


	/**
	 *	Respond to clicks on items in the grid.
	 *
	 *	@param	p_xEvent
	 **/	
	mouseClickHandler : function(p_xEvent)
	{
		if (!this._xHighlightedItem) {
			return;
		}
		
		var l_sItemID = $(this._xHighlightedItem).attr("id");
		var l_sItemType = l_sItemID.substr(0, 4);
		
		if (l_sItemType == "more") {
			this.highlightItem(null);
			var l_nDecade = l_sItemID.substr(5, 4);
			this.loadMoreItems(l_nDecade);
		}
		else if (l_sItemType == "item") {
			this.showDetailOverlay(l_sItemID.substr(5));
		}
	},

	/**
	 *	Show detail overlay SWF for the given item ID or hash.
	 *	NOTE: Hash will be passed to the SWF via browser URL, not through ExternalInterface.
	 *
	 *	@param	p_sItemID	ID of the item to display (may be null if p_sHash is specified)
	 *	@param	p_sItemHash	Hash of the item to display (may be null if p_sItemID is specified)
	 **/
	showDetailOverlay : function(p_sItemID, p_sItemHash)
	{
	    $.blockUI.defaults.overlayCSS.opacity = .8;
	    
    	$("#" + DrPepper.About.Timeline.Grid.DETAIL_SWF_WRAPPER_ID).css("display", "none");
    	$("#" + DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WRAPPER_ID).css("display", "none");

		// Set browser location hash for the SWF to retrieve
		if (p_sItemID) {
		    location.hash = this._xItemsByID[p_sItemID].hash;
		}
		else {
		    location.hash = p_sItemHash;
		} 

		// Once the blocking overlay is visible, show the detail SWF with a bit of delay, to ensure smooth rendering
	    $.blockUI.defaults.onBlock = function() {
	    	setTimeout(function() {
		    	$("#" + DrPepper.About.Timeline.Grid.DETAIL_SWF_WRAPPER_ID).css("display", "block");
	    	}, 200);
	    };

		swfobject.embedSWF(
			DrPepper.About.Timeline.Grid.DETAIL_SWF_URL,
			DrPepper.About.Timeline.Grid.DETAIL_SWF_ID,
			DrPepper.About.Timeline.Grid.DETAIL_SWF_WIDTH, DrPepper.About.Timeline.Grid.DETAIL_SWF_HEIGHT,
			"9.0.124", null, 
			{ },
			{ wmode: "transparent", allowfullscreen: true, allowscriptaccess: "always" },
			{ id: DrPepper.About.Timeline.Grid.DETAIL_SWF_ID, name: DrPepper.About.Timeline.Grid.DETAIL_SWF_ID }
		);
	    
	    // Open the overlay here
		DrPepper.About.Timeline.Grid.openOverlay();
	},

	showContributeOverlay : function()
	{
	    $.blockUI.defaults.overlayCSS.opacity = .6;

    	$("#" + DrPepper.About.Timeline.Grid.DETAIL_SWF_WRAPPER_ID).css("display", "none");
    	$("#" + DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WRAPPER_ID).css("display", "none");

		// Once the blocking overlay is visible, show the detail SWF with a bit of delay, to ensure smooth rendering
	    $.blockUI.defaults.onBlock = function() {
	    	setTimeout(function() {
		    	$("#" + DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WRAPPER_ID).css("display", "block");
	    	}, 200);
	    }

		swfobject.embedSWF(
			DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_URL,
			DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID,
			DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_WIDTH, DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_HEIGHT,
			"9.0.124", null, 
			{ },
			{ wmode: "transparent", allowfullscreen: true, allowscriptaccess: "always" },
			{ id: DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID, name: DrPepper.About.Timeline.Grid.CONTRIBUTE_SWF_ID }
		);
	    
	    // Open the overlay here
		DrPepper.About.Timeline.Grid.openOverlay();
	},
	
	/**
	 * Shows the highlight marker on top of the specified item. Differentiates between image/video and more... items.
	 * No other elements are highlighted.
	 *
	 * @param	p_xItemDomElement	DOM element of the item to be highlighted
	 **/
	highlightItem : function(p_xItemDomElement)
	{
		if (p_xItemDomElement) {
			//
			// An item was specified...
			//

			if (this._xHighlightedItem) {
				// Bring back semi-transparent overlay of the item that was highlighted until now (if any)
				$(".overlay", this._xHighlightedItem).removeClass("inactive");
			}

			// Record specified item as the currently highlighted one
			this._xHighlightedItem = p_xItemDomElement;

			// Deactivate its overlay
			$(".overlay", p_xItemDomElement).addClass("inactive");

			// Get item info (ID and type ("item" or "more") - this is faster than using .hasClass())
			var l_nItemID = $(p_xItemDomElement).attr("id").substr(5);
			var l_sItemType = $(p_xItemDomElement).attr("id").substr(0, 4); 

			if (l_sItemType == "item") {
				//
				// For all regular items...
				//
				
				// Fetch item info from internal items array
				var l_xItem = this._xItemsByID[l_nItemID];
				
				// Set title, but allow a maximum of 20 characters
				var l_sTitle = l_xItem.title;
				l_sTitle = l_sTitle.length > 20 ? l_sTitle.substr(0, 20) + "..." : l_sTitle; 
				this._xHighlightTitleDomElement
					.text(l_sTitle);

				// Adjust visual setup of highlight box; this includes correctly positioning the time sprite.
				//					
				l_nDecade4 = Math.floor(l_xItem.year * .1) * 10;						// 1932 -> 1930
				l_nYear1 = l_xItem.year - l_nDecade4;									// 1932 -> 2
				l_nDecade1 = (l_nDecade4 - Math.floor(l_nDecade4 * .01) * 100) * .1;	// 1932 -> 3

				this._xHighlightDomElement.removeClass("more");				
				this._xHighlightTimeDomElement
					.addClass("year")
					.removeClass("decade")
					.css("backgroundPosition", (-l_nYear1 * 44) + "px " + (-l_nDecade1 * 21) + "px");
			}
			else if (l_sItemType == "more") {
				//
				// For all "more" items...
				//
				
				// Fetch item info from internal items array
				var l_xItem = this._xItemsByID[l_nItemID];

				// Adjust visual setup of highlight box; this includes correctly positioning the time sprite.
				//					
				l_nDecade4 = $(p_xItemDomElement).attr("id").substr(5, 4);				// 1932 -> 1930
				l_nDecade1 = (l_nDecade4 - Math.floor(l_nDecade4 * .01) * 100) * .1;	// 1932 -> 3

				this._xHighlightDomElement.addClass("more");
				this._xHighlightTitleDomElement.text("");
				this._xHighlightTimeDomElement
					.addClass("decade")
					.removeClass("year")
					.css("backgroundPosition", (-l_nDecade1 * 56) + "px -210px");
			}

			// Show highlight box and position it on top of the specified item
			this._xHighlightDomElement
				.css("display", "block")
				.css("left", $(this._xHighlightedItem).offset().left)
				.css("top", $(this._xHighlightedItem).offset().top + ($.browser.safari ? this._xScrollContainer.scrollTop() : 0));
		}
		else {
			//
			// No item specified: clear highlight.
			//
			if (this._xHighlightedItem) {
				// If there is still a highlighted item, bring back its semi-transparent overlay
				$(".overlay", this._xHighlightedItem).removeClass("inactive");
				this._xHighlightedItem = null;
			}
			
			// Hide highlight entirely
			this._xHighlightDomElement.css("display", "none")
		}
	},


	/**
	 *	Load more items as a response to the user clicking on a MORE... item.
	 *
	 *	@param	p_nDecade	Decade for which to load more items.
	 **/
	loadMoreItems : function(p_nDecade)
	{
		// If items are currently being created in response to a previous call, don't add more.
		// This is done because each call to the server sends the hash of the last visible item for the decade,
		// which is only known after a call returns. (That is: Clicking repeatedly on MORE very fast would
		// result in the same items being requested again.)
		// 
		if (this._bIsCreatingItems) {
			return;
		}
		this._bIsCreatingItems = true;

		var l_xThis = this;

		var l_nMaxDecade = p_nDecade;
		var l_nMinDecade = p_nDecade;


		// STEP 1:
		// Based on total item count as retrieved on page load via getItemCountByDecade(), generate as many DOM
		// elements as necessary to host the items that will be returned by the server.
		var l_xNewItemDomElement;
		var l_xNewItemDomElements = [];
		

		// Generate actual items
		var l_nRequestedItemCount = Math.min(l_xThis._xDecades[p_nDecade].totalItemCount - l_xThis._xDecades[p_nDecade].currentItemCount, DrPepper.About.Timeline.Grid.MORE_ITEM_PAGING_COUNT);

		var l_xMoreItem = $("#more-" + p_nDecade, this._xDomElement);
		for (var i = 0; i < l_nRequestedItemCount; i++) {
			l_xNewItemDomElement = this.createItem(DrPepper.About.Timeline.Grid.ITEMTYPE_ITEM);
			l_xNewItemDomElements.push(l_xNewItemDomElement);
			
			l_xNewItemDomElement.insertBefore(l_xMoreItem);
			
			if (!$.browser.msie || $.browser.version > 6) {
				l_xNewItemDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY * (1 + i * .5));
			}
		}

		if (l_xThis._xDecades[p_nDecade].currentItemCount + l_nRequestedItemCount >= l_xThis._xDecades[p_nDecade].totalItemCount) {
			l_xMoreItem.css("display", "none");
			this._nVisibleItemCount--;
		}


		// Adjust terminating element in last row (this left-aligns items in last row)
		this.terminateLastRow();


		// STEP 2:
		// Request items from server
		//
 		$.ajax({
 			type:		"GET",
 			cache:		false,
 			dataType:	"json",
 			url:		DrPepper.About.Timeline.Grid.URL_TIMELINE_MANAGER,
 			data:		"action=getmoreitems&d=" + p_nDecade + "&rh=" + l_xThis._xDecades[p_nDecade].referenceHash + "&mc=" + (l_nRequestedItemCount + 1),
 			success:	loadSuccessHandler,
 			error:		loadErrorHandler	
 		});		


		// STEP 3:
		// Fill above DOM elements using items returned by server
		//
		function loadSuccessHandler(p_xReturnedItems)
		{
			if (!p_xReturnedItems || !p_xReturnedItems.length || p_xReturnedItems == "error") {
				return;
			}

			var l_xItem;
			var l_nItemID;
			var l_sItemHash;
			var l_xItemDomElement;
			var l_xImage;
			
			var l_xNewItemCount = Math.min(l_xNewItemDomElements.length, p_xReturnedItems.length);
			for (i = 0; i < l_xNewItemCount; i++)
			{
				l_xItem = p_xReturnedItems[i];
				l_nItemID = l_xItem.id;
				l_sItemHash = l_xItem.hash;
				
				// Store item for quick reference when it is clicked
				l_xThis._xItemsByID[l_nItemID] =  l_xItem;

				// Set ID and item type (image / video) of corresponding DOM element
				l_xItemDomElement = l_xNewItemDomElements[i];
				$(l_xItemDomElement).attr("id", "item-" + l_nItemID);
				l_xThis.setItemType(l_xItemDomElement, parseInt(l_xItem.type));

				// Begin loading item image (thumbnail)
				//				
				l_xImage = new Image();
				l_sImageSrc = DrPepper.About.Timeline.Grid.URL_IMAGES + DrPepper.About.Timeline.Grid.FILENAME_PATTERN_THUMBNAIL.replace(/__HASH__/, l_sItemHash);
				
				(function(p_xItemDomElement) {
					$(l_xImage)
						.load(function() {
							$(this).unbind();

							var l_xPayloadDomElement = $(".payload", p_xItemDomElement);

							l_xPayloadDomElement.attr("src", this.src);
							
							if (!$.browser.msie || $.browser.version > 6) {
								l_xPayloadDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY);
							}

							p_xItemDomElement.addClass("loaded");
						})
						.error(function() {
							$(this).unbind();
						})
						.attr("src", l_sImageSrc)
				})(l_xItemDomElement);

				l_xThis._xDecades[p_nDecade].currentItemCount++;
			}
			
			// Remember hash of the last loaded item and ensure that subsequent loads will not be blocked anymore
			l_xThis._xDecades[p_nDecade].referenceHash = l_sItemHash;
			l_xThis._bIsCreatingItems = false;
		}
		function loadErrorHandler()
		{
			// If loading fails, remove the created DOM elements again
			//
			var l_nNewItemCount = l_xNewItemDomElements.length;
			
			for (var i = 0; i < l_nNewItemCount; i++) {
				$(l_xNewItemDomElements[i]).remove();
			}
			l_xThis._bIsCreatingItems = false;
		}
	},


	/**
	 *	Load more items as a response to the user scrolling or resizing the window
	 *
	 *	@param	p_nMaxDecade	Most recent decade for which to load splash items
	 *	@param	p_nMinDecade	Oldest decade for which to load splash items
	 **/
	loadSplashItems : function(l_nMaxDecade, l_nMinDecade)
	{
		// If items are currently being created in response to a previous call, don't add more.
		// This is done because each call to the server sends the hash of the last visible item for the decade,
		// which is only known after a call returns. (That is: Clicking repeatedly on MORE very fast would
		// result in the same items being requested again.)
		// 
		if (this._bIsCreatingItems) {
			return;
		}
		this._bIsCreatingItems = true;

		var l_xThis = this;

		if (!l_nMaxDecade) {
			// Determine which decades to request to generate more splash items; this is based on the oldest decade
			// currently available in the document, and on how large the paging size for decades is.
			var l_nMaxDecade = (this._nCurrentMinDecade ? this._nCurrentMinDecade - 10 : this._nLatestDecade);
			var l_nMinDecade = Math.max(this._nEarliestDecade, l_nMaxDecade - (DrPepper.About.Timeline.Grid.DECADE_PAGING_COUNT * 10) + 10);
		}

		if ((this._nCurrentMinDecade == l_nMinDecade) || (this._nCurrentMinDecade > this._nCurrentMaxDecade)) {
			this._bIsCreatingItems = false;
			return;
		}

		this._nCurrentMinDecade = l_nMinDecade;
		this._nCurrentMaxDecade = l_nMaxDecade;


		// STEP 1:
		// Based on total item count as retrieved on page load via getItemCountByDecade(), generate as many DOM
		// elements as necessary to host the items that will be returned by the server.
		var l_xNewItemDomElement;
		var l_xNewItemDomElementsByDecade = [];
		var l_nTotalItemCount;
		var l_nRequestedItemCount;
		
		for (var l_nDecade = l_nMaxDecade; l_nDecade >= l_nMinDecade; l_nDecade -= 10)
		{
			if (!this._xDecades[l_nDecade]) {
				continue;
			}
			
			// Keep references to all DOM elements for loadSuccessHandler() to fill them directly
			l_xNewItemDomElementsByDecade[l_nDecade] = [];
			l_nTotalItemCount = this._xDecades[l_nDecade].totalItemCount;

			
			// Generate header item for this decade
			l_xNewItemDomElement = this.createItem(DrPepper.About.Timeline.Grid.ITEMTYPE_HEADER);
			l_xNewItemDomElement
				.attr("id", "decade-" + l_nDecade)
				.insertBefore(this._xTerminatorItem);
			$("img", l_xNewItemDomElement).css("background", "url(/image/about/timeline/item_load-indicator.gif) 50% 50% no-repeat").css("opacity", .5);

			var l_xImage = new Image();
			(function(p_xHeaderDomElement) {
				$(l_xImage)
					.load(function() {
						$(this).unbind();
						
						var l_xImageDomElement = $("img", p_xHeaderDomElement); 
						if (!$.browser.msie || $.browser.version > 6) {
							l_xImageDomElement
								.attr("src", this.src)
								.css({ "background": "transparent", "opacity": 0 })
								.animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY, "swing", function() { l_xImageDomElement.css("opacity", ""); });
						}
						else {
							l_xImageDomElement
								.css("filter", "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "', sizingMethod='crop')")
								.css({ "background": "transparent", "opacity": "" });
						}
					})
					.attr("src", "/image/about/timeline/item_header_" + l_nDecade + "s.png");
			})(l_xNewItemDomElement);


			
			// Generate actual items
			l_nRequestedItemCount = Math.min(l_nTotalItemCount, DrPepper.About.Timeline.Grid.SPLASH_ITEM_PAGING_COUNT);
			for (var i = 0; i < l_nRequestedItemCount; i++) {
				l_xNewItemDomElement = this.createItem(DrPepper.About.Timeline.Grid.ITEMTYPE_ITEM);
				l_xNewItemDomElementsByDecade[l_nDecade].push(l_xNewItemDomElement);

				l_xNewItemDomElement.insertBefore(this._xTerminatorItem);

				if (!$.browser.msie || $.browser.version > 6) {
					l_xNewItemDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY * (1 + i * .5));
				}
			}
			
			// Generate "more" item, if the total number of items is larger than splash item paging size
			if (l_nTotalItemCount > DrPepper.About.Timeline.Grid.SPLASH_ITEM_PAGING_COUNT) {
				l_xNewItemDomElement = this.createItem(DrPepper.About.Timeline.Grid.ITEMTYPE_MORE);
				
				l_xNewItemDomElement.attr("id", "more-" + l_nDecade).insertBefore(this._xTerminatorItem);
				
				if (!$.browser.msie || $.browser.version > 6) {
					l_xNewItemDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY * (1 + i * .5));
				}
			}
		}

		// Adjust terminating element in last row (this left-aligns items in last row)
		this.terminateLastRow();

		// Don't prevent scroll updates anumore at this point
		this._bIsCreatingItems = false;
		
		
		// STEP 2:
		// Request items from server
		//
 		$.ajax({
 			type:		"GET",
 			cache:		false,
 			dataType:	"json",
 			url:		DrPepper.About.Timeline.Grid.URL_TIMELINE_MANAGER,
 			data:		"action=getsplashitems&maxd=" + l_nMaxDecade + "&mind=" + l_nMinDecade + "&mc=" + DrPepper.About.Timeline.Grid.SPLASH_ITEM_PAGING_COUNT,
 			success:	loadSuccessHandler,
 			error:		loadErrorHandler	
 		});		


		// STEP 3:
		// Fill above DOM elements using items returned by server
		//
		function loadSuccessHandler(p_xResult)
		{
			if (!p_xResult || p_xResult == "error") {
				return;
			}

			var l_xItem;
			var l_sItemHash;
			var l_nItemID;
			var l_xItemDomElement;
			var l_xImage;
			
			for (var l_nDecade = l_nMaxDecade; l_nDecade >= l_nMinDecade; l_nDecade -= 10)
			{
				if (!l_xThis._xDecades[l_nDecade]) {
					continue;
				}
				var l_xReturnedItems = p_xResult[l_nDecade];
				var l_xNewItemDomElements = l_xNewItemDomElementsByDecade[l_nDecade];
				
				for (i = 0; i < Math.min(l_xNewItemDomElements.length, p_xResult[l_nDecade].length); i++)
				{
					l_xItem = l_xReturnedItems[i];

					// Get item information
					l_sItemHash = l_xItem.hash;
					l_nItemID = l_xItem.id;
					
					// Store item for quick reference when it is clicked
					l_xThis._xItemsByID[l_nItemID] =  l_xItem;

					// Set ID and type of the corresponding DOM element
					l_xItemDomElement = l_xNewItemDomElements[i];
					$(l_xItemDomElement).attr("id", "item-" + l_nItemID);
					l_xThis.setItemType(l_xItemDomElement, parseInt(l_xItem.type));

					// Load image (thumbnail) for this item					
					l_xImage = new Image();
					l_sImageSrc = DrPepper.About.Timeline.Grid.URL_IMAGES + DrPepper.About.Timeline.Grid.FILENAME_PATTERN_THUMBNAIL.replace(/__HASH__/, l_sItemHash);
					
					(function(p_xItemDomElement) {
						$(l_xImage)
							.load(function() {
								$(this).unbind();
								
								var l_xPayloadDomElement = $(".payload", p_xItemDomElement);
								l_xPayloadDomElement.attr("src", this.src);

								if (!$.browser.msie || $.browser.version > 6) {								
									l_xPayloadDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY);
								}

								p_xItemDomElement.addClass("loaded");
							})
							.error(function() {
								$(this).unbind();
							})
							.attr("src", l_sImageSrc)
					})(l_xItemDomElement);

					// Keep track of current item count for this decade (faster access than calculating based on DOM
					// elements. Needs to be handled carefully, though, due to redundancy.
					l_xThis._xDecades[l_nDecade].currentItemCount++;
					
					// Keep track of last visible item hash for this decade
					l_xThis._xDecades[l_nDecade].referenceHash = l_sItemHash;
				}
				
				// Load image for MORE item
				l_xImage = new Image();
				(function(p_nDecade) {
					$(l_xImage).load(function() {
						$(this).unbind();
						
						var l_xPayloadDomElement = $("#more-" + p_nDecade + " .payload", l_xThis._xDomElement);
						l_xPayloadDomElement.attr("src", "/image/about/timeline/item_more_" + p_nDecade + "s.jpg");

						if (!$.browser.msie || $.browser.version > 6) {
							l_xPayloadDomElement.css("opacity", 0).animate({ opacity: 1 }, DrPepper.About.Timeline.Grid.ITEM_FADEIN_DELAY);
						}
					})
					.error(function() {
						$(this).unbind();
					})
					.attr("src", "/image/about/timeline/item_more_" + p_nDecade + "s.jpg");
				})(l_nDecade);
			}
		}
		function loadErrorHandler()
		{
			// If loading fails, remove the created DOM elements again
			//
			for (var l_nDecade in l_xNewItemDomElementsByDecade) {
				var l_nNewItemCount = l_xNewItemDomElementsByDecade[l_nDecade].length;
				for (var i = 0; i < l_nNewItemCount; i++) {
					$(l_xNewItemDomElementsByDecade[l_nDecade][i]).remove();
				}
				$("#more-" + l_nDecade).remove();
				$("#decade-" + l_nDecade).remove();
			}
		}
	},


	/**
	 *	Expand or contract the final DOM element in the grid (the "terminator"), to ensure that the last row is
	 *	always left-aligned.
	 **/
	terminateLastRow : function()
	{
		var l_nItemsPerRow = Math.floor(this._xDomElement.width() / this.ITEM_WIDTH);
		var l_nLastRowItemCount = this._nVisibleItemCount % l_nItemsPerRow;
		var l_nTerminatorItemCount = l_nLastRowItemCount > 0 ? l_nItemsPerRow - l_nLastRowItemCount : 0;
		
		this._xTerminatorItem.width(l_nTerminatorItemCount * this.ITEM_WIDTH);
		this._xDomElement.append(this._xTerminatorItem);
	},


	/**
	 *	Create a grid item
	 *
	 *	@param	p_nItemType	ITEMTYPE_ITEM, _HEADER, _MORE, or _TERMINATOR; _ITEM is used here for both IMAGE and VIDEO,
	 *						and will be specified in more detail when the item is actually loaded.
	 **/
	createItem : function(p_nItemType)
	{
		var r_xNewItem;
		
		switch (p_nItemType) {
			
			case DrPepper.About.Timeline.Grid.ITEMTYPE_ITEM:
				r_xNewItem = $("<a />").addClass("item image"); // image added only for the load spinner

				r_xNewItem
					.append($("<img />").attr("src", "/image/about/timeline/transparent.gif").addClass("payload"))
					.append($("<span />").addClass("overlay"));
			
				this._nVisibleItemCount++;
				break;
				
			case DrPepper.About.Timeline.Grid.ITEMTYPE_HEADER:
				r_xNewItem = $("<div />").addClass("item");

				r_xNewItem
					.addClass("header")
					.append($("<img />").attr("src", "/image/about/timeline/transparent.gif"));

				this._nVisibleItemCount++;
				break;
				
			case DrPepper.About.Timeline.Grid.ITEMTYPE_MORE:
				r_xNewItem = $("<a />").addClass("item");

				r_xNewItem
					.addClass("more loaded")
					.append($("<img />").attr("src", "/image/about/timeline/transparent.gif").addClass("payload"))
					.append($("<span />").addClass("icon"))
					.append($("<span />").addClass("overlay"));
				
				this._nVisibleItemCount++;
				break;

			case DrPepper.About.Timeline.Grid.ITEMTYPE_TERMINATOR:
				r_xNewItem = $("<div />").addClass("item");

				r_xNewItem
					.addClass("terminator");
					
				break;
		}

		return r_xNewItem;
	},

	/**
	 *	Set specific type for an item. Currently supported: IMAGE and VIDEO. Video items will have a play button.
	 *	overlay.
	 *
	 *	@param	p_xItemDomElement	DOM element of the item
	 *	@param	p_nItemType			item type (image or video)
	 *
	 **/
	setItemType: function(p_xItemDomElement, p_nItemType)
	{
		switch (p_nItemType)
		{
			case DrPepper.About.Timeline.Grid.ITEMTYPE_IMAGE:
				$(p_xItemDomElement).addClass("image");
				break;
			
			case DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_YOUTUBE:
			case DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_MYSPACE:
			case DrPepper.About.Timeline.Grid.ITEMTYPE_VIDEO_VIMEO:
				($("<img />").attr("src", "/image/about/timeline/transparent.gif").addClass("video-overlay")).insertBefore($(".overlay", p_xItemDomElement));
				break;
			
			default:
				// unknown item type	
		}
	}
}
