/* Creates a proxy for the basket with the specified name.
 * @param	String	name	Specifies the basket name.
 * @param	Object	options	Optional parameter that specifies overrides for default settings
 */
var basket = function(name, options) {

	var that = {},
		defaultSettings = { serviceUrl: '/toon/basketservice.json' },
		settings = $.extend(defaultSettings, options);

	// validate parameters
	if ("undefined" === typeof(name) || "" === name) {
		throw new Error("The name of the cookie cannot be null or empty.");
	}

	/* Gets the datatype of the service, based on the extension of the path.
	 */
	function getDataType() {
		var url = settings.serviceUrl;
		return url.substring(url.lastIndexOf(".") +1, url.length);
	}

	/* Performs an AJAX request to interact with the server-side basket.
	 * @param	Function	callback	Optional parameter that specifies an AJAX success callback.
	 * @param	String		action		Optional parameter that specifies the basket action: add, delete or clear.
	 * @param	Number		productID	Optional parameter that specifies the product ID. 
	 */
	function call(callback, action, productID) {
		// assemble querystring parameters
		var data = { basket: name };
		if ("undefined" !== typeof action) {
			data.action = action;
		}
		if ("undefined" !== typeof productID) {
			data.productid = productID;
		}

		$.ajax({
			type: "GET",
			url: settings.serviceUrl,
			data: data,
			dataType: getDataType(),
			success: callback
		});
	}
	
	/* Adds a product to the basket.
	 * @param	productID	Specifies the product ID.
	 * @param	Function	callback	Optional parameter that specifies the success callback.
	 */
	that.add = function(productID, callback) {
		return call(callback, "add", productID);
	};

	/* Removes a product from the basket.
	 * @param	productID	Specifies the product ID.
	 * @param	Function	callback	Optional parameter that specifies the success callback.
	 */
	that.remove = function(productID, callback) {
		return call(callback, "delete", productID);
	};
	
	/* Removes all products from the basket.
	 * @param	Function	callback	Optional parameter that specifies the success callback.
	 */
	that.clear = function(callback) {
		return call(callback, "clear");
	};
	
	/* Loads the basket without executing any action.
	 * @param	Function	callback	Optional parameter that specifies the success callback.
	 */
	that.load = function(callback) {
		return call(callback);
	};

	return that;
};

/* Creates a UI handler for the comparison basket. The comparison basket is initialized automatically on page load.
 */
var comparisonBasket = (function() {

	var that = {};
	$(document).ready(function() {

		var products = basket("compare"),
			elements = {
				btnCompare: $("#basket-options a.compare"),		// show comparison button
				btnAdd: $("#product-options a.compare, #pricebar a.compare"),		// add product to basket button
				productBasketContainer: $("#savedTripspopup")	// saved trips popup container
			},
			settings = {
				errorInsufficientProducts: "Er moeten tenminste twee producten zijn geselecteerd om te vergelijken."
			},
			productFinder = /\bproduct_(\d+)\b/;

		/* Updates the display of the number of items in the UI.
		 * @param	Number	count	Specifies the number of items in the basket.
		 */
		function updateProductCounter(count) {
			if (count < 2) {
				elements.btnCompare.removeClass("checked");
			} else {
				elements.btnCompare.addClass("checked");
			}
			$("span", elements.btnCompare).text("[" + count + "]");
		}

		/* Gets the number of products in the basket.
		 * @return	Number	The number of products in the basket.
		 */
		function getProductCount() {
			var text = $("span", elements.btnCompare).text();
			return Number(/\[(\d+)\]/.exec(text)[1]);
		}

		/* Callback function that is called when the contents of the basket have changed.
		 * @param	Object	obj	Specifies the server JSON-response.
		 */	 
		function onBasketUpdate(obj) {
			updateProductCounter(obj.count);
		}
		
		/* Creates a HTML update delegate that calls the optional callback if specified.
		 * @param	Function	callback	Optional function that is called when the asynchronous operation is finished.
		 */
		function createUpdateCallback(callback) {
			return function(obj) {
				onBasketUpdate(obj);
				if (!!callback) {				
					callback();
				}
			};
		}
		
		/* Checks if enough items are selected to start the comparison and redirects to the comparison page.
		 */
		that.compare = function() {
			var count = getProductCount();
			if (count < 2) {
				alert(settings.errorInsufficientProducts);
			} else {
				window.location.href = elements.btnCompare.attr("href");
			}
		};
		
		/* Adds a product to the basket.
		 * @param	Number		productID	Specifies the product ID.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.add = function(productID, callback) {
			products.add(productID, createUpdateCallback(callback));
		};
	
		/* Removes a product from the basket.
		 * @param	Number		productID	Specifies the product ID.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.remove = function(productID, callback) {
			products.remove(productID, createUpdateCallback(callback));
		};
	
		/* Removes all products from the basket.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.clear = function(callback) {
			products.clear(createUpdateCallback(callback));
		}
		
		// Bind UI eventhandlers
		elements.btnCompare.click(function() {
			that.compare();
			return false;
		});
		elements.btnAdd.click(function() {
			var className = $(this).attr("class"),
				isChecked = $(this).hasClass("checked"),
				productID = productFinder.exec(className)[1],
				buttons = $(this).add("a.compare.product_" + productID, elements.productBasketContainer).addClass("checked");
			
			// add or remove the product in the comparison basket
			if (!isChecked) {
				that.add(productID);
				buttons.addClass("checked");
			} else {
				that.remove(productID);
				buttons.removeClass("checked");
			}
			return false;
		});
		
	});
	return that;

})();

/* Creates a UI handler for the product basket. The product basket is initialized automatically on page load.
 */ 
var productBasket = (function() {

	var that = {};
	$(document).ready(function() {

		var	products = basket("saved", { 
				serviceUrl: '/toon/basket.html'			// override JSON service url
			}),
			isLoaded = false,
			isVisible = false,
			canToggleVisibility = true,
			settings = {
				speed: 150
			},
			elements = {
				container: $("#savedTripspopup"),											// basket container
				btnToggle: $("#basket-options a.saved"),									// show / hide basket button
				btnAdd: $("#product-options a.save, #compare a.save, #pricebar a.save"),	// add product to basket button
				btnCompare: $("#product-options a.compare, #pricebar a.compare")			// compare button for product that is possibly in basket
			},
			productFinder = /\bproduct_(\d+)\b/;
	
		/* Updates the display of the number of items in the UI.
		 */
		function updateProductCounter() {
			var count = $(".trip", elements.container).length;
			$("span", elements.btnToggle).text("[" + count + "]");
		}
	
		/* Callback function that is called when the contents of the basket have changed.
		 * @param	String	html	Specifies the new basket HTML.
		 */	 
		function onBasketUpdate(html) {
			unbindEventhandlers();
			html = html.replace(/ xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\"/g, "");
			elements.container.html(html);
			bindEventhandlers();
			updateProductCounter();
			isLoaded = true;
		}
	
		/* Callback function that is called when an error occurs.
		 */
		function onError() {
			var message = 'Helaas, het is op dit moment niet mogelijk de opgeslagen reizen op te vragen. Probeer het later nog eens';
			alert(message);
		}

		/* Creates a HTML update delegate that calls the optional callback if specified.
		 * @param	Function	callback	Optional function that is called when the asynchronous operation is finished.
		 */
		function createUpdateCallback(callback) {
			return function(html) {
				onBasketUpdate(html);
				if (!!callback) {				
					callback();
				}
			};
		}

		/* Binds the deletion and comparison eventhandlers. 
		 */
		function bindEventhandlers() {
			$(".options .delete", elements.container).bind("click", function() { 
				that.hide();
				that.clear();
				return false;
			});
			$(".options .compare", elements.container).bind("click", function() { 
				comparisonBasket.compare();
				return false;
			});
			$(".trip a.compare").bind("click", function() {
				var radio = $(this),
					className = radio.attr("class"),
					isChecked = radio.hasClass("checked"),
					productID = productFinder.exec(className)[1],
					buttons = elements.btnCompare.filter("a.compare.product_" + productID).add(radio);
				
				// add or remove the product in the comparison basket
				if (!isChecked) {
					comparisonBasket.add(productID);
					buttons.addClass("checked");
				} else {
					comparisonBasket.remove(productID);
					buttons.removeClass("checked");
				}
				return false;
			});
			$(".trip a.delete").bind("click", function() {
				var className = $(this).attr("class");
				var productID = productFinder.exec(className)[1];
				that.remove(productID);
				return false;
			});
		}

		/* Unbinds the deletion and comparison eventhandlers. 
		 */
		function unbindEventhandlers() {
			$(".options a, .trip a.delete", elements.container).unbind("click");
		}
		
		/* Shows the product basket. Showing is only possible if the basket is hidden and no other action is executing.
		 * If necessary, the product basket is loaded before displaying it.
		 * @param	Function	callback	Optional function that is called when the basket is cleared.
		 */
		that.show = function(callback) {

			/* Shows the product basket. Showing is only possible if the basket is hidden and no other action is executing.
			 */
			function internalShow() {
				if (!isVisible && canToggleVisibility) {
					canToggleVisibility = false;
					elements.btnToggle.addClass("open");
					elements.container.slideDown(settings.speed, function(){
						canToggleVisibility = true;
						isVisible = true;
						if (typeof callback === "function") {
							callback();
						}
					});
				}
			}
			
			if (!isLoaded) {
				products.load(function(html) {
					onBasketUpdate(html);
					internalShow();
				});
			} else {
				internalShow();
			}
		};
		
		/* Hides the product basket. Hiding is only possible if the basket is displayed and no other action is executing.
		 * @param	Function	callback	Optional function that is called when the basket is cleared.
		 */
		that.hide = function(callback) {
			if (isVisible && canToggleVisibility) {
				canToggleVisibility = false;
				elements.container.slideUp(settings.speed, function(){
					canToggleVisibility = true;
					isVisible = false;
					elements.btnToggle.removeClass("open");
					if (typeof callback === "function") {
						callback();
					}
				});
			}
		};
		
		/* Toggles the visibility.
		 * @param	Function	callback	Optional function that is called when the basket is cleared.		
		 */
		that.toggle = function(callback) {
			if (isVisible) {
				that.hide(callback);
			} else {
				that.show(callback);
			}
		}

		/* Adds a product to the basket.
		 * @param	Number		productID	Specifies the product ID.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.add = function(productID, callback) {
			products.add(productID, createUpdateCallback(callback));
		};
	
		/* Removes a product from the basket.
		 * @param	Number		productID	Specifies the product ID.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.remove = function(productID, callback) {
			products.remove(productID, createUpdateCallback(callback));
		};
	
		/* Removes all products from the basket.
		 * @param	Function	callback	Optional function that is called when the operation is finished.
		 */
		that.clear = function(callback) {
			products.clear(createUpdateCallback(callback));
		}
		
		// Bind UI eventhandlers
		elements.btnToggle.click(function() { 
			that.toggle();
			return false;
		});
		elements.btnAdd.click(function() {
			var className = $(this).attr("class"),
				isChecked = $(this).hasClass("checked"),
				productID = productFinder.exec(className)[1];
				
			// add or remove the product in the comparison basket
			if (!isChecked) {
				that.add(productID);
				$(this).addClass("checked");
			} else {
				that.remove(productID);
				$(this).removeClass("checked");
			}
			return false;
		});
	});
	return that;

})();

/* Creates a UI handler for the recently view products basket. The product basket is initialized automatically 
 * on page load.
 */
var recentlyViewedBasket = (function() {

	var that = {};
	$(document).ready(function() {
	
		var products = basket("recent"),
			elements = {
				container: $("#watched_trips"),
				btnNext: $("#watched_trips a.next"),
				btnPrevious: $("#watched_trips a.previous"),
				btnClear: $("#watched_trips a.delete"),
				scroller: $("#watched_trips .scroller"),
				cells: $("#watched_trips .scroller td")
			},
			position = 0;	// position of the scroller

		/* Gets if a next item exists to scroll to.
		 * @return	Boolean	True, if a next item exists; false, otherwise.
		 */
		function hasNext() {
			return position < elements.cells.length -4;
		}
		
		/* Gets if a previous item exists to scroll to.
		 * @return	Boolean	True, if a previous item exists; false, otherwise.
		 */
		function hasPrevious() {
			return 0 < position;
		}

		/* Updates the UI to reflect the position of the scroller.
		 * Buttons for navigation are hidden/displayed accordingly.
		 */
		function updateScroller() {
			// update position of scroller
			var offset = new String(- elements.cells.eq(position)[0].offsetLeft) + "px";
			elements.scroller.css("margin-left", offset);
			
			// hide next button if necessary
			if (hasNext()) {
				elements.btnNext.css("visibility", "visible");
			} else {
				elements.btnNext.css("visibility", "hidden");
			}
			
			// hide previous button if necessary
			if (hasPrevious()) {
				elements.btnPrevious.css("visibility", "visible");
			} else {
				elements.btnPrevious.css("visibility", "hidden");
			}
		}

		/* Scrolls to the next recently viewed product.
		 */
		that.next = function() {
			if (hasNext()) {
				position = position +1;
				updateScroller();
			}
		};
		
		/* Scrolls to the previous recently viewed product.
		 */
		that.previous = function() {
			if (hasPrevious()) {
				position = position -1;
				updateScroller();
			}
		};
		
		/* Removes all products from the basket.
		 */
		that.clear = function() {
			products.clear(function() {
				elements.container.hide();
			});
		};

		// Bind UI eventhandlers
		elements.btnNext.click(function() { 
			that.next();
			return false;
		});
		elements.btnPrevious.click(function() { 
			that.previous();
			return false;
		});
		elements.btnClear.click(function() { 
			that.clear();
			return false;
		});
		
		// show next / previous buttons
		if (elements.container.length != 0) {
			updateScroller();
		}
	});
	return that;
})();