/**
 * @fileoverview	newsday.com Homepage Feature Module Javascript
 * @author			Michael Bester <mbester@schematic.com>
 * @version			1.0
 * @dependencies	jquery.js (v.1.3.x)
 *					jquery.jcarousel.js
 *					global.js
 */

( XLI.FeaturedGuides = function() {

	/**
		A Flag to keep track of whether or not we've initialized this object.
	*/
	var initialized = false;

	/**
	 *	Set Some Constants
	 */
	var CONSTANTS = {

		// Featured Module ID
		MODULE_ID : "#featuredGuides",

		// The nav list id
		NAV_ID : "#featuredGuidesNav",

		// The class name for the carousel list.
		CAROUSEL_CLASS : "carousel",

		// The id of the scrollbar
		CONTENT_MAP_ID : "featuredViewScrollbar",

		// The ID of the scrollbar wrapper
		CONTENT_MAP_WRAPPER_ID : "featuredViewScrollbarWrapper",

		// The drag handle class name
		DRAG_HANDLE_CLASS : "dragHandle",

		// Content Map dragging class, to be applied to the HTML element
		DRAGGING_CLASS : 'dragging'

	};

	/**
	 *	Element References as returned by JQuery
	 *	references created as needed
	 */
	var $elements = {
		html : $('html'),
		module : null,
		nav	: null,
		carousel : null,
		contentMap : null,
		dragHandle : null,
		slices : []
	};

	/**
	 *	Shorthand references to the CONSTANTS and $elements.
	 */
	var C	= CONSTANTS;
	var $e	= $elements;

	/**
		a handle for the carousel
	*/
	var carousel = null;

	/**
	 * Sets up the module carousel functionality and nav.
	 * @private
	 * @returns nothing
	 */
	var initModule = function() {

		// Get some elements we'll need to deal with.
		$e.nav			= $e.module.find(C.NAV_ID);
		$e.carousel		= $e.module.find('.' + C.CAROUSEL_CLASS);

		if (!$e.carousel.length || !$e.nav.length) {
			return;
		}

		// Create a wrapper for the scrollbar nav
		$e.contentMapWrapper	= $('<div></div>')
									.attr('id', C.CONTENT_MAP_WRAPPER_ID)
									.appendTo($e.module);

		// Set up the carousel
		$e.carousel.jcarousel({
			pagerTarget : $e.contentMapWrapper,
			scroll: 3,
			initCallback : function(karousel) {
				carousel = karousel;
				buildContentMap();
				carousel.options.initCallback = null;
			},
			itemVisibleInCallback : {
				onBeforeAnimation: activateSlice,
				onAfterAnimation: null
			},
			itemVisibleOutCallback : {
				onBeforeAnimation: inactivateSlice,
				onAfterAnimation: null
			},
			classIR : "ir",
			classSlideTitleSuffix : "",
			containsAds : false,
			animation : 400
		});

		// Set up the navigation based on the
		// XLI.FeaturedGuides.guides array included in the template
		if ($.isArray(XLI.FeaturedGuides.guides)) {

			// First, set up the existing HTML
			var $li = $e.nav.find('li'),
				li	= $li.get(0);

			// Make note of the current content
			li.$content = $e.carousel.find(">li");

			$li.bind('click', activateGuide);
			// IE6 Hover fix
			XLI.Global.ie6Hover($li);

			// Then add additional items in the nav.
			$.each(XLI.FeaturedGuides.guides, function(){
				var $li = $('<li></li>')
							.text(this.name)
							.bind('click', activateGuide)
							.appendTo($e.nav);

				var li = $li.get(0);
				li.dataUrl = this.dataUrl;

				// IE6 Hover fix
				XLI.Global.ie6Hover($li);
			});

			// Activate the first item
			$li.eq(0).trigger('click');
		}

		/**
			Fix a redraw bug on the nav in IE.
			see: http://schematic.onjira.com/browse/XLI-272
		*/
		if (XLI.Global.ie6 || XLI.Global.ie7) {
			var hackery = function(){
				var $win = $(this),
					wst	= $win.scrollTop(),
					wh	= $win.height(),
					no	= $e.nav.offset(),
					nh	= $e.nav.height();
				if (wst + wh >= no.top + nh) {
					$e.nav.css('display','none').css('display','block');
				}
			};
			$(window).bind('scroll', hackery);
		}
	};

	/**
	 * Activates a guide when selected in the nav. Gets guide data via XHR
	 * if necessary and builds slides in the carousel based on the data recieved.
	 * @private
	 * @returns nothing
	 */
	var activateGuide = function() {
		var $this = $(this),
			that = this;

		if ($this.hasClass(XLI.Global.C.ACTIVE_CLASS)) {
			return;
		}

		this.$siblings = this.$siblings || $this.siblings();

		// Empty the carousel and the content map
		carousel.reset();
		resetContentMap();

		// If we already have the content...
		if (this.$content && this.$content.length) {
			// Add the slides
			this.$content.each(function(i){
				carousel.add((i + 1), $(this).html());
			});

			// build the slider
			buildContentMap();
			// reload the carousel
			carousel.setup();

		} else if (this.dataUrl) {
			// We don't yet have the data, so lets request it
			try {

				carousel.container.addClass(XLI.Global.C.LOADING_CLASS);

				$.ajax({
					url : this.dataUrl,
					dataType : 'json',
					cache : false,
					beforeSend : function(){
						carousel.lock();
					},
					success : function(json){

						carousel.container.removeClass(XLI.Global.C.LOADING_CLASS);

						if ($.isArray(json.slides)) {
							$.each(json.slides, function(){
								carousel.appendSlide(this);
							});
						}

						// build the slider
						buildContentMap();

						// reload the carousel
						carousel.unlock();
						carousel.setup();

						// Save the rendered carousel data
						that.$content = $e.carousel.find(">li");
					},
					error : function(xhr, textStatus, errorThrown) {
						carousel.container.removeClass(XLI.Global.C.LOADING_CLASS);
						carousel.unlock();
						XLI.Debug.error("Error getting new featured guide carousel content: " + textStatus);
					}
				});
			} catch(e) {
				carousel.container.removeClass(XLI.Global.C.LOADING_CLASS);
				XLI.Debug.error("Error getting new featured guide carousel content: " + e.message);
			}
		}

		// Set the active nav item.
		this.$siblings.removeClass(XLI.Global.C.ACTIVE_CLASS);
		$this.addClass(XLI.Global.C.ACTIVE_CLASS);
	};

	/**
	 * Sets up the HTML for the content map, properly adding and adjusting sections for each slide
	 * @private
	 * @returns nothing
	 */
	var buildContentMap = function() {

		var HTML = {
				DIV : "<div></div>"
			},
			slides = [],
			wideSlides = [],
			elContentMap,
			cmWidth,
			availWidth,
			adjustedAvailWidth = 0,
			cols,
			ww, wn,
			handleWidth = 0;

		// Create and append the Content Map Wrapper, or reset if if we've already got it.
		if ($e.contentMap !== null) {
			resetContentMap();
		} else {
			$e.contentMap = $(HTML.DIV)
								.attr('id', C.CONTENT_MAP_ID)
								.appendTo($e.contentMapWrapper);

		}

		elContentMap = $e.contentMap.get(0);
		elContentMap.oWidth = elContentMap.oWidth || $e.contentMap.width();

		// Create a skeleton structure of all slides, both rendered...
		$e.carousel.find('>li').each(function(i){
			var $this = $(this);
			slides.push({
				'title' : $this.find('.title').text(),
				'width' : (($this.hasClass('wide') || $this.hasClass('ad')) ? "wide" : ""),
				'displayAd' : $this.hasClass('ad')
			});
		});

		// Append slides to the map
		cmWidth = $e.contentMap.width();
		// Available width
		availWidth = cmWidth - slides.length;
		// Find out how many columns we have in our underlying grid of slides.
		cols = slides.length;
		// and set up our widths.
		w = Math.round(availWidth / cols);

		// Now let's add the sections
		$.each(slides, function(i){
			var $slice = $(HTML.DIV)
							.attr('carouselIndex', i + 1)
							.css('width', w)
							.appendTo($e.contentMap);

			if (this.title) {
				$slice.attr('title', this.title);
			}

			adjustedAvailWidth += w;

			// Save a reference to each slice
			$e.slices.push($slice);
		});

		// Add the margins back in
		//adjustedAvailWidth += (slides.length * 2);

		// Adjust available width and bind a click event to the slices
		$e.contentMapWrapper
			.css('width', adjustedAvailWidth);
		$e.contentMap
			.css('width', adjustedAvailWidth)
			.bind('click', function(e){
				var $slice = $(e.target),
					ci = parseInt($slice.attr('carouselIndex'), 10);

				if (isNaN(ci)) {
					return;
				}

				carousel.scroll(ci);
			});

	};

	/**
	 * Sets up the content map to be re-initialized
	 * @private
	 * @returns nothing
	 */
	var resetContentMap = function() {

		var cm = $e.contentMap.get(0);

		// empty the content map and reset its width
		$e.contentMap
			.empty()
			.css('width', cm.oWidth);

		// Empty the slices array
		while($e.slices.length) {
			$e.slices.pop();
		}

		// Remove the drag handle
		if ($e.dragHandle !== null) {
			$e.dragHandle.remove();
			$e.dragHandle = null;
		}
	};

	/**
	 * Activates a slice in the content map
	 * Called as a callback from the carousel action
	 * @private
	 * @param {Object} karousel A reference to the carousel object
	 * @param {Object} item The active slide
	 * @param {Number} index The 1-based index of the active slide
	 * @returns nothing
	 */
	var activateSlice = function(karousel, item, index) {
		var i = index - 1;
		if (typeof $e.slices[i] === 'undefined' || !$e.slices[i].length) {
			return;
		}
		$e.slices[i].addClass(XLI.Global.C.ACTIVE_CLASS);
		//if (!XLI.Global.ie6) {
			updateDragHandle();
		//}
	};

	/**
	 * Deactivates a slice in the content map
	 * Called as a callback from the carousel action
	 * @private
	 * @param {Object} karousel A reference to the carousel object
	 * @param {Object} item The active slide
	 * @param {Number} index The 1-based index of the active slide
	 * @returns nothing
	 */
	var inactivateSlice = function(karousel, item, index) {
		var i = index - 1;
		if (typeof $e.slices[i] === 'undefined' || !$e.slices[i].length) {
			return;
		}
		$e.slices[i].removeClass(XLI.Global.C.ACTIVE_CLASS);
		//if (!XLI.Global.ie6) {
			updateDragHandle();
		//}
	};

	/**
	 * Creates the drag handle for the content map if its not available.
	 * @private
	 * @returns nothing
	 */
	var createDragHandle = function() {
		$e.dragHandle = $('<div></div>')
							.addClass(C.DRAG_HANDLE_CLASS)
							.bind('mousedown', startDrag)
							.appendTo($e.contentMap);

		if (XLI.Global.moz) {
			$e.dragHandle
				.css('cursor', '-moz-grab')
				.bind('mouseup', function(){
					$e.dragHandle.css('cursor', '-moz-grab');
				})
				.bind('mousedown', function(){
					$e.dragHandle.css('cursor', '-moz-grabbing');
				});
		}
	};

	/**
	 * Updates the position and width of the drag handle to wrap around the active slices in the content map
	 * @private
	 * @returns nothing
	 */
	var updateDragHandle = function() {
		if ($e.dragHandle === null) {
			createDragHandle();
		}

		var padding = 2,
			width = 0,
			elFirst,
			$last,
			pos,
			x,
			prevActive = false,
			$curr;

		for (x = 0; x < $e.slices.length; x++) {
			$curr = $e.slices[x];

			$curr
				.removeClass('first')
				.removeClass('last');

			if ($curr.hasClass(XLI.Global.C.ACTIVE_CLASS)) {
				var el = $curr.get(0);
				el.pos = el.pos || $curr.position();
				el.offset = el.offset || $curr.offset();
				el.ow = el.ow || $curr.outerWidth(true);
				el.iw = el.iw || $curr.outerWidth();
				width += el.ow;
				if (!prevActive) {
					$curr.addClass('first');
					elFirst = $curr.get(0);
				}
				if (x === $e.slices.length - 1) {
					$curr.addClass('last');
					$last = $curr;
				}
				prevActive = $curr;
			} else {
				if (prevActive && prevActive.length) {
					prevActive.addClass('last');
					$last = prevActive;
				}
			}
		}

		$e.dragHandle
			.css({
				width : width + padding,
				left : elFirst.pos.left - (elFirst.ow - elFirst.iw) - padding
			});
			/*
			 * UNCOMMENT TO ADD NUMBERING
			.text(carousel.first + "–" + $last.attr('carouselIndex') + " / " + $e.slices.length);
			*/
	};

	/**
	 * Initializes dragging actions on the content map.
	 * @private
	 * @returns nothing
	 */
	var startDrag = function() {
		$e.html
			.bind('mouseup', endDrag)
			.bind('mousemove', updateDrag);
		if ($.browser.msie) {
			$e.html
				.bind('dragstart', ignoreNativeDrag)
				.bind('selectstart', ignoreNativeDrag);
		}

		if (!XLI.Global.moz) {
			$e.html.addClass(C.DRAGGING_CLASS);
		}

		// Save the original animation speed.
		carousel.originalAnimation = carousel.originalAnimation || carousel.options.animation;
		// and turn off the animation for dragging.
		carousel.options.animation = 100;

		return false;
	};

	/**
	 * Cleans up after content map dragging action.
	 * @private
	 * @returns nothing
	 */
	var endDrag = function() {
		$e.html
			.unbind('mouseup', endDrag)
			.unbind('mousemove', updateDrag);
		if ($.browser.msie) {
			$e.html
				.unbind('dragstart', ignoreNativeDrag)
				.unbind('selectstart', ignoreNativeDrag);
		}

		if (!XLI.Global.moz) {
			$e.html.removeClass(C.DRAGGING_CLASS);
		}

		// Revert the animation to its original speed.
		carousel.options.animation = carousel.originalAnimation;
	};

	/**
	 * Updates the carousel while dragging through the content map..
	 * @private
	 * @returns nothing
	 */
	var updateDrag = function(e) {

		$.each($e.slices, function(i){

			var el = this.get(0);
			el.offset = el.offset || this.offset();
			el.ow = el.ow || this.outerWidth(true);
			el.iw = el.iw || this.outerWidth();

			if (e.pageX > el.offset.left && e.pageX < (el.offset.left + el.iw)) {
				carousel.scroll(i + 1);
				return false;
			}
		});
	};

	/**
	 * Nothing more than a dummy function for IE compatibility while dragging.
	 * @private
	 * @returns false
	 * @type Boolean
	 */
	var ignoreNativeDrag = function() {
		return false;
	};

	return {

		/**
		 * Sets up the Featured Guides module
		 * @public
		 * @returns nothing
		 */
		initialize : function() {
			if (initialized) {
				return;
			}

			// Create a reference to the module.
			$e.module = $(C.MODULE_ID);

			// And bail out if we can't find it.
			if (!$e.module.length) {
				return;
			}

			initModule();

			initialized = true;
		}

	};

}());

/**
	Fire it up.
*/
$(document).ready(XLI.FeaturedGuides.initialize);
