(function($, window){
	Function.prototype.debounce = function (threshold, execAsap) {
		var func = this, // reference to original function
			timeout; // handle to setTimeout async task (detection period)
		// return the new debounced function which executes the original function only once
		// until the detection period expires
		return function debounced () {
			var obj = this, // reference to original context object
				args = arguments; // arguments at execution time
			// this is the detection function. it will be executed if/when the threshold expires
			function delayed () {
				// if we're executing at the end of the detection period
				if (!execAsap)
					func.apply(obj, args); // execute now
				// clear timeout handle
				timeout = null;
			};
			// stop any current detection period
			if (timeout)
				return;
				//clearTimeout(timeout);
			// otherwise, if we're not already waiting and we're executing at the beginning of the waiting period
			else if (execAsap)
				func.apply(obj, args); // execute now
			// reset the waiting period
			timeout = setTimeout(delayed, threshold || 100);
		};
	}
	
	// Our publication and subscription broker
	//http://marcuswest.in/Javascript/PubSub/PubSubBroker.js
	function PubSubBroker() {
		var signals = arguments;
		this.subscribers = {};
		for (var i=0; i < signals.length; i++) {
			this.subscribers[signals[i]] = [];
		}
	}

	PubSubBroker.prototype.publish = function(signal) {
		var args = Array.prototype.slice.call(arguments, 1);
		for (var i=0; i < this.subscribers[signal].length; i++) {
			var handler = this.subscribers[signal][i];
			handler.apply(this, args);
		}
	}

	PubSubBroker.prototype.subscribe = function(signal, scope, handlerName){
		var curryArray = Array.prototype.slice.call(arguments, 3);
		this.subscribers[signal].push(function(){
			var normalizedArgs = Array.prototype.slice.call(arguments, 0);
			scope[handlerName].apply((scope || window), curryArray.concat(normalizedArgs));
		});
	}
	
	//establish the broker
	var tlBroker = new PubSubBroker(
		'control-pos-change',
		'control-move-to-slide',
		'control-pos-tomark',
		'slideshow-move-to-slide',
		'show-pos-change'
	);
	
	$.fn.prTimeline = function(method) {
		var prTimeline = function(elm, options) {
			this.$elm = $(elm);
			var defaults = {
				resolution: 350
			}
			this.options = $.extend({}, defaults, options);
			this.$aperture = this.$elm.find(".aperture");
			this.$slides = $(".apertureItem", this.$aperture);
			var apWidth =  0;
			var pointsByPercent = {}
			var pointsByFrame = {};
			var slidesCount = this.$slides.length;
			this.$slides.each(function(i,v){
				var pP = new Array();
				var pF = new Array();
				//by per
				pP.push(apWidth, i);
				pointsByPercent[parseFloat(i/slidesCount).toFixed(3)] = pP;
				//by frame
				pF.push(apWidth, parseFloat(i/slidesCount).toFixed(3));
				pointsByFrame[i] = pF;
				apWidth += $(this).outerWidth();
			});
			this.slidesCount = slidesCount;
			this.pointsByPercent = pointsByPercent;
			this.pointsByFrame = pointsByFrame;
			this.apWidth = apWidth;
			this.$aperture.width(apWidth).show();
			//attach events
			this.attachEvents();
			this.activateSubscriptions();
		};
		prTimeline.prototype = {
			attachEvents: function() {
			},
			activateSubscriptions: function() {
				tlBroker.subscribe("control-pos-change", this, "moveShow");
				tlBroker.subscribe("control-move-to-slide", this, "moveShow");
				tlBroker.subscribe("control-pos-tomark", this, "markToFrame");
			},
			mouseMove: function(e) {
			},
			moveEvent: function(e) {
				this.moveShow(e.data);
			},
			moveShow: function(moveTo) {
				if(moveTo.hasOwnProperty("percent")) {
					this.moveToPercent(moveTo.percent);
				} else if(moveTo.hasOwnProperty("frame")) {
					this.moveToFrame(moveTo.frame);
				} else {
					return;
				}
			},
			moveToFrame: function(frameNo) {
				this.$aperture.animate({left: -(this.pointsByFrame[frameNo][0])}, 550, "easeInOutExpo");
				tlBroker.publish("slideshow-move-to-slide", {frame:frameNo, percent:this.pointsByFrame[frameNo]});
			},
			moveToPercent: function(percent) {
				this.$aperture.clearQueue();
				this.$aperture.animate({left:-(this.apWidth*percent)}, 250);
			},
			markToFrame: function(mark) {
				var point = this.pointsByPercent[mark.percent];
				this.moveToFrame(point[1]);
			}
		} 
		var init = function() {
			return this.each(function(){
				$this = $(this);
				var obj = $this.data().prTimelineObj
				if(typeof(obj) === "object") {
					return obj;
				} else {
					obj = new prTimeline(this, arguments);
					$this.data("prTimelineObj", obj);
					return obj;
				}
			});
		};
		
		var methods = {
			
		};
		
		// Method calling logic
		if ( methods[method] ) {
		  return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
		  return init.apply( this, Array.prototype.slice.call(arguments, 1) );
		} else {
		  $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
		}    
  
	}
	
	$.fn.prTimelineControl = function(method) {
		var prTimelineControl = function(elm, options) {
			this.$elm = $(elm);
			var defaults = {
				resolution: 100,
				edgeOn: 'left'
			}
			this.options = $.extend({}, defaults, options);
			this.$control = this.$elm.find(".shuttleBox");
			this.lineWidth = this.$elm.width();
			this.controlWidth = this.$control.width();
			this.timelineMarks = new Array();
			this.$control.draggable({
				containment:"parent", 
				axis:"x", 
				drag: $.proxy(this.onDrag.debounce(this.options.resolution), this),
				stop: $.proxy(this.onDragStop, this)
			});
			//add timeline entries
			for(var R = this.options.slideCount - 1, i = R; i >= 0; i--) {
				this.timelineMarks.push(parseFloat(i/this.options.slideCount).toFixed(3));
			}
			this.attachEvents();
			this.activateSubscriptions();
		};
		prTimelineControl.prototype = {
			attachEvents: function(e) {
			},
			activateSubscriptions: function() {
				tlBroker.subscribe("slideshow-move-to-slide", this, "moveToPercent");
			},
			onDrag: function(e, ui) {
				var actualPoint = ui.position.left;
				var percentagePoint = parseFloat(actualPoint/this.lineWidth).toFixed(3);
				tlBroker.publish("control-pos-change", {actual:actualPoint, percent:percentagePoint});
			},
			onDragStop: function(e, ui) {
				var actualPoint = ui.position.left;
				var percentagePoint = this.findNearestSlideMark(parseFloat(actualPoint/this.lineWidth).toFixed(3));
				ui.helper.animate({left:this.lineWidth*percentagePoint}, 150, "easeOutExpo");
				tlBroker.publish("control-pos-tomark", {actual:actualPoint, percent:percentagePoint});
			},
			moveToPercent: function(data) {
				this.$control
				.animate({left:(data.percent[1]*this.lineWidth)}, 550, "easeOutExpo");
			},
			findNearestSlideMark: function(testMark) {
				var cand = 0, diff = 1.0, refMark;
				for(var R = this.timelineMarks.length, i = R; i > 0; i--) {
					refMark = this.timelineMarks[i];
					if(Math.abs(testMark - refMark) < diff) {
						diff = Math.abs(testMark - refMark);
						cand = refMark;
					}
				}
				return cand;
			}
		};
		
		var init = function(initOptions) {
			return this.each(function(){
				$this = $(this);
				var obj = $this.data().prTimelineControlObj
				if(typeof(obj) === "object") {
					return obj;
				} else {
					obj = new prTimelineControl(this, initOptions);
					$this.data("prTimelineControlObj", obj);
					return obj;
				}
			});
		};
		
		var methods = {
			
		};
		
		// Method calling logic
		if ( methods[method] ) {
		  return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
		  return init.apply( this, arguments );
		} else {
		  $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
		}    
	}
	
	$(function(){
		var slideCount = 6;
		$(".timeline").prTimeline();
		$(".shuttle").prTimelineControl({slideCount:slideCount});
		
		//nav elms
		var $left = $(".timelineNav.left").data({prIndex:-1}).hide();
		var $right = $(".timelineNav.right").data({prIndex:1});
		$axisItems = $(".axisItem");
		$shuttleMarks = $(".shuttleMark");
		
		//nav methods
		var navControlMethods = {
			updateArrowData: function(f) {
				var dGo = f.frame -1;
				var iGo = f.frame +1;
				$left[(dGo < 0) ? "hide" : "show" ]().data("prIndex", dGo);
				$right[(iGo == slideCount) ? "hide" : "show"]().data("prIndex", iGo);
			},
			updateTimeAxisItem: function(f) {
				this.updateNavContainer(f.frame, $axisItems);
			},
			updateShuttleMark: function(f) {
				this.updateNavContainer(f.frame, $shuttleMarks);
			},
			updateNavContainer: function(frame, collection) {
				$this = $(collection[frame]);
				collection.removeClass("active");
				$this.addClass("active");
			}
		};
		
		//arrow nav
		$.fn.noop = $.noop;
		$("a", ".timelineNav").bind("click", function(e){
			e.preventDefault();
			var $this = $(this);
			var go = null;
			go = $this.parent().data().prIndex;
			if(go < -1 || go > slideCount) {
				return;
			}
			var f = {frame:go};
			navControlMethods.updateArrowData(f);
			tlBroker.publish("control-move-to-slide", f);
		});
		tlBroker.subscribe("slideshow-move-to-slide", navControlMethods, "updateArrowData");
		
		//box nav
		$axisItems.each(function(i, v){
			var d = {tlFrameNumber:i};
			$($shuttleMarks[i]).data(d);
			$(this).data(d);
		});
		function publishIndexChange(e) {
			e.preventDefault();
			var $this = $(this);
			var f = {frame:$this.data().tlFrameNumber};
			navControlMethods.updateTimeAxisItem(f);
			tlBroker.publish("control-move-to-slide", f);
		}
		$(".timeAxis").delegate("div.axisItem", "click", publishIndexChange);
		var addActive = function(e) {
				$(this).toggleClass("hover");
		}
		$("div.axisItem").hover(addActive);
		$("div.timelineNav").hover(addActive);
		$(".shuttle").delegate("span.shuttleMark", "click", publishIndexChange);
		tlBroker.subscribe("slideshow-move-to-slide", navControlMethods, "updateTimeAxisItem");
		tlBroker.subscribe("slideshow-move-to-slide", navControlMethods, "updateShuttleMark");
	});
})(jQuery, this);

