/**
 * jquery.sv.interfaces.js provides the core functionality for interfaces
 *
 * this is particularly useful for the ajax calls used to grab additional items
 * for interfaces
 *
 */
;(function($){

	// defaults for all interfaces
	var iFaceDefaults = {
		pluginOpts : {} // options to be passed to plugin being used
		,ajaxOpts : { // options to be used to grab more data
			url : ''
			,error : function (XMLHttpRequest, textStatus, errorThrown) {
				$(this).data('more',false);
			}
			,data : {}
		}
		,extraOpts : {}
	};

	$.extend($.sv, {
		scrollInterface : function ( options ) { // scrollInterface
			var defaults = {
				pluginOpts : {
					scrollable : {
						api : true
					}
					,autoscroll : {
						enable : false
					}
				}
				,extraOpts : {
					itemGrouping : 1
				}
			},
			$this = $(this);

			var options = $.extend(true, defaults, iFaceDefaults, options),
			autoscrollEnable = options.pluginOpts.autoscroll.enable;

			// check dependencies
			if ( !$.tools.scrollable ) {
				$.error( 'This plugin depends on the jQuery tools scrollable plugin' );
			}

			// only use the ajax stuff if you have to
			if ( options.ajaxOpts.url.length > 0 ) {
				// indicate that there are more slides by default
				$this.data('more',true);

				options.ajaxOpts.context = $this; // needed for the callbacks

				// a list of ids should be be stored for later
				if ( $.isArray(options.ajaxOpts.ids) ) {
					$this.data('idList',options.ajaxOpts.ids);
					// remove ids from ajaxOpts (this is very important)
					delete options.ajaxOpts.ids;
				}

				var success = options.ajaxOpts.success = function (itemData) {
					var itemGrouping = options.extraOpts.itemGrouping,
					$this = $(this),
					scrollApi = $this.scrollable(),
					conf = scrollApi.getConf();

					// expect the data returned to be an array
					if ( !$.isArray(itemData) ) {
						$this.data('more',false);
						$.error( 'Incorrect format returned from call' );
					} else if ( itemData.length < 1 ) {
						$this.data('more',false);
						$.error( 'No data remaining' );
					}

					// check if there is a next or prev object
					// (used to construct the query)
					if ( $.isPlainObject(itemData[0].prev) ) {
						$this.data('prev',itemData[0].prev);
					}
					if ( $.isPlainObject(itemData[itemData.length-1].next) ) {
						$this.data('next',itemData[itemData.length-1].next);
					}

					if ( itemGrouping > 1 ) {
						// first replace any 'duplicate' items
						var j = 0; // index for duplicate insertion
						$this.find('.widget-item.duplicate').each(function(i) {
							if ( itemData.length > 0 ) { // replace with items from itemData
								$(this).html(itemData.shift().html).removeClass('duplicate');
							} else {
								$(this).html($this.find('.widget-item')[j++].html());
							}
						});

						// if there are more itemData items, create new item-groups
						var itemProto = $this.find('.widget-item').first()
							,finalGroup = $this.find('.item-group').last()
							,newItem;
						while ( itemData.length > 0 ) {
							if ( finalGroup.find('.widget-item').length == itemGrouping ) {
								finalGroup = finalGroup.clone().empty();
								scrollApi.addItem(finalGroup);
							}
							newItem = itemProto.eq(0).clone();
							newItem.html(itemData.shift().html);
							finalGroup.append(newItem);
						}

						// fill up the finalGroup with duplicates
						while ( finalGroup.find('.widget-item').length < itemGrouping ) {
							$($this.find('.widget-item')[j++]).clone().addClass('duplicate').appendTo(finalGroup);
						}

					} else { // just add the single item
						var newItem;
						while ( itemData.length > 0 ) {
							newItem = $this.find('.widget-item').eq(0).clone();
							newItem.html(itemData.shift().html);
							scrollApi.addItem(newItem);
						}
					}

					$this.parent().find('.loading-img').fadeOut(100);
					$this.data('blockReq',false);
					if ( !conf.circular && ( $this.data('more') || scrollApi.getIndex() < scrollApi.getSize() - 1 ) ) {
						scrollApi.getNaviButtons().filter(conf.next).removeClass(conf.disabledClass);
					}
				};

				// construct query for ajax request
				function constructQuery (idx) {
					var ajaxOpts = {},
						idList = $this.data('idList'),
						nextData = $this.data('next');

					$.extend(ajaxOpts,options.ajaxOpts);

					if ( $.isArray(idList) && idList.length > 0 ) {
						// pass next id from list
						ajaxOpts.data.id = idList.shift();
						if ( idList.length == 0 ) {
							$this.data('more', false);
						}
					} else if ( !$.isArray(idList) && $.isPlainObject(nextData) ) {
						// use the conditions passed from the previous request
						$.extend(ajaxOpts.data,nextData);
					} else if ( !$.isArray(idList) ) {
						// just grab the next page
						ajaxOpts.data.page = idx + 2; // +1 for the next, +1 for 1-based indexing
					}

					return ajaxOpts;
				};

				var onSeek = options.pluginOpts.scrollable.onSeek = function (obj, idx) {
					if ( ! $this.data('more') ) { return }; // no more slides

					if ( typeof idx == 'undefined' || idx == (this.getSize()-1) ) {
						$this.parent().find('.loading-img').fadeIn(100);
						$this.data('blockReq',true);
						$.ajax(constructQuery(this.getIndex()));
					}
				};

				var onBeforeSeek = options.pluginOpts.scrollable.onBeforeSeek = function (obj, idx) {
					if ( $this.data('blockReq') ) {
						obj.preventDefault();
					}
					var scrollable = this,
					conf = scrollable.getConf(),
					navBttns = scrollable.getNaviButtons(),
					prev = navBttns.filter(conf.prev),
					next = navBttns.filter(conf.next);
					// because we start with only one item, the original onBeforeSeek
					// toggling the disabled class is not being done by the plugin
					if ( !conf.circular ) {
						setTimeout(function() {
							if (!obj.isDefaultPrevented()) {
								prev.toggleClass(conf.disabledClass, idx <= 0);
								next.toggleClass(conf.disabledClass, idx >= scrollable.getSize() - 1)
							}
						}, 1);
					} else {
						// TODO: figure out a way to get circular to work with ajax
						// There are some pretty big difficulties in implementing this
					}
				}
				// the original onBeforeSeek doesn't handle circular and ajax
				// together so we're going to set circular to always be false when
				// using ajax. It's not ideal, but it prevents completely broken
				// behavior.
				options.pluginOpts.scrollable.circular = false;

			} else {
				$this.data('more', false);
			}
			
			// The following is a workaround for an issue in jQuery Tools
			// see https://github.com/jquerytools/jquerytools/pull/412 for more info.
			//
			// We want to exit early, if we have one or zero items
			// This assumes the standard setup and value for the "items" option
			// and the standard setup for prev and next buttons
			if ($this.data('more') === false
					&& $this.find('.items').first().children().length <= 1) {
				$this.parent().find('.prev, .next').addClass('disabled');
				return this;
			}
			
			var scrollApi = $this.scrollable(options.pluginOpts.scrollable);

			// we are using ajax here
			if ( $.isFunction(options.pluginOpts.scrollable.onSeek) ) {
				options.pluginOpts.scrollable.onSeek.apply( scrollApi );

				var conf = scrollApi.getConf();

				if ( !conf.circular ) {
					scrollApi.getNaviButtons().filter(conf.prev).addClass(conf.disabledClass);
				}
			}
			
			if (autoscrollEnable ) {
				$this.autoscroll(options.pluginOpts.autoscroll);	
			}

		}
		////
		// slideInterface
		////
		,slideInterface: function( options ) {
			var defaults = {},
			$this = $(this);

			var options = $.extend(true, defaults, iFaceDefaults, options);

			// check dependencies
			if ( !$.fn.cycle ) {
				$.error( 'This plugin depends of the jQuery Cycle plugin' );
			}
			

			if ( options.pluginOpts.pager ) {
				options.pluginOpts.pagerAnchorBuilder = function(idx, el) {
					return $(this.pager).find('li a')[idx];
				};
				options.pluginOpts.updateActivePagerLink = function(pager, currSlide, clsName) {
					$thumbs = $(pager).children('li');
					$thumbs.not(function(idx){return idx == currSlide})
						.removeClass(clsName).fadeTo('slow', 0.5);
					$thumbs.eq(currSlide).addClass(clsName).fadeTo('50', 1);
				};
			}

			// only use the ajax stuff if you have to
			if ( options.ajaxOpts.url.length > 0 ) {
				// indicate that there are more slides by default
				$this.data('more',true);

				options.ajaxOpts.context = $this; // needed for the callbacks

				// a list of ids should be be stored for later
				if ( $.isArray(options.ajaxOpts.ids) ) {
					$this.data('idList',options.ajaxOpts.ids);
					// remove ids from ajaxOpts (this is very important)
					delete options.ajaxOpts.ids;
				}

				var success = options.ajaxOpts.success = function (itemData) {
					var $this = $(this),
						opts = $this.data('cycle.opts'),
						fwd = $this.data('fwd');

					// expect the data returned to be an array
					if ( !$.isArray(itemData) ) {
						$this.data('more',false);
						$.error( 'Incorrect format returned from call' );
					} else if ( itemData.length < 1 ) {
						$this.data('more',false);
						$.error( 'No data remaining' );
					}

					// add new item to slideshow according to the options set
					var newItem;
					while ( itemData.length > 0 ) {
						newItem = $this.find('.widget-item').eq(0).clone().html(itemData.shift().html);
						opts.addSlide(newItem, !fwd);
					}

					$this.parent().find('.loading-img').fadeOut(100);
				};

				// construct query for ajax request
				function constructQuery (opts, fwd, success) {
					var ajaxOpts = {},
						idList = $this.data('idList');

					$.extend(ajaxOpts,options.ajaxOpts);

					// allow override of success callback
					if ( $.isFunction(success) ) {
						ajaxOpts.success = success;
					}

					// make sure the callback knows the direction
					$this.data('fwd',fwd);

					if ( $.isArray(idList) && idList.length > 0 ) {
						ajaxOpts.data.id = fwd ? idList.shift() : idList.pop();
						$this.data('idList',idList);
						$this.data('more',(idList.length > 0));
					} else if ( !$.isArray(idList) ) {
						// just grab the next page (we don't care about fwd here)
						// +1 for the next, +1 for 1-based indexing
						ajaxOpts.data.page = opts.nextSlide + 2;
						$this.data('fwd',true); // we only go forward
					}

					return ajaxOpts;
				};

				var before = options.pluginOpts.before = function (curr, next, opts, fwd) {
					if ( !opts.addSlide || !$this.data('more') ) { return; }; // no more slides

					$this.parent().find('.loading-img').fadeIn(100);

					$.ajax(constructQuery(opts, fwd));
				};

				// unless we reset the nextSlide to the actual next slide, it will go
				// back to the start. This works much better than having to reinitialize
				// the whole slideshow every time we add a slide.
				options.pluginOpts.onAddSlide = function(newSlide) {
					// the this scope in the callback here is the cycle.opts object
					this.nextSlide = this.currSlide > this.slideCount ? 0 : this.currSlide+1;
				};

				// pre-load slides (next and prev)
				// if the pager option is set, we load *all* slides
				$this.parent().find('.loading-img').fadeIn(100);

				(function preloadSlides(){
					$.ajax(constructQuery({nextSlide : 0},true,function (itemData) {
						var $this = $(this),
						$pager = $(options.pluginOpts.pager).children('li'),
						fwd = $this.data('fwd');

						// expect the data returned to be an array
						if ( !$.isArray(itemData) ) {
							$this.data('more',false);
							$.error( 'Incorrect format returned from call' );
						} else if ( itemData.length < 1 ) {
							$this.data('more',false);
							$.error( 'No data remaining' );
						}

						// add new item to slideshow according to the options set
						var newItem,
						newItemData,
						allItems = $this.find('.widget-item'),
						idx,
						pagerItem;
						while ( itemData.length > 0 ) {
							idx = fwd ? allItems.length - 1 : 0; // add to end or start
							newItemData = itemData.shift();
							newItem = $(allItems[idx]).clone(true).html(newItemData.html);
							if ( $pager.length > 0 && newItemData.headerID.length > 0 ) {
								pagerItem = $($pager[idx]).clone(true);
								pagerItem.find('img').attr('src',newItemData.headerID);
							}

							if ( fwd ) {
								$(allItems[idx]).after(newItem);
								if ( pagerItem ) $($pager[idx]).after(pagerItem);
							} else {
								$(allItems[idx]).before(newItem);
								if ( pagerItem ) $($pager[idx]).before(pagerItem);
							}

						}

						// recursively call this ajax function to pre-load all images if
						// a pager is in use
						if ( $pager.length > 0 && $this.data('more') ) {
							return preloadSlides();
						}

						$this.parent().find('.loading-img').fadeOut(100);

						// start this thing 
						$this.cycle(options.pluginOpts);

						// run the before function to load the last slide
						before(allItems[0],allItems[0],$this.data('cycle.opts'),false);
					}));
				})();

				return $this;

			} else {
				return $this.cycle(options.pluginOpts);
			}
			
		}
		////
		// tabInterface
		////
		,tabInterface: function ( options ) {
			var $this = $(this),
			defaults = {
				extraOpts: {
					tabHandles: $this.find('ul.tabs')
					,tabContent: $this.children('.widget-content').children('.widget-item')
					,linkTemplate: '<li class="${widgetClass}"><a href="#${widgetId}-${ii}">${title}</a></li>'
				}
				,ajaxOpts : {
					url : ''
					,ids : [] // an empty array
				}
			},
			options = $.extend(true, defaults, iFaceDefaults, options);

			// check dependencies
			if ( !$.tools.tabs ) {
				$.error( 'This plugin depends on the jQuery Tools Tabs plugin' );
			}

			// The ajax functionality built into jQuery tools is a little difficult
			// to integrate smoothly with the interfaces as they are, but not out of
			// the question. We may revisit it as a solution or even combine with the
			// pre-loading solution here.
			//
			// Pre-load all tabs via ajax based on the id list. Ajax for tabs needs to
			// be limited to a predefined set; pulling in on the fly doesn't work the
			// way it does for other interfaces.
			if ( options.ajaxOpts.url.length > 0
					&& $.isArray(options.ajaxOpts.ids)
					&& options.ajaxOpts.ids.length > 0 ) {
				// check dependencies
				if ( !$.isFunction($.tmpl) ) {
					$.error( 'This plugin depends on the jQuery Templates plugin' );
				}

				// indicate that there are more slides by default
				$this.data('more',true);

				options.ajaxOpts.context = $this; // for the callbacks

				// the list of ids should be be stored for later
				$this.data('idList',options.ajaxOpts.ids);
				delete options.ajaxOpts.ids;

				// asynchronous recursive call to grab all tabs
				(function preloadTabs() {
					var ajaxOpts = {},
					idList = $this.data('idList');

					$.extend(ajaxOpts,options.ajaxOpts);

					// need to make sure we only procede when there are more tabs
					if ( idList.length <= 0 || !$this.data('more') ) {
						$this.data('more',false);
						return;
					}

					ajaxOpts.data.id = idList.shift();
					$this.data('idList',idList);

					ajaxOpts.success = function(itemData) {
						var $this = $(this),
						$tabs = $(options.extraOpts.tabHandles);

						// expect the data returned to be an array
						if ( !$.isArray(itemData) ) {
							$this.data('more',false);
							$.error( 'Incorrect format returned from call' );
						} else if ( itemData.length < 1 ) {
							$this.data('more',false);
							$.error( 'No data remaining' );
						}

						// add new tabs
						var newItem,
						newItemData,
						idx,
						cfIdx, // the effective 1-based index of the new item
						tabList,
						tabItem;
						while ( itemData.length > 0 ) {
							tabList = options.extraOpts.tabHandles.find('li');
							idx = tabList.length - 1;
							newItemData = itemData.shift();
							newItem = $(options.extraOpts.tabContent[idx]).clone(true).html(newItemData.html);

							// update the id
							cfIdx = idx + 2;
							newItem.attr('id',$this.attr('id') + '-' + cfIdx);

							tabItem = $.tmpl(options.extraOpts.linkTemplate, {
								"widgetClass" : tabList[idx].classList
								,"widgetId" : $this.attr('id')
								,"ii" : cfIdx
								,"title" : newItemData.title
							});

							// add the latest item to the tabContent object
							$(options.extraOpts.tabContent[idx]).after(newItem);
							options.extraOpts.tabContent = options.extraOpts.tabContent.add(newItem);

							// add the tab to the list
							options.extraOpts.tabHandles.append(tabItem);
						}

						// recurse this thing
						if ( idList.length > 0 && $this.data('more') ) {
							return preloadTabs();
						}

						// initialize the plugin
						$(options.extraOpts.tabHandles).tabs(options.extraOpts.tabContent, options.pluginOpts);
					};

					$.ajax(ajaxOpts);
				})();

			} else {
				$(options.extraOpts.tabHandles).tabs(options.extraOpts.tabContent, options.pluginOpts);
			}

			return this;
		}
		////
		// galleryInterface is a wrapper for the popeye plugin
		// TODO: investigate ajax support for this
		////
		,galleryInterface: function ( options ) {
			var $this = $(this),
			options = $.extend(true, iFaceDefaults, options);

			// check dependencies
			if ( !$.isFunction($.fn.popeye) ) {
				$.error( 'This plugin depends on the jQuery Popeye plugin' );
			}

			// no ajax support
			$this.popeye( options.pluginOpts );
		}
	});

})(jQuery);


