/*
 * jQuery SmoothDivScroll 1.3
 *
 * Copyright (c) 2013 Thomas Kahn
 * Licensed under the GPL license.
 *
 * http://www.smoothdivscroll.com/
 *
 * Depends:
 * jquery-1.10.2.min.js
   Please use https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
   ...or later

 * jquery-ui-1.10.3.custom.min
   Make your own custom download at http://jqueryui.com/download.
   First deselect all components. Then check just "Widget" and "Effects Core".
   Download the file and put it in your javascript folder.

 * jquery.mousewheel.min.js
   Used for mousewheel functionality.
   Download the latest version at https://github.com/brandonaaron/jquery-mousewheel
 *

 * jquery.kinetic.min.js
   Used for scrolling by dragging, mainly used on touch devices.
   Download the latest version at https://github.com/davetayls/jquery.kinetic
 *
 */
(function ($) {

	$.widget("thomaskahn.smoothDivScroll", {
		// Default options
		options: {
			// Classes for elements added by Smooth Div Scroll
			scrollingHotSpotLeftClass: "scrollingHotSpotLeft", // String
			scrollingHotSpotRightClass: "scrollingHotSpotRight", // String
			scrollingHotSpotLeftVisibleClass: "scrollingHotSpotLeftVisible", // String
			scrollingHotSpotRightVisibleClass: "scrollingHotSpotRightVisible", // String
			scrollableAreaClass: "scrollableArea", // String
			scrollWrapperClass: "scrollWrapper", // String

			// Misc settings
			hiddenOnStart: false, // Boolean
			getContentOnLoad: {}, // Object
			countOnlyClass: "", // String
			startAtElementId: "", // String

			// Hotspot scrolling
			hotSpotScrolling: true, // Boolean
			hotSpotScrollingStep: 15, // Pixels
			hotSpotScrollingInterval: 10, // Milliseconds
			hotSpotMouseDownSpeedBooster: 3, // Integer
			visibleHotSpotBackgrounds: "hover", // always, onStart, hover or empty (no visible hotspots)
			hotSpotsVisibleTime: 5000, // Milliseconds
			easingAfterHotSpotScrolling: true, // Boolean
			easingAfterHotSpotScrollingDistance: 10, // Pixels
			easingAfterHotSpotScrollingDuration: 300, // Milliseconds
			easingAfterHotSpotScrollingFunction: "easeOutQuart", // String

			// Mousewheel scrolling
			mousewheelScrolling: "", // vertical, horizontal, allDirections or empty (no mousewheel scrolling) String
			mousewheelScrollingStep: 70, // Pixels
			easingAfterMouseWheelScrolling: true, // Boolean
			easingAfterMouseWheelScrollingDuration: 300, // Milliseconds
			easingAfterMouseWheelScrollingFunction: "easeOutQuart", // String

			// Manual scrolling (hotspot and/or mousewheel scrolling)
			manualContinuousScrolling: false, // Boolean

			// Autoscrolling
			autoScrollingMode: "", // always, onStart or empty (no auto scrolling) String
			autoScrollingDirection: "endlessLoopRight", // right, left, backAndForth, endlessLoopRight, endlessLoopLeft String
			autoScrollingStep: 1, // Pixels
			autoScrollingInterval: 10, // Milliseconds

			// Touch scrolling
			touchScrolling: false,

			// Easing for when the scrollToElement method is used
			scrollToAnimationDuration: 1000, // Milliseconds
			scrollToEasingFunction: "easeOutQuart" // String
		},
		_create: function () {
			var self = this, o = this.options, el = this.element;

			// Create variables for any existing or not existing 
			// scroller elements on the page.
			el.data("scrollWrapper", el.find("." + o.scrollWrapperClass));
			el.data("scrollingHotSpotRight", el.find("." + o.scrollingHotSpotRightClass));
			el.data("scrollingHotSpotLeft", el.find("." + o.scrollingHotSpotLeftClass));
			el.data("scrollableArea", el.find("." + o.scrollableAreaClass));

			// Check which elements are already present on the page. 
			// Create any elements needed by the plugin if
			// the user hasn't already created them.

			// First detach any present hot spots
			if (el.data("scrollingHotSpotRight").length > 0) {

				el.data("scrollingHotSpotRight").detach();
			}
			if (el.data("scrollingHotSpotLeft").length > 0) {

				el.data("scrollingHotSpotLeft").detach();
			}

			// Both the scrollable area and the wrapper are missing
			if (el.data("scrollableArea").length === 0 && el.data("scrollWrapper").length === 0) {
				el.wrapInner("<div class='" + o.scrollableAreaClass + "'>").wrapInner("<div class='" + o.scrollWrapperClass + "'>");

				el.data("scrollWrapper", el.find("." + o.scrollWrapperClass));
				el.data("scrollableArea", el.find("." + o.scrollableAreaClass));
			}
			// Only the wrapper is missing
			else if (el.data("scrollWrapper").length === 0) {
				el.wrapInner("<div class='" + o.scrollWrapperClass + "'>");
				el.data("scrollWrapper", el.find("." + o.scrollWrapperClass));
			}
			// Only the scrollable area is missing
			else if (el.data("scrollableArea").length === 0) {
				el.data("scrollWrapper").wrapInner("<div class='" + o.scrollableAreaClass + "'>");
				el.data("scrollableArea", el.find("." + o.scrollableAreaClass));
			}

			// Put the right and left hot spot back into the scroller again
			// or create them if they where not present from the beginning.
			if (el.data("scrollingHotSpotRight").length === 0) {
				el.prepend("<div class='" + o.scrollingHotSpotRightClass + "'></div>");
				el.data("scrollingHotSpotRight", el.find("." + o.scrollingHotSpotRightClass));
			} else {
				el.prepend(el.data("scrollingHotSpotRight"));
			}

			if (el.data("scrollingHotSpotLeft").length === 0) {
				el.prepend("<div class='" + o.scrollingHotSpotLeftClass + "'></div>");
				el.data("scrollingHotSpotLeft", el.find("." + o.scrollingHotSpotLeftClass));
			} else {
				el.prepend(el.data("scrollingHotSpotLeft"));
			}


			// Create variables in the element data storage
			el.data("speedBooster", 1);
			el.data("scrollXPos", 0);
			el.data("hotSpotWidth", el.data("scrollingHotSpotLeft").innerWidth());
			el.data("scrollableAreaWidth", 0);
			el.data("startingPosition", 0);
			el.data("rightScrollingInterval", null);
			el.data("leftScrollingInterval", null);
			el.data("autoScrollingInterval", null);
			el.data("hideHotSpotBackgroundsInterval", null);
			el.data("previousScrollLeft", 0);
			el.data("pingPongDirection", "right");
			el.data("getNextElementWidth", true);
			el.data("swapAt", null);
			el.data("startAtElementHasNotPassed", true);
			el.data("swappedElement", null);
			el.data("originalElements", el.data("scrollableArea").children(o.countOnlyClass));
			el.data("visible", true);
			el.data("enabled", true);
			el.data("scrollableAreaHeight", el.data("scrollableArea").height());
			el.data("scrollerOffset", el.offset());

			/*****************************************
			SET UP EVENTS FOR TOUCH SCROLLING
			*****************************************/
			if (o.touchScrolling && el.data("enabled")) {
				// Use jquery.kinetic.js for touch scrolling
				// Vertical scrolling disabled
				el.data("scrollWrapper").kinetic({
					y: false,
					moved: function (settings) {
						if (o.manualContinuousScrolling) {
							if (el.data("scrollWrapper").scrollLeft() <= 0) {
								self._checkContinuousSwapLeft();
							} else {
								self._checkContinuousSwapRight();
							}
						}
						
						// Callback
						self._trigger("touchMoved");
					},
					stopped: function (settings) {
						// Stop any ongoing animations
						el.data("scrollWrapper").stop(true, false);

						// Stop any ongoing auto scrolling
						self.stopAutoScrolling();
						
						// Callback
						self._trigger("touchStopped");
					}
				});
			}

			/*****************************************
			SET UP EVENTS FOR SCROLLING RIGHT
			*****************************************/
			// Check the mouse X position and calculate 
			// the relative X position inside the right hotspot
			el.data("scrollingHotSpotRight").bind("mousemove", function (e) {
				if (o.hotSpotScrolling) {
					var x = e.pageX - $(this).offset().left;
					el.data("scrollXPos", Math.round((x / el.data("hotSpotWidth")) * o.hotSpotScrollingStep));

					// If the position is less then 1, it's set to 1
					if (el.data("scrollXPos") === Infinity || el.data("scrollXPos") < 1) {
						el.data("scrollXPos", 1);
					}
				}
			});

			// Mouseover right hotspot - scrolling
			el.data("scrollingHotSpotRight").bind("mouseover", function () {
				if (o.hotSpotScrolling) {
					// Stop any ongoing animations
					el.data("scrollWrapper").stop(true, false);

					// Stop any ongoing auto scrolling
					self.stopAutoScrolling();

					// Start the scrolling interval
					el.data("rightScrollingInterval", setInterval(function () {
						if (el.data("scrollXPos") > 0 && el.data("enabled")) {
							el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + (el.data("scrollXPos") * el.data("speedBooster")));

							if (o.manualContinuousScrolling) {
								self._checkContinuousSwapRight();
							}

							self._showHideHotSpots();
						}
					}, o.hotSpotScrollingInterval));

					// Callback
					self._trigger("mouseOverRightHotSpot");
				}
			});

			// Mouseout right hotspot - stop scrolling
			el.data("scrollingHotSpotRight").bind("mouseout", function () {
				if (o.hotSpotScrolling) {
					clearInterval(el.data("rightScrollingInterval"));
					el.data("scrollXPos", 0);

					// Easing out after scrolling
					if (o.easingAfterHotSpotScrolling && el.data("enabled")) {
						el.data("scrollWrapper").animate({ scrollLeft: el.data("scrollWrapper").scrollLeft() + o.easingAfterHotSpotScrollingDistance }, { duration: o.easingAfterHotSpotScrollingDuration, easing: o.easingAfterHotSpotScrollingFunction });
					}
				}
			});


			// mousedown right hotspot (add scrolling speed booster)
			el.data("scrollingHotSpotRight").bind("mousedown", function () {
				el.data("speedBooster", o.hotSpotMouseDownSpeedBooster);
			});

			// mouseup anywhere (stop boosting the scrolling speed)
			$("body").bind("mouseup", function () {
				el.data("speedBooster", 1);
			});

			/*****************************************
			SET UP EVENTS FOR SCROLLING LEFT
			*****************************************/
			// Check the mouse X position and calculate
			// the relative X position inside the left hotspot
			el.data("scrollingHotSpotLeft").bind("mousemove", function (e) {
				if (o.hotSpotScrolling) {
					var x = el.data("hotSpotWidth") - (e.pageX - $(this).offset().left);

					el.data("scrollXPos", Math.round((x / el.data("hotSpotWidth")) * o.hotSpotScrollingStep));

					// If the position is less then 1, it's set to 1
					if (el.data("scrollXPos") === Infinity || el.data("scrollXPos") < 1) {
						el.data("scrollXPos", 1);
					}
				}
			});

			// Mouseover left hotspot
			el.data("scrollingHotSpotLeft").bind("mouseover", function () {
				if (o.hotSpotScrolling) {
					// Stop any ongoing animations
					el.data("scrollWrapper").stop(true, false);

					// Stop any ongoing auto scrolling
					self.stopAutoScrolling();

					el.data("leftScrollingInterval", setInterval(function () {
						if (el.data("scrollXPos") > 0 && el.data("enabled")) {
							el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() - (el.data("scrollXPos") * el.data("speedBooster")));

							if (o.manualContinuousScrolling) {
								self._checkContinuousSwapLeft();
							}

							self._showHideHotSpots();
						}
					}, o.hotSpotScrollingInterval));

					// Callback
					self._trigger("mouseOverLeftHotSpot");
				}
			});

			// mouseout left hotspot
			el.data("scrollingHotSpotLeft").bind("mouseout", function () {
				if (o.hotSpotScrolling) {
					clearInterval(el.data("leftScrollingInterval"));
					el.data("scrollXPos", 0);

					// Easing out after scrolling
					if (o.easingAfterHotSpotScrolling && el.data("enabled")) {
						el.data("scrollWrapper").animate({ scrollLeft: el.data("scrollWrapper").scrollLeft() - o.easingAfterHotSpotScrollingDistance }, { duration: o.easingAfterHotSpotScrollingDuration, easing: o.easingAfterHotSpotScrollingFunction });
					}
				}
			});

			// mousedown left hotspot (add scrolling speed booster)
			el.data("scrollingHotSpotLeft").bind("mousedown", function () {
				el.data("speedBooster", o.hotSpotMouseDownSpeedBooster);
			});

			/*****************************************
			SET UP EVENT FOR MOUSEWHEEL SCROLLING
			*****************************************/
			el.data("scrollableArea").mousewheel(function (event, delta, deltaX, deltaY) {

				if (el.data("enabled") && o.mousewheelScrolling.length > 0) {
					var pixels;

					// Can be either positive or negative
					// Is multiplied/inverted by minus one since you want it to scroll 
					// left when moving the wheel down/right and right when moving the wheel up/left
					if (o.mousewheelScrolling === "vertical" && deltaY !== 0) {
						// Stop any ongoing auto scrolling if it's running
						self.stopAutoScrolling();
						event.preventDefault();
						pixels = Math.round((o.mousewheelScrollingStep * deltaY) * -1);
						self.move(pixels);
					} else if (o.mousewheelScrolling === "horizontal" && deltaX !== 0) {
						// Stop any ongoing auto scrolling if it's running
						self.stopAutoScrolling();
						event.preventDefault();
						pixels = Math.round((o.mousewheelScrollingStep * deltaX) * -1);
						self.move(pixels);
					} else if (o.mousewheelScrolling === "allDirections") {
						// Stop any ongoing auto scrolling if it's running
						self.stopAutoScrolling();
						event.preventDefault();
						pixels = Math.round((o.mousewheelScrollingStep * delta) * -1);
						self.move(pixels);
					}


				}
			});

			// Capture and disable mousewheel events when the pointer
			// is over any of the hotspots
			if (o.mousewheelScrolling) {
				el.data("scrollingHotSpotLeft").add(el.data("scrollingHotSpotRight")).mousewheel(function (event) {
					event.preventDefault();
				});
			}

			/*****************************************
			SET UP EVENT FOR RESIZING THE BROWSER WINDOW
			*****************************************/
			$(window).bind("resize", function () {
				self._showHideHotSpots();
				self._trigger("windowResized");
			});

			/*****************************************
			FETCHING CONTENT ON INITIALIZATION
			*****************************************/
			// If getContentOnLoad is present in the options, 
			// sort out the method and parameters and get the content

			if (!(jQuery.isEmptyObject(o.getContentOnLoad))) {
				self[o.getContentOnLoad.method](o.getContentOnLoad.content, o.getContentOnLoad.manipulationMethod, o.getContentOnLoad.addWhere, o.getContentOnLoad.filterTag);
			}

			// Should it be hidden on start?
			if (o.hiddenOnStart) {
				self.hide();
			}

			/*****************************************
			AUTOSCROLLING
			*****************************************/
			// The $(window).load event handler is used because the width of the elements are not calculated
			// properly until then, at least not in Google Chrome. The start of the auto scrolling and the
			// setting of the hotspot backgrounds is started here as well for the same reason. 
			// If the auto scrolling is not started in $(window).load, it won't start because it 
			// will interpret the scrollable areas as too short.
			$(window).load(function () {

				// If scroller is not hidden, recalculate the scrollable area
				if (!(o.hiddenOnStart)) {
					self.recalculateScrollableArea();
				}

				// Autoscrolling is active
				if ((o.autoScrollingMode.length > 0) && !(o.hiddenOnStart)) {
					self.startAutoScrolling();
				}

				// If the user wants to have visible hotspot backgrounds, 
				// here is where it's taken care of
				if (o.autoScrollingMode !== "always") {

					switch (o.visibleHotSpotBackgrounds) {
						case "always":
							self.showHotSpotBackgrounds();
							break;
						case "onStart":
							self.showHotSpotBackgrounds();
							el.data("hideHotSpotBackgroundsInterval", setTimeout(function () {
								self.hideHotSpotBackgrounds(250);
							}, o.hotSpotsVisibleTime));
							break;
						case "hover":
							el.mouseenter(function (event) {
								if (o.hotSpotScrolling) {
									event.stopPropagation();
									self.showHotSpotBackgrounds(250);
								}
							}).mouseleave(function (event) {
								if (o.hotSpotScrolling) {
									event.stopPropagation();
									self.hideHotSpotBackgrounds(250);
								}
							});
							break;
						default:
							break;
					}
				}

				self._showHideHotSpots();

				self._trigger("setupComplete");

			});

		},
		/**********************************************************
		_init 
		**********************************************************/
		// When the contents of the scrollable area is changed outside the widget,
		// the widget must be reinitilaized.
		// This code is run every time the widget is called without arguments
		 _init: function () {
			var self = this, el = this.element;
		 
			// Recalculate the total width of the elements inside the scrollable area
			self.recalculateScrollableArea();
		 
			// Determine which hotspots to show
			self._showHideHotSpots();
		
			// Trigger callback
			self._trigger("initializationComplete");
		},
		/**********************************************************
		Override _setOption and handle altered options
		**********************************************************/
		_setOption: function (key, value) {
			var self = this, o = this.options, el = this.element;

			// Update option
			o[key] = value;

			if (key === "hotSpotScrolling") {
				// Handler if the option hotSpotScrolling is altered
				if (value === true) {
					self._showHideHotSpots();
				} else {
					el.data("scrollingHotSpotLeft").hide();
					el.data("scrollingHotSpotRight").hide();
				}
			} else if (key === "autoScrollingStep" ||
			// Make sure that certain values are integers, otherwise
			// they will summon bad spirits in the plugin
				key === "easingAfterHotSpotScrollingDistance" ||
				key === "easingAfterHotSpotScrollingDuration" ||
				key === "easingAfterMouseWheelScrollingDuration") {
				o[key] = parseInt(value, 10);
			} else if (key === "autoScrollingInterval") {
				// Handler if the autoScrollingInterval is altered
				o[key] = parseInt(value, 10);
				self.startAutoScrolling();
			}

		},
		/**********************************************************
		Hotspot functions
		**********************************************************/
		showHotSpotBackgrounds: function (fadeSpeed) {

			// Alter the CSS (SmoothDivScroll.css) if you want to customize
			// the look'n'feel of the visible hotspots
			var self = this, el = this.element, o = this.options;


			// Fade in the hotspot backgrounds
			if (fadeSpeed !== undefined) {
				// Before the fade-in starts, we need to make sure the opacity is zero
				//el.data("scrollingHotSpotLeft").add(el.data("scrollingHotSpotRight")).css("opacity", "0.0");

				el.data("scrollingHotSpotLeft").addClass(o.scrollingHotSpotLeftVisibleClass);
				el.data("scrollingHotSpotRight").addClass(o.scrollingHotSpotRightVisibleClass);

				// Fade in the hotspots
				el.data("scrollingHotSpotLeft").add(el.data("scrollingHotSpotRight")).fadeTo(fadeSpeed, 0.35);
			}
			// Don't fade, just show them
			else {

				// The left hotspot
				el.data("scrollingHotSpotLeft").addClass(o.scrollingHotSpotLeftVisibleClass);
				el.data("scrollingHotSpotLeft").removeAttr("style");

				// The right hotspot
				el.data("scrollingHotSpotRight").addClass(o.scrollingHotSpotRightVisibleClass);
				el.data("scrollingHotSpotRight").removeAttr("style");
			}

			self._showHideHotSpots();

		},
		hideHotSpotBackgrounds: function (fadeSpeed) {
			var el = this.element, o = this.options;

			// Fade out the hotspot backgrounds
			if (fadeSpeed !== undefined) {

				// Fade out the left hotspot
				el.data("scrollingHotSpotLeft").fadeTo(fadeSpeed, 0.0, function () {
					el.data("scrollingHotSpotLeft").removeClass(o.scrollingHotSpotLeftVisibleClass);
				});

				// Fade out the right hotspot
				el.data("scrollingHotSpotRight").fadeTo(fadeSpeed, 0.0, function () {
					el.data("scrollingHotSpotRight").removeClass(o.scrollingHotSpotRightVisibleClass);
				});

			}
			// Don't fade, just hide them
			else {
				el.data("scrollingHotSpotLeft").removeClass(o.scrollingHotSpotLeftVisibleClass).removeAttr("style");
				el.data("scrollingHotSpotRight").removeClass(o.scrollingHotSpotRightVisibleClass).removeAttr("style");
			}

		},
		// Function for showing and hiding hotspots depending on the
		// offset of the scrolling
		_showHideHotSpots: function () {
			var self = this, el = this.element, o = this.options;

			// Hot spot scrolling is not enabled so show no hot spots
			if (!(o.hotSpotScrolling)) {
				el.data("scrollingHotSpotLeft").hide();
				el.data("scrollingHotSpotRight").hide();
			} else {

				// If the manual continuous scrolling option is set show both
				if (o.hotSpotScrolling && o.autoScrollingMode !== "always" && el.data("autoScrollingInterval") !== null) {
					el.data("scrollingHotSpotLeft").show();
					el.data("scrollingHotSpotRight").show();
				}
				// Autoscrolling not set to always and hotspot scrolling enabled.
				// Regular hot spot scrolling.
				else if (o.autoScrollingMode !== "always" && o.hotSpotScrolling) {
					// If the scrollable area is shorter than the scroll wrapper, both hotspots
					// should be hidden
					if (el.data("scrollableAreaWidth") <= (el.data("scrollWrapper").innerWidth())) {
						el.data("scrollingHotSpotLeft").hide();
						el.data("scrollingHotSpotRight").hide();
					}
					// When you can't scroll further left the left scroll hotspot should be hidden
					// and the right hotspot visible.
					else if (el.data("scrollWrapper").scrollLeft() === 0) {
						el.data("scrollingHotSpotLeft").hide();
						el.data("scrollingHotSpotRight").show();
						// Callback
						self._trigger("scrollerLeftLimitReached");
						// Clear interval
						clearInterval(el.data("leftScrollingInterval"));
						el.data("leftScrollingInterval", null);
					}
					// When you can't scroll further right
					// the right scroll hotspot should be hidden
					// and the left hotspot visible
					else if (el.data("scrollableAreaWidth") <= (el.data("scrollWrapper").innerWidth() + el.data("scrollWrapper").scrollLeft())) {
						el.data("scrollingHotSpotLeft").show();
						el.data("scrollingHotSpotRight").hide();
						// Callback
						self._trigger("scrollerRightLimitReached");
						// Clear interval
						clearInterval(el.data("rightScrollingInterval"));
						el.data("rightScrollingInterval", null);
					}
					// If you are somewhere in the middle of your
					// scrolling, both hotspots should be visible
					else {
						el.data("scrollingHotSpotLeft").show();
						el.data("scrollingHotSpotRight").show();
					}
				}
				// If auto scrolling is set to always, there should be no hotspots
				else {
					el.data("scrollingHotSpotLeft").hide();
					el.data("scrollingHotSpotRight").hide();
				}
			}



		},
		// Function for calculating the scroll position of a certain element
		_setElementScrollPosition: function (method, element) {
			var el = this.element, o = this.options, tempScrollPosition = 0;

			switch (method) {
				case "first":
					el.data("scrollXPos", 0);
					return true;
				case "start":
					// Check to see if there is a specified start element in the options 
					// and that the element exists in the DOM
					if (o.startAtElementId !== "") {
						if (el.data("scrollableArea").has("#" + o.startAtElementId)) {
							tempScrollPosition = $("#" + o.startAtElementId).position().left;
							el.data("scrollXPos", tempScrollPosition);
							return true;
						}
					}
					return false;
				case "last":
					el.data("scrollXPos", (el.data("scrollableAreaWidth") - el.data("scrollWrapper").innerWidth()));
					return true;
				case "number":
					// Check to see that an element number is passed
					if (!(isNaN(element))) {
						tempScrollPosition = el.data("scrollableArea").children(o.countOnlyClass).eq(element - 1).position().left;
						el.data("scrollXPos", tempScrollPosition);
						return true;
					}
					return false;
				case "id":
					// Check that an element id is passed and that the element exists in the DOM
					if (element.length > 0) {
						if (el.data("scrollableArea").has("#" + element)) {
							tempScrollPosition = $("#" + element).position().left;
							el.data("scrollXPos", tempScrollPosition);
							return true;
						}
					}
					return false;
				default:
					return false;
			}


		},
		/**********************************************************
		Jumping to a certain element
		**********************************************************/
		jumpToElement: function (jumpTo, element) {
			var self = this, el = this.element;

			// Check to see that the scroller is enabled
			if (el.data("enabled")) {
				// Get the position of the element to scroll to
				if (self._setElementScrollPosition(jumpTo, element)) {
					// Jump to the element
					el.data("scrollWrapper").scrollLeft(el.data("scrollXPos"));
					// Check the hotspots
					self._showHideHotSpots();
					// Trigger the right callback
					switch (jumpTo) {
						case "first":
							self._trigger("jumpedToFirstElement");
							break;
						case "start":
							self._trigger("jumpedToStartElement");
							break;
						case "last":
							self._trigger("jumpedToLastElement");
							break;
						case "number":
							self._trigger("jumpedToElementNumber", null, { "elementNumber": element });
							break;
						case "id":
							self._trigger("jumpedToElementId", null, { "elementId": element });
							break;
						default:
							break;
					}

				}
			}
		},
		/**********************************************************
		Scrolling to a certain element
		**********************************************************/
		scrollToElement: function (scrollTo, element) {
			var self = this, el = this.element, o = this.options, autoscrollingWasRunning = false;

			if (el.data("enabled")) {
				// Get the position of the element to scroll to
				if (self._setElementScrollPosition(scrollTo, element)) {
					// Stop any ongoing auto scrolling
					if (el.data("autoScrollingInterval") !== null) {
						self.stopAutoScrolling();
						autoscrollingWasRunning = true;
					}

					// Stop any other running animations
					// (clear queue but don't jump to the end)
					el.data("scrollWrapper").stop(true, false);

					// Do the scolling animation
					el.data("scrollWrapper").animate({
						scrollLeft: el.data("scrollXPos")
					}, { duration: o.scrollToAnimationDuration, easing: o.scrollToEasingFunction, complete: function () {
						// If auto scrolling was running before, start it again
						if (autoscrollingWasRunning) {
							self.startAutoScrolling();
						}

						self._showHideHotSpots();

						// Trigger the right callback
						switch (scrollTo) {
							case "first":
								self._trigger("scrolledToFirstElement");
								break;
							case "start":
								self._trigger("scrolledToStartElement");
								break;
							case "last":
								self._trigger("scrolledToLastElement");
								break;
							case "number":
								self._trigger("scrolledToElementNumber", null, { "elementNumber": element });
								break;
							case "id":
								self._trigger("scrolledToElementId", null, { "elementId": element });
								break;
							default:
								break;
						}
					}
					});
				}
			}

		},
		move: function (pixels) {
			var self = this, el = this.element, o = this.options;
			// clear queue, move to end
			el.data("scrollWrapper").stop(true, true);

			// Only run this code if it's possible to scroll left or right,
			if ((pixels < 0 && el.data("scrollWrapper").scrollLeft() > 0) || (pixels > 0 && el.data("scrollableAreaWidth") > (el.data("scrollWrapper").innerWidth() + el.data("scrollWrapper").scrollLeft())) || o.manualContinuousScrolling) {

        var scrollLength = el.data("scrollableArea").width() - el.data("scrollWrapper").width();
        var sOffset = el.data("scrollWrapper").scrollLeft() + pixels;

        if(sOffset < 0) { // Swap last element to be the first one if scroll out of the left edge of view
                
            var forceSwapElementLeft = function(){
              el.data("swappedElement", el.data("scrollableArea").children(":last").detach());
              el.data("scrollableArea").prepend(el.data("swappedElement"));
              el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + el.data("swappedElement").outerWidth(true));              
            };
            
            while(sOffset < 0){ // keep swap elements left until it has enough length for scrolling left
              forceSwapElementLeft();
              sOffset = el.data("scrollableArea").children(":first").outerWidth(true) + sOffset;                   
            }

        } else if(sOffset - scrollLength > 0){ // Swap the first element to be the last one if scroll out of the right edge of view
           
          var forceSwapElementRight = function(){            
            el.data("swappedElement", el.data("scrollableArea").children(":first").detach());
            el.data("scrollableArea").append(el.data("swappedElement"));
            var wrapperLeft = el.data("scrollWrapper").scrollLeft();
            el.data("scrollWrapper").scrollLeft(wrapperLeft - el.data("swappedElement").outerWidth(true));            
          };
          
          while(sOffset - scrollLength > 0){ // keep swap elements right until it has enough length for scrolling right
            forceSwapElementRight();
            sOffset = sOffset - el.data("scrollableArea").children(":last").outerWidth(true);              
          }
          
        }
          
				if (o.easingAfterMouseWheelScrolling) {
        
          el.data("scrollWrapper").animate({ scrollLeft: el.data("scrollWrapper").scrollLeft() + pixels }, { duration: o.easingAfterMouseWheelScrollingDuration, easing: o.easingAfterMouseWheelFunction, complete: function () {
            self._showHideHotSpots();
            if (o.manualContinuousScrolling) {
              if (pixels > 0) {
                self._checkContinuousSwapRight();
              } else {
                self._checkContinuousSwapLeft();
              }
            }
          }
          });
          
				} else {
					el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + pixels);
					self._showHideHotSpots();

					if (o.manualContinuousScrolling) {
						if (pixels > 0) {
							self._checkContinuousSwapRight();
						} else {
							self._checkContinuousSwapLeft();
						}
					}
				}
         

			}

   },
		/**********************************************************
		Adding or replacing content
		**********************************************************/
		/*  Arguments are:
		content - a valid URL to a Flickr feed - string
		manipulationMethod - addFirst, addLast or replace (default) - string
		*/
		getFlickrContent: function (content, manipulationMethod) {
			var self = this, el = this.element;

			$.getJSON(content, function (data) {
				// small square - size is 75x75
				// thumbnail -> large - size is the longest side
				var flickrImageSizes = [{ size: "small square", pixels: 75, letter: "_s" },
										{ size: "thumbnail", pixels: 100, letter: "_t" },
										{ size: "small", pixels: 240, letter: "_m" },
										{ size: "medium", pixels: 500, letter: "" },
										{ size: "medium 640", pixels: 640, letter: "_z" },
										{ size: "large", pixels: 1024, letter: "_b"}];
				var loadedFlickrImages = [];
				var imageIdStringBuffer = [];
				var startingIndex;
				var numberOfFlickrItems = data.items.length;
				var loadedFlickrImagesCounter = 0;

				// Determine a plausible starting value for the
				// image height
				if (el.data("scrollableAreaHeight") <= 75) {
					startingIndex = 0;
				} else if (el.data("scrollableAreaHeight") <= 100) {
					startingIndex = 1;
				} else if (el.data("scrollableAreaHeight") <= 240) {
					startingIndex = 2;
				} else if (el.data("scrollableAreaHeight") <= 500) {
					startingIndex = 3;
				} else if (el.data("scrollableAreaHeight") <= 640) {
					startingIndex = 4;
				} else {
					startingIndex = 5;
				}

				// Put all items from the feed in an array.
				// This is necessary
				$.each(data.items, function (index, item) {
					loadFlickrImage(item, startingIndex);
				});

				function loadFlickrImage(item, sizeIndex) {
					var path = item.media.m;
					var imgSrc = path.replace("_m", flickrImageSizes[sizeIndex].letter);
					var tempImg = $("<img />").attr("src", imgSrc);

					tempImg.load(function () {
						// Is it still smaller? Load next size
						if (this.height < el.data("scrollableAreaHeight")) {
							// Load a bigger image, if possible
							if ((sizeIndex + 1) < flickrImageSizes.length) {
								loadFlickrImage(item, sizeIndex + 1);
							} else {
								addImageToLoadedImages(this);
							}
						}
						else {
							addImageToLoadedImages(this);
						}

						// Finishing stuff to do when all images have been loaded
						if (loadedFlickrImagesCounter === numberOfFlickrItems) {
							switch (manipulationMethod) {
								case "addFirst":
									// Add the loaded content first in the scrollable area
									el.data("scrollableArea").children(":first").before(loadedFlickrImages);
									break;
								case "addLast":
									// Add the loaded content last in the scrollable area
									el.data("scrollableArea").children(":last").after(loadedFlickrImages);
									break;
								default:
									// Replace the content in the scrollable area
									el.data("scrollableArea").html(loadedFlickrImages);
									break;
							}

							// Recalculate the total width of the elements inside the scrollable area
							self.recalculateScrollableArea();

							// Determine which hotspots to show
							self._showHideHotSpots();

							// Trigger callback
							self._trigger("addedFlickrContent", null, { "addedElementIds": imageIdStringBuffer });
						}

					});
				}

				// Add the loaded content first or last in the scrollable area
				function addImageToLoadedImages(imageObj) {
					// Calculate the scaled width
					var widthScalingFactor = el.data("scrollableAreaHeight") / imageObj.height;
					var tempWidth = Math.round(imageObj.width * widthScalingFactor);
					// Set an id for the image - the filename is used as an id
					var tempIdArr = $(imageObj).attr("src").split("/");
					var lastElemIndex = (tempIdArr.length - 1);
					tempIdArr = tempIdArr[lastElemIndex].split(".");
					$(imageObj).attr("id", tempIdArr[0]);
					// Set the height of the image to the height of the scrollable area and add the width
					$(imageObj).css({ "height": el.data("scrollableAreaHeight"), "width": tempWidth });
					// Add the id of the image to the array of id's - this
					// is used as a parameter when the callback is triggered
					imageIdStringBuffer.push(tempIdArr[0]);
					// Add the image to the array of loaded images
					loadedFlickrImages.push(imageObj);

					// Increment counter for loaded images
					loadedFlickrImagesCounter++;
				}

			});
		},
		/*  Arguments are:
		content - a valid URL to an AJAX content source - string
		manipulationMethod - addFirst, addLast or replace (default) - string
		filterTag - a jQuery selector that matches the elements from the AJAX content
		source that you want, for example ".myClass" or "#thisDiv" or "div" - string
		*/
		getAjaxContent: function (content, manipulationMethod, filterTag) {
			var self = this, el = this.element;
			$.ajaxSetup({ cache: false });

			$.get(content, function (data) {
				var filteredContent;

				if (filterTag !== undefined) {
					if (filterTag.length > 0) {
						// A bit of a hack since I can't know if the element
						// that the user wants is a direct child of body (= use filter)
						// or other types of elements (= use find)
						filteredContent = $("<div>").html(data).find(filterTag);
					} else {
						filteredContent = content;
					}
				} else {
					filteredContent = data;
				}

				switch (manipulationMethod) {
					case "addFirst":
						// Add the loaded content first in the scrollable area
						el.data("scrollableArea").children(":first").before(filteredContent);
						break;
					case "addLast":
						// Add the loaded content last in the scrollable area
						el.data("scrollableArea").children(":last").after(filteredContent);
						break;
					default:
						// Replace the content in the scrollable area
						el.data("scrollableArea").html(filteredContent);
						break;
				}

				// Recalculate the total width of the elements inside the scrollable area
				self.recalculateScrollableArea();

				// Determine which hotspots to show
				self._showHideHotSpots();

				// Trigger callback
				self._trigger("addedAjaxContent");

			});
		},
		getHtmlContent: function (content, manipulationMethod, filterTag) {
			var self = this, el = this.element;

			// No AJAX involved at all - just add raw HTML-content
			/* Arguments are:
			content - any raw HTML that you want - string
			manipulationMethod - addFirst, addLast or replace (default) - string
			filterTag - a jQuery selector that matches the elements from the AJAX content
			source that you want, for example ".myClass" or "#thisDiv" or "div" - string
			*/
			var filteredContent;
			if (filterTag !== undefined) {
				if (filterTag.length > 0) {
					// A bit of a hack since I can't know if the element
					// that the user wants is a direct child of body (= use filter)
					// or other types of elements (= use find)
					filteredContent = $("<div>").html(content).find(filterTag);
				} else {
					filteredContent = content;
				}
			} else {
				filteredContent = content;
			}

			switch (manipulationMethod) {
				case "addFirst":
					// Add the loaded content first in the scrollable area
					el.data("scrollableArea").children(":first").before(filteredContent);
					break;
				case "addLast":
					// Add the loaded content last in the scrollable area
					el.data("scrollableArea").children(":last").after(filteredContent);
					break;
				default:
					// Replace the content in the scrollable area
					el.data("scrollableArea").html(filteredContent);
					break;
			}

			// Recalculate the total width of the elements inside the scrollable area
			self.recalculateScrollableArea();
	
			// Determine which hotspots to show
			self._showHideHotSpots();

			// Trigger callback
			self._trigger("addedHtmlContent");

		},
		/**********************************************************
		Recalculate the scrollable area
		**********************************************************/
		recalculateScrollableArea: function () {

			var tempScrollableAreaWidth = 0, foundStartAtElement = false, o = this.options, el = this.element;

			// Add up the total width of all the items inside the scrollable area
			el.data("scrollableArea").children(o.countOnlyClass).each(function () {
				// Check to see if the current element in the loop is the one where the scrolling should start
				if ((o.startAtElementId.length > 0) && (($(this).attr("id")) === o.startAtElementId)) {
					el.data("startingPosition", tempScrollableAreaWidth);
					foundStartAtElement = true;
				}
				tempScrollableAreaWidth = tempScrollableAreaWidth + $(this).outerWidth(true);

			});

			// If the element with the ID specified by startAtElementId
			// is not found, reset it
			if (!(foundStartAtElement)) {
				el.data("startAtElementId", "");
			}

			// Set the width of the scrollable area
			el.data("scrollableAreaWidth", tempScrollableAreaWidth);
			el.data("scrollableArea").width(el.data("scrollableAreaWidth"));

			// Move to the starting position
			el.data("scrollWrapper").scrollLeft(el.data("startingPosition"));
			el.data("scrollXPos", el.data("startingPosition"));
		},
		/**********************************************************
		Get current scrolling left offset
		**********************************************************/
		getScrollerOffset: function () {
			var el = this.element;

			// Returns the current left offset
			// Please remember that if the scroller is in continuous
			// mode, the offset is not that relevant anymore since
			// the plugin will swap the elements inside the scroller
			// around and manipulate the offset in this process.
			return el.data("scrollWrapper").scrollLeft();
		},
		/**********************************************************
		Stopping, starting and doing the auto scrolling
		**********************************************************/
		stopAutoScrolling: function () {
			var self = this, el = this.element;

			if (el.data("autoScrollingInterval") !== null) {
				clearInterval(el.data("autoScrollingInterval"));
				el.data("autoScrollingInterval", null);

				// Check to see which hotspots should be active
				// in the position where the scroller has stopped
				self._showHideHotSpots();

				self._trigger("autoScrollingStopped");
			}
		},
		/**********************************************************
		Start Autoscrolling
		**********************************************************/
		startAutoScrolling: function () {
			var self = this, el = this.element, o = this.options;

			if (el.data("enabled")) {
				self._showHideHotSpots();

				// Stop any running interval
				clearInterval(el.data("autoScrollingInterval"));
				el.data("autoScrollingInterval", null);

				// Callback
				self._trigger("autoScrollingStarted");

				// Start interval
				el.data("autoScrollingInterval", setInterval(function () {

					// If the scroller is not visible or
					// if the scrollable area is shorter than the scroll wrapper
					// any running auto scroll interval should stop.
					if (!(el.data("visible")) || (el.data("scrollableAreaWidth") <= (el.data("scrollWrapper").innerWidth()))) {
						// Stop any running interval
						clearInterval(el.data("autoScrollingInterval"));
						el.data("autoScrollingInterval", null);

					}
					else {

						// Store the old scrollLeft value to see if the scrolling has reached the end
						el.data("previousScrollLeft", el.data("scrollWrapper").scrollLeft());

						switch (o.autoScrollingDirection) {
							case "right":

								el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + o.autoScrollingStep);
								if (el.data("previousScrollLeft") === el.data("scrollWrapper").scrollLeft()) {
									self._trigger("autoScrollingRightLimitReached");
									self.stopAutoScrolling();
								}
								break;

							case "left":
								el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() - o.autoScrollingStep);
								if (el.data("previousScrollLeft") === el.data("scrollWrapper").scrollLeft()) {
									self._trigger("autoScrollingLeftLimitReached");
									self.stopAutoScrolling();
								}
								break;

							case "backAndForth":
								if (el.data("pingPongDirection") === "right") {
									el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + (o.autoScrollingStep));
								}
								else {
									el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() - (o.autoScrollingStep));
								}

								// If the scrollLeft hasnt't changed it means that the scrolling has reached
								// the end and the direction should be switched
								if (el.data("previousScrollLeft") === el.data("scrollWrapper").scrollLeft()) {
									if (el.data("pingPongDirection") === "right") {
										el.data("pingPongDirection", "left");
										self._trigger("autoScrollingRightLimitReached");
									}
									else {
										el.data("pingPongDirection", "right");
										self._trigger("autoScrollingLeftLimitReached");
									}
								}
								break;

							case "endlessLoopRight":

								// Do the auto scrolling
								el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + o.autoScrollingStep);

								self._checkContinuousSwapRight();
								break;
							case "endlessLoopLeft":

								// Do the auto scrolling
								el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() - o.autoScrollingStep);

								self._checkContinuousSwapLeft();
								break;
							default:
								break;

						}
					}
				}, o.autoScrollingInterval));
			}
		},
		/**********************************************************
		Check Continuos Swap Right
		**********************************************************/
		_checkContinuousSwapRight: function () {
			var el = this.element, o = this.options;

			// Get the width of the first element. When it has scrolled out of view,
			// the element swapping should be executed. A true/false variable is used
			// as a flag variable so the swapAt value doesn't have to be recalculated
			// in each loop.  
			if (el.data("getNextElementWidth")) {

				if ((o.startAtElementId.length > 0) && (el.data("startAtElementHasNotPassed"))) {
					// If the user has set a certain element to start at, set swapAt 
					// to that element width. This happens once.
					el.data("swapAt", $("#" + o.startAtElementId).outerWidth(true));
					el.data("startAtElementHasNotPassed", false);
				}
				else {
					// Set swapAt to the first element in the scroller
					el.data("swapAt", el.data("scrollableArea").children(":first").outerWidth(true));
				}
				el.data("getNextElementWidth", false);
			}


			// Check to see if the swap should be done
			if (el.data("swapAt") <= el.data("scrollWrapper").scrollLeft()) {
				el.data("swappedElement", el.data("scrollableArea").children(":first").detach());
				el.data("scrollableArea").append(el.data("swappedElement"));
				var wrapperLeft = el.data("scrollWrapper").scrollLeft();
				el.data("scrollWrapper").scrollLeft(wrapperLeft - el.data("swappedElement").outerWidth(true));
				el.data("getNextElementWidth", true);
			}
		},
		/**********************************************************
		Check Continuos Swap Left
		**********************************************************/
		_checkContinuousSwapLeft: function () {
			var el = this.element, o = this.options;

			// Get the width of the first element. When it has scrolled out of view,
			// the element swapping should be executed. A true/false variable is used
			// as a flag variable so the swapAt value doesn't have to be recalculated
			// in each loop.

			if (el.data("getNextElementWidth")) {
				if ((o.startAtElementId.length > 0) && (el.data("startAtElementHasNotPassed"))) {
					el.data("swapAt", $("#" + o.startAtElementId).outerWidth(true));
					el.data("startAtElementHasNotPassed", false);
				}
				else {
					el.data("swapAt", el.data("scrollableArea").children(":first").outerWidth(true));
				}

				el.data("getNextElementWidth", false);
			}

			// Check to see if the swap should be done
			if (el.data("scrollWrapper").scrollLeft() === 0) {
       
				el.data("swappedElement", el.data("scrollableArea").children(":last").detach());
				el.data("scrollableArea").prepend(el.data("swappedElement"));
				el.data("scrollWrapper").scrollLeft(el.data("scrollWrapper").scrollLeft() + el.data("swappedElement").outerWidth(true));
				el.data("getNextElementWidth", true);
        
			}

		},
		restoreOriginalElements: function () {
			var self = this, el = this.element;

			// Restore the original content of the scrollable area
			el.data("scrollableArea").html(el.data("originalElements"));
			self.recalculateScrollableArea();
			self.jumpToElement("first");
		},
		show: function () {
			var el = this.element;
			el.data("visible", true);
			el.show();
		},
		hide: function () {
			var el = this.element;
			el.data("visible", false);
			el.hide();
		},
		enable: function () {
			var el = this.element;

			// Enable touch scrolling
			if (this.options.touchScrolling) {
				el.data("scrollWrapper").kinetic('attach');
			}

			// Set enabled to true
			el.data("enabled", true);
		},
		disable: function () {
			var self = this, el = this.element;

			// Clear all running intervals
			self.stopAutoScrolling();
			clearInterval(el.data("rightScrollingInterval"));
			clearInterval(el.data("leftScrollingInterval"));
			clearInterval(el.data("hideHotSpotBackgroundsInterval"));

			// Disable touch scrolling
			if (this.options.touchScrolling) {
				el.data("scrollWrapper").kinetic('detach');
			}

			// Set enabled to false
			el.data("enabled", false);
		},
		destroy: function () {
			var self = this, el = this.element;

			// Clear all running intervals
			self.stopAutoScrolling();
			clearInterval(el.data("rightScrollingInterval"));
			clearInterval(el.data("leftScrollingInterval"));
			clearInterval(el.data("hideHotSpotBackgroundsInterval"));

			// Remove all element specific events
			el.data("scrollingHotSpotRight").unbind("mouseover");
			el.data("scrollingHotSpotRight").unbind("mouseout");
			el.data("scrollingHotSpotRight").unbind("mousedown");

			el.data("scrollingHotSpotLeft").unbind("mouseover");
			el.data("scrollingHotSpotLeft").unbind("mouseout");
			el.data("scrollingHotSpotLeft").unbind("mousedown");

			el.unbind("mousenter");
			el.unbind("mouseleave");

			// Remove all elements created by the plugin
			el.data("scrollingHotSpotRight").remove();
			el.data("scrollingHotSpotLeft").remove();
			el.data("scrollableArea").remove();
			el.data("scrollWrapper").remove();

			// Restore the original content of the scrollable area
			el.html(el.data("originalElements"));

			// Call the base destroy function
			$.Widget.prototype.destroy.apply(this, arguments);

		}


	});
})(jQuery);
