// Define Util namespace for utility methods and classes.
Util = {};

// Parse the parameters in the URL of the current document and return them as a map.
Util.getUrlParameters = function() {
	var parts = document.location.search.substring(1).split("&");
	
	var params = [];
	for( var i = 0; i < parts.length; ++i ) {
		var p = parts[i].split("=");
		params[p[0]] = p[1];
	}
	
	return params;
}

// Convert an empty or all white-space string to null.
Util.nullIfEmpty = function(s) {
	if (s != null && s.strip().length == 0) {
		return null;
	}
	return s;
}

// Log a message to the log DIV.
Util.log = function(msg) {
	if (Util.debug) {
		if (Util.Logger.instance == null) {
			Util.Logger.instance = new Util.Logger();
		}
		Util.Logger.instance.log(msg);
	}
}
	
// Generic logger class. Use the Util.log() function to log messages to a log DIV that
// will automatically be shown on the first call to this method.
Util.Logger = function() {
	var container = document.createElement("div");
	container.style.position = "absolute";
	container.style.left = container.style.top = "0px";
	
	var html = "<div id='logger' style='" +
		"position: absolute; top: 30px; left: 730px; width: 500px; height: 500px; " +
		"overflow: auto; border: 1px solid black; color: black; background-color: white; " +
		"font-size: 10px'></div>";
		
	container.innerHTML = html;
	document.body.appendChild(container);
	this.logOutput = container.firstChild;
}

Util.Logger.prototype = {
	// Log a message to the log DIV.
	log: function(msg) {
		var textNode = document.createTextNode(new Date().formattedTime() + "| " + msg);
		this.logOutput.appendChild(textNode);
		
		var br = document.createElement("br");
		this.logOutput.appendChild(br);

		// Delay scrolling a bit since it slows down this method a lot.
		if (this.scrollTimer) {
			window.clearTimeout(this.scrollTimer);
		}
		this.scrollTimer = window.setTimeout(this.scrollToBottom.bind(this), 25);
	},
	
	// Scroll to bottom of log output.
	scrollToBottom: function() {
		var div = this.logOutput;
		div.scrollTop = div.scrollHeight;
	}
};

// Show a popup window with the given link, name and attributes.
// The attributes is a map where all the regular window.open() attributes can be given, in
// addition to an attribute "center", which if set to true, centers the popup over the visible
// part of the browser.	
Util.showPopup = function(link, name, attributes) {
	var attrString = Util.createAttributeString(attributes);
	common.log("Opening popup with attributes: " + attrString);
	var w = window.open(link, name, attrString);
	w.focus();
	
	if (attributes.closeOnEscape) {
		var self = this;
		window.setTimeout(function() {
			try {
				w.document.onkeydown = Util.handlePopupKeyDown.bindAsEventListener(Util, w);
			} catch( e ) {
				common.log("Error while accessing popup window: " + e.description);
			}
		}, 100);
	}
	
	return false;
};

// Createa a string suitable for the attributes argument of window.open().
Util.createAttributeString = function(map) {
	if (map.center) {
		var leftTop = Util.centerOnScreen(map.width, map.height);
		map.left = leftTop[0];
		map.top = leftTop[1];
		common.log("Popup position: " + map.left + ", " + map.top);
	}
	
	var names = ["left", "top", "width", "height", "resizable", "scrollbars", "status", 
				 "fullscreen", "titlebar", "toolbar", "menubar", "location"];

	var buf = [];
	for( var i = 0; i < names.length; ++i ) {
		var name = names[i];
		if (map[name] != null) {
			if (map[name] == true) {
				value = "yes";
			} else if (map[name] == false) {
				value = "no";
			} else {
				value = map[name];
			}
			buf.push(name + "=" + value);
		}
	}
	return buf.join(", ");
};

// Handle a keydown event in a popup.	
Util.handlePopupKeyDown = function(e, popupWindow) {
	e = e || popupWindow.event;
	if (e.keyCode == 27) {
		popupWindow.close();
	}
};

// Check if cookies are enabled.
Util.cookiesEnabled = function() {
	// Set cookie.
	var expires = new Date();
	expires.setTime(expires.getTime() + 10000);
	document.cookie = "CookieTest=42;expires=" + expires.toGMTString();
	
	// Read cookie.
	var cookies = String(document.cookie);
	return cookies.indexOf("CookieTest=") != -1;
}

// Move an absolute element by some delta x, y.
Util.moveElement = function(e, dx, dy) {
	var s = $(e).style;
	s.left = (parseInt(s.left) + dx) + "px";
	s.top = (parseInt(s.top) + dy) + "px";
}

// General center function. Centers the given rectangle within the visible browser area.
// Returns an array with two elements, the future left and top coordinates of the rectangle.
Util.centerOverVisible = function(width, height) {
	var size = Util.getWindowSize();
	var offsets = Util.getScrollOffsets();
	var left = (size[0] - Math.floor(width - 10)) / 2 + offsets[0];
	var top = Math.max(20, Math.floor((size[1] - height) / 2)) + offsets[1];
	return [left, top];
};

// Center function to center the given rectangle over the browser (useful for popups).
// Returns an array with two elements, the future left and top coordinates of the rectangle.
Util.centerOverWindow = function(width, height) {
	var location = Util.getScreenLocation();
	var size = Util.getWindowSize();
	var left = location[0] + Math.round((size[0] - width) / 2);
	var top = location[1] + Math.round((size[1] - height) / 2);
	return [left, top];
};

// Center function to center the given rectangle on the user's screen (useful for popups).
// Returns an array with two elements, the future left and top coordinates of the rectangle.
Util.centerOnScreen = function(width, height) {
	var screen = Util.getScreenSize();
	var left = Math.round((screen[0] - width) / 2);
	var top = Math.round((screen[1] - height) / 2);
	return [left, top];
};

// Get the location of the browser window on the user's screen.
// Note that for IE this returns the coords of the client area, not of the browser window.
Util.getScreenLocation = function() {
	if (window.screenLeft != null) {
		return [window.screenLeft, window.screenTop];
	} else {
		return [window.screenX, window.screenY];
	}
};

// Get the size of the user's screen.
// Returns an array of two elements, the width and height of the screen.
Util.getScreenSize = function() {
	return [window.screen.availWidth, window.screen.availHeight];
}

// Get the size of the current document body.
// Returns an array of two elements, the width and height of the complete document.
Util.getBodySize = function() {
	var width = 0, height = 0;
	width = document.documentElement.scrollWidth;
	height = document.documentElement.scrollHeight;
	return [width, height];
}

// Get the size of the current browser window.
// Returns an array of two elements, the width and height of the window in pixels.
Util.getWindowSize = function() {
	var width = 0, height = 0;
	if( typeof window.innerWidth == 'number') {
		// Non-IE
		width = window.innerWidth;
		height = window.innerHeight;

	} else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
		// IE 6+ in 'standards compliant mode'
		width = document.documentElement.clientWidth;
		height = document.documentElement.clientHeight;

	} else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
		// IE 4 compatible
		width = document.body.clientWidth;
		height = document.body.clientHeight;
	}
	return [width, height];
};

// Retrieve the scroll offsets of the window.
// Returns an array of two elements, where the first element contains the amount the
// window was scrolled horizontally, and the second the amount scrolled vertically.
Util.getScrollOffsets = function() {
	var offsets = [0, 0];
	
	if (window.pageXOffset) {
		offsets[0] = window.pageXOffset
	} else if (document.documentElement && document.documentElement.scrollLeft) {
		offsets[0] = document.documentElement.scrollLeft;
	} else if (document.body) {
		offsets[0] = document.body.scrollLeft;
	}
	
	if (window.pageYOffset) {
		offsets[1] = window.pageYOffset;
	} else if (document.documentElement && document.documentElement.scrollTop) {
		offsets[1] = document.documentElement.scrollTop;
	} else if (document.body) {
		offsets[1] = document.body.scrollTop;
	}
	
	return offsets;
}

Util._windowBlockCount = 0; // Keeps track of nested block/unblock calls.
Util._windowBlockerOpacity = 0;
Util._windowBlockerEnabled = true;

// Enable or disable the blocker.
Util.enableBlocker = function(enable) {
	Util._windowBlockerEnabled = enable;
}

// Sets the blocker opacity, a number between 0 (fully transparent) and 1 (fully opaque).
// Is used in the next call to Util.blockWindow().
Util.setBlockerOpacity = function(opacity) {
	common.log("Setting blocker opacity to: " + opacity);
	Util._windowBlockerOpacity = opacity;
}

// Create a DIV that can be used to block the entire window, preventing users
// from interacting with it.
Util.blockWindow = function() {
	if (!Util._windowBlockerEnabled) {
		return;
	}
	
	common.log("Block window (" + Util._windowBlockCount + ")");
	var blocker = Util._getWindowBlocker();
	Util.log("blockWindow: setting opacity: " + Util._windowBlockerOpacity);
	Element.setOpacity(blocker, Util._windowBlockerOpacity);
	// For IE, hide select elements, since they shine through overlayed DIVs.
	if (Element.getOpacity(blocker) > 0) {
		if (Prototype.Browser.IE) {
			var toHide = document.getElementsByTagName("SELECT");
			for( var i = 0; i < toHide.length; ++i ) {
				var e = toHide[i];
				if (Element.visible(e)) {
					e.style.display = "none";
					e.hiddenByBlocker = true;
				}
			}
		}
	}
	
	if (++Util._windowBlockCount == 1) {
		// Show blocker DIV.
		blocker.style.display = "";
		
		if (Prototype.Browser.IE) {
			// In IE, width/height=100% for the overlay does not work.
			var size = Util.getBodySize();
			var windowSize = Util.getWindowSize();
			blocker.style.width = Math.max(size[0], windowSize[0]) + "px";
			blocker.style.height =  Math.max(size[1], windowSize[1]) + "px";
			
			// If window is resized, updated blocker size/position.
			Util._windowResizeTimer = window.setInterval(Util._windowBlockerAdjuster, 100);	
			Event.observe(window, "resize", Util._windowBlockerResizer);
		}
	}
}

// Remove the DIV that blocks the window.
Util.unblockWindow = function() {
	if (!Util._windowBlockerEnabled) {
		return;
	}
	
	--Util._windowBlockCount;
	common.log("Unblock window (" + Util._windowBlockCount + ")");
	if (Util._windowBlockCount <= 0) {
		Util._windowBlockCount = 0;

		var blocker = Util._windowBlocker;
		if (blocker != null) {
			blocker.style.display = "none";
		}
		
		// For IE, show previously hidden select elements.
		if (Prototype.Browser.IE) {
			var toHide = document.getElementsByTagName("SELECT");
			for( var i = 0; i < toHide.length; ++i ) {
				var e = toHide[i];
				if (e.hiddenByBlocker) {
					e.style.display = "";
				}
			}
			
			// Stop scroll/resize event monitoring.
			window.clearInterval(Util._windowResizeTimer); 
			Util._windowResizeTimer = null;
			Event.stopObserving(window, "resize", Util._windowBlockerResizer);
		}
	}
}

Util._getWindowBlocker = function() {
	var blocker = Util._windowBlocker;
	if (blocker == null) {
		var blocker = document.createElement("DIV");
		blocker.id = "blockerDiv";
		blocker.style.display = "none";
		document.body.appendChild(blocker);
		Util._windowBlocker = blocker;
	}
	return blocker;
}

Util._windowBlockerResizer = function() {
	Util._windowDimensionChanged = true;
}

Util._windowBlockerAdjuster = function() {
	if (Util._windowDimensionChanged) {
		Util._windowDimensionChanged = false;
		
		var blocker = Util._windowBlocker;
		if (blocker != null) {
			var size = Util.getBodySize();
			var windowSize = Util.getWindowSize();
			blocker.style.width = Math.max(size[0], windowSize[0]) + "px";
			blocker.style.height =  Math.max(size[1], windowSize[1]) + "px";
		}
	}
}

// Parse a color string into its red, green and blue parts.
Util.parseColor = function(colorString) {
	if (colorString == null) {
		return {red: 0, green: 0, blue: 0};
	}
	
	if (colorString.length == 7) {
		return {
			red: parseInt(colorString.substr(1, 2), 16), 
			green: parseInt(colorString.substr(3, 2), 16),
			blue: parseInt(colorString.substr(5, 2), 16)
		};
		
	} else if (colorString.length == 4) {
		return {
			red: 15 + 16 * parseInt(colorString.substr(1, 1), 16), 
			green: 15 + 16 * parseInt(colorString.substr(2, 1), 16),
			blue: 15 + 16 * parseInt(colorString.substr(3, 1), 16)
		};
		
	} else if (colorString.toLowerCase().startsWith("rgb(")) {
		var re = new RegExp("(\\s*[\\d]+)\\s*,\\s*([\\d]+)\\s*,\\s+([\\d]+).*");
		re.exec(colorString.substring(4));
		return {red: parseInt(RegExp.$1), green: parseInt(RegExp.$2), blue: parseInt(RegExp.$3)};
	}
	
	return {red: 0, green: 0, blue: 0};
};

// Wrapper functions around the used history mechanism.
Util.initializeHistory = function() {
	Util._historyManager = historyManager; // Created in dhtmlHistory.js
};

Util.setHistoryListener = function(listener) {
	Util._historyManager.addEvent("onHistoryChange", listener);
};

Util.addHistoryState = function(state) {
	Util._historyManager.addState(state);
};

// Utility class to easily create DOM structures from code.
var DomBuilder = {
	initialize: function() {
		var elements = [
			"a", "span", "img", "div", "h1", "h2", "h3", "h4", "h5", "br", "p", "ul", "ol", "li", "select", "option",
			"table", "tbody", "thead", "tr", "td"
		];
		for( var i = 0; i < elements.length; ++i ) {
			(function() {
				var e = elements[i];
				DomBuilder[e] = function() { return this.add(document.createElement(e), arguments) };
			})();
		}
	},
	
	add: function(parent, children) {
		var n = children.length;
		for( var i = 0; i < n; ++i ) {
			var child = children[i];
			if (child == null) {
				continue;
			}
			
			if (child.nodeType != null) {
				// Must be a DOM element.
				parent.appendChild(child);
				
			} else if (typeof child == "string") {
				// Create a text node.
				parent.appendChild(document.createTextNode(child));
				
			} else {
				// Assume map with attributes for the parent element.
				for( var name in child ) {
					if (name == "style") {
						var style = child["style"];
						for( var prop in style ) {
							parent.style[prop] = style[prop];
						}
					} else {
						parent[name] = child[name];
					}
				}
			}
		}
		return parent;
	},
	
	text: function() {
		return document.createTextNode($A(arguments).join(""));

	},
	
	fragment: function() {
		return this.add(document.createDocumentFragment(), arguments);
	}
};

DomBuilder.initialize();

// Define Effects namespace.
Effects = {};
Effects.defaultHighlightColor = "#ffff37"; // Yellow.

/**
 * Highlight the given element, fading from the specified highlight color
 * to the element's background color.
 * @param {Object} element
 * @param {Object} highlightColor
 */
Effects.highlight = function(element, highlightColor) {
	element = $(element);
	if (element == null) {
		return false;
	}
	
	// If a highlighter is in progress, cancel it.
	if (element.highlightUpdater) {
		window.clearInterval(element.highlightUpdater.timer);
		Element.setStyle(element, { backgroundColor: element.highlightUpdater.endColor }, true);
		element.highlightUpdater = null;
	}
	
	element.highlightUpdater = {};
	
	var updater = element.highlightUpdater;
	updater.element = element;
	updater.endColor = Element.getStyle(element, "background-color");

	var seconds = 1;
	var delay = 50;
	var step = seconds * 1000 / delay;
	var startColor = highlightColor || Config.highlightColor || Effects.defaultHighlightColor;
	
	var start =  Util.parseColor(startColor);
	var end = Util.parseColor(updater.endColor);
	//common.log("Highlighter: end color: " + end.red + "," + end.green + "," + end.blue);
	
	updater.red0 = Math.min(start.red, end.red);
	updater.green0 = Math.min(start.green, end.green);
	updater.blue0 = Math.min(start.blue, end.blue);
	
	updater.red1 = Math.max(start.red, end.red);
	updater.green1 = Math.max(start.green, end.green);
	updater.blue1 = Math.max(start.blue, end.blue);
	
	updater.redDelta = Math.floor((updater.red1 - updater.red0) / step);
	updater.greenDelta = Math.floor((updater.green1 - updater.green0) / step);
	updater.blueDelta = Math.floor((updater.blue1 - updater.blue0) / step);
	
	updater.timer = window.setInterval(Effects._highlightUpdater.bind(updater), 50);
	Effects._highlightUpdater.apply(updater);
	return true;
};

Effects._highlightUpdater = function() {
	this.red0 = Math.min(255, this.red0 + this.redDelta);
	this.green0 = Math.min(255, this.green0 + this.greenDelta);
	this.blue0 = Math.min(255, this.blue0 + this.blueDelta);
	
	if (this.red0 >= this.red1 && this.green0 >= this.green1 && this.blue0 >= this.blue1) {
		window.clearInterval(this.timer);
		Element.setStyle(this.element, { backgroundColor: this.endColor }, true);
		this.element.highlightUpdater = null;
	} else {
		var newColor = "rgb(" + this.red0 + "," + this.green0 + "," + this.blue0 + ")";
		Element.setStyle(this.element, { backgroundColor: newColor }, true);
	}
}

// Extra object extensions.
String.padNumber = function(n, width) {
	var s = new String(n);
	var count = width - s.length;
	if (count <= 0) {
		return s;
	}

	for( var i = 0; i < count; ++i ) {
		s = "0" + s;
	}

	return s;
}

// Formatted time string of a date.
Date.prototype.formattedTime = function(withZoneOffset) {
	var offset = "";
	if (withZoneOffset) {
		var tzo = this.getTimezoneOffset();
		var sign = tzo >= 0 ? 1 : -1;
		offset = (sign > 0 ? "-" : "+") + String.padNumber(Math.round(sign*tzo / 60), 2) + ":" + String.padNumber(Math.round(sign*tzo % 60), 2);
	}
	return String.padNumber(this.getHours(), 2) + 
		":" + String.padNumber(this.getMinutes(), 2) + 
		":" + String.padNumber(this.getSeconds(), 2) +
		"." + String.padNumber(this.getMilliseconds(), 3) + offset;
}

// Set "debug" URL parameter to true to enable logging output from Util.log() calls.
Util.defaultDebug = false;
Util.debug = Util.getUrlParameters()["debug"] == "true" ? true : Util.defaultDebug;

/**
 * Class for common Accessory functionality.
 */
var AccessoryCommon = Class.create();
AccessoryCommon.prototype = {
	loadingPopupDelay: 250,	// If a DWR takes more than this, the loadingDiv is shown.
	busyElements: [],		// Elements to set to busy while DWR calls are in progress.
	ignoreClickHandler: function() { return false; }, // Handler that causes clicks on links to be ignored.
	
	enableRemoveFromWishList: true, // If true, instead of "In wish list", you get buttons called "Remove from wish list".
	wishListListeners: [], // List of listeners to wish list changes.
	
	// Maximum time in seconds to wait for a DWR request response.
 	dwrRequestTimeout: 30,
 	
 	// Message shown when an Ajax request timeouts.
 	dwrConnectionErrorMessage: "Could not connect to the server. Please try again later.",
 	
 	// Error message shown when an Ajax error occurs.
 	dwrErrorMessage: "Could not retrieve information from the server. Please try again later.",
 	
 	// Warning message shown when an Ajax warning occurs.
 	dwrWarningMessage: "The server returned a warning message:",
 	
 	// Message shown when an Ajax request timeouts.
 	dwrTimeoutMessage: "The server did not respond in time. Please try again later.",
		
	// Constructor.
	initialize: function() {
	},

	// Common page setup.
	onLoad: function() {
		this.log("User agent: " + navigator.userAgent + ", IE: " + Prototype.Browser.IE);
		
	 	// Setup DWR if available.
	 	if (typeof DWREngine != "undefined") {
		 	DWREngine.setErrorHandler(this.handleRemoteError.bind(this));
		 	DWREngine.setWarningHandler(this.handleRemoteWarning.bind(this));
	
		 	// Never wait longer than this number of seconds for a response.
		 	DWREngine.setTimeout(this.dwrRequestTimeout * 1000);
	
			// Show loading popup while request is outstanding.	 	
		 	DWREngine.setPreHook(this.dwrPreHook.bind(this));
		 	DWREngine.setPostHook(this.dwrPostHook.bind(this));
		 }
	},

 	// Method to start a DWR batch.
 	beginBatch: function() {
 		DWREngine.beginBatch();
 	},
 	
 	// Method end (flush) a DWR batch, optionally setting specified
 	// element's cursors to 'wait'.
 	endBatch: function() {
 		DWREngine.endBatch.apply(DWREngine, arguments);
 	},
 	
 	// Method to clear a batch that has been started.
 	cancelBatch: function() {
 		dwr.engine._batch = null;
 	},
 	
 	// Method to add elements to be made busy (cursor: wait) when
 	// a DWR call is in progress.
 	pushBusyElements: function() {
 		for( var i = 0; i < arguments.length; ++i ) {
	 		this.busyElements.push($(arguments[i]));
	 	}
 	},
 	
 	// Sets all currently pushed busy elements to busy (cursor: wait).
 	setBusy: function() {
 		document.body.style.cursor = 'wait';
 		
 		// Set cursor of all extra specified elements in 'busyElements' to 'wait'
 		// during call, if specified. After the call (in dwrPostHook) the cursor
 		// is reset to the old value, and the 'busyElements' property is cleared.
 		if (this.busyElements != null) {
 			for( var i = 0; i < this.busyElements.length; ++i ) {
 				var e = $(this.busyElements[i]);
 				if (e != null) {
 					e.prevCursorValue = Element.getStyle(e, "cursor");
 					Element.setStyle(e, {cursor: "wait"});
 				}
 			}
 		}
 	},
 	
 	// Sets all busy elements back to have their previous cursor.
 	clearBusy: function() {
 		document.body.style.cursor = 'auto';
 		
 		// Reset busy elements.
 		if (this.busyElements != null) {
 			for( var i = 0; i < this.busyElements.length; ++i ) {
 				var e = $(this.busyElements[i]);
 				if (e != null) {
 					Element.setStyle(e, {cursor: e.prevCursorValue});
 				}
 			}
 			this.busyElements = [];
 		}
 	},

 	// Called by DWR before a request is started.
 	dwrPreHook: function() {
 		this.dwrErrorShown = false;
 		this.setBusy();
		Util.blockWindow();
 		
		// Use a delay of some milliseconds before showing the popup.
		// This will ensure the popup is only shown when the request takes longer
		// than this (avoids flashing the popup when the request returns quickly).
		this.dwrLoadingTimer = window.setTimeout(
			function() {
		 		var loadingDiv = $('loadingDiv');
		 		if (loadingDiv != null) {
			 		var size = Util.getWindowSize();
			 		var offsets = Util.getScrollOffsets();
			 		loadingDiv.style.left = size[0] - 160 + "px";
			 		loadingDiv.style.top = size[1] - 60 + offsets[1] + "px";
			 		loadingDiv.style.display = 'block';
			 	}
		 	},
			this.loadingPopupDelay
		);
 	},

	// Called by DWR after a request is finished.
	dwrPostHook: function() {
		if (this.dwrLoadingTimer != null) {
			window.clearTimeout(this.dwrLoadingTimer);
			this.dwrLoadingTimer = null;
		}
		
 		var loadingDiv = $('loadingDiv');
 		if (loadingDiv != null) {
	 		loadingDiv.style.display = 'none';
	 	}
	 	
	 	Util.unblockWindow();
	 	this.clearBusy();
	},
 	 	
 	// Handle a DWR error.
 	handleRemoteError: function(error, exception) {
 		if (error == "Timeout") {
 			this.handleTimeoutError();
 		} else {
			// Only show the first error, since if we're doing a batched request, we 
			// get multiple calls to this method.
	 		if (!this.dwrErrorShown) {
		 		var msg = this.dwrErrorMessage;
			 	if (exception != null && exception.message != "Error") {
			 		// Just show the exception details.
			 		msg += "\n\nError details:\n";
			 		msg += "    " + exception.javaClassName + ": ";
			 		msg += ((exception.message != null && exception.message.length != 0) ? exception.message : "No message.");
			 		
			 		var cause = exception.cause;
			 		while( cause != null ) {
			 			msg += "\n    " + cause.javaClassName + ": ";
				 		msg += ((cause.message != null && cause.message.length) != 0 ? cause.message : "No message.");
			 			
			 			cause = cause.cause;
		 			}

			 	} else if (error != null && error.length > 0 && error != "Error") {
			 		msg += "\n\nError details: " + error;
			 	}
				Util.log("DWR remote error: " + msg);
		 		alert(msg);
		 	}
	 		this.dwrErrorShown = true;
	 	}
 	},
 	
 	// Handle a DWR warning.
 	handleRemoteWarning: function(warning, exception) {
 		if (warning.indexOf("NS_ERROR_NOT_AVAILABLE")) {
 			// FireFox message when server cannot be contacted.
 			this.handleConnectionError(warning);
 			
 		} else if (warning.indexOf("No data received from server")) {
 			// Internet Explorer message when server cannot be contacted.
 			this.handleConnectionError(warning);
 			
 		} else {
 			// Else just show the warning.
			var msg = this.dwrWarningMessage + " " + warning;
			Util.log("DWR warning: " + msg);
			alert(msg);
	 	}
 	},
 	
 	// Handle a DWR timeout error.
 	handleTimeoutError: function() {
 		// This is not called by DWR on timeouts for some reason, so do it ourselves.
 		this.dwrPostHook();
 		
		Util.log("DWR timeout: " + this.dwrTimeoutMessage);
 		alert(this.dwrTimeoutMessage);
 	},
 	
 	// Handle a DWR exception occurred when making a remote call.
 	handleConnectionError: function(error) {
 		var msg = this.dwrConnectionErrorMessage;
 		if (error != null && error.length > 0) {
	 		msg += "\n\nError details: " + error;
	 	}
		
		Util.log("DWR connection error: " + msg);
 		alert(msg);
 	},
 	
 	// IE indents for option elements.
	ieIndents: [
		"", 
		"    ",
		"        ",
		"            ",
		"                "
	],
		
 	// Method to create a possibly indented Option element.
 	createOption: function(value, text, level) {
		var option = new Option();
		option.value = value;
		if (level == null) {
			// Just create a normal option.
			option.text = text;
		} else {
			// Fix to get indented options work on both IE and FF (and hopefully all others).
			option.className = "indent" + level;
			if (Prototype.Browser.IE) {
				// In IE6, text-indent style on options doesn't work,
				// so indent manually. IE shows normal spaces in an option,
				// while FireFox doesn't and needs &nbsp;, which can only
				// be set using innerHTML, which does not work in IE. Sigh.
				option.text = this.ieIndents[level - 1] + text;
			} else {
				option.text = text;
			}
		}
		return option;
 	},
 	
 	// Log a debug message.
 	log: function(msg) {
 		Util.log(msg);
 	},
 	
 	// Check if cookies are enabled, if not give a warning.
 	cookieCheck: function() {
		if (!Util.cookiesEnabled()) {
			alert(Messages["common.cookiesDisabled"]);
			return false;
		}
		return true;
 	},
 	
 	// Simple assertion.
 	assert: function(description, condition) {
 		if (!condition) {
 			throw new Error("Assertion failed: " + description);
 		}
 		return true;
 	},
 	
 	// Get the full path to an accessory image.
 	getAccessoryImage: function(imageName) {
		if (imageName == null) {
			return Config["noPicture.gif"];
		}
		
		return Config["image.base"] + imageName.replace("\\", "/");
 	},
 	
 	// Get the full path to the thumbnail of an accessory image.
 	getAccessoryThumbnail: function(imageName) {
		if (imageName == null) {
			return Config["noPicture.gif"];
		}

		var thumb = imageName.replace("_HR", "_TH").replace("\\", "/");
		return Config["image.base"] + thumb;
 	},
 	
 	// Get the full path to a PDF document.
 	getAccessoryDocument: function(documentName) {
 		if (documentName == null) {
 			return null;
 		}
 		return Config["document.base"] + documentName.replace("\\", "/");
 	},
 	
 	// Retrieve the wish list. Used by a page to retrieve the wish list on-load (done by details.js and overview.js).
 	// An optional callback function is invoked after the wish list has been received.
 	retrieveWishList: function(callback) {
		AccessoryService.getWishList(Config.countryCode, Config.languageCode, this.handleGetWishList.bind(this, callback));
	},
	
 	// Retrieve the wish list synchrously. Used when calling from a close or unload event of a popup window.
 	// An optional callback function is invoked after the wish list has been received.
 	retrieveWishListSynch: function(callback) {
		common.log("Retrieving wish list synchronously...");
		AccessoryService.getWishList(Config.countryCode, Config.languageCode, 
			{ callback: this.handleGetWishList.bind(this, callback),
			  asynch: false });
	},
	
	// Callback for the DWR call to get the wish list.
	handleGetWishList: function(callback, wishList) {
		this.wishList = wishList;
		common.log("Got wish list: " + wishList.size + " item(s).");
		for( var partCode in wishList.partCodes ) {
			common.log(" - part code: '" + partCode + "'");
		}
		if (callback != null) {
			callback();
		}
	},
	
	// Add a wish list listener.
	addWishListListener: function(listener) {
		this.wishListListeners.push(listener);
	},
	
	// Remove a wish list listener.
	removeWishListListener: function(listenerToRemove) {
		var n = this.wishListListeners.length;
		if (n > 0) {
			for( var i = 0; i < n; ++i ) {
				var listener = this.wishListListeners[i];
				if (listener == listenerToRemove) {
					delete this.wishListListeners[i];
					break;
				}
			}
		}
	},
	
	// To add a part code to the client's wish list.
	// Invokes all wish list listeners as well.
	addToClientWishList: function(partCode) {
		if (this.wishList != null) {
			this.wishList.partCodes[partCode] = true;
			var n = this.wishListListeners.length;
			if (n > 0) {
				for( var i = 0; i < n; ++i ) {
					var listener = this.wishListListeners[i];
					if (listener && listener.wishListChanged) {
						listener.wishListChanged(partCode, true);
					}
				}
			}
			this.log("Added to wish list: " + partCode);
		}
	},
	
	// To remove a part code from the client's wish list.
	// Invokes all wish list listeners as well.
	removeFromClientWishList: function(partCode) {
		if (this.wishList != null) {
			if (this.wishList.partCodes[partCode]) {
				delete this.wishList.partCodes[partCode];
				var n = this.wishListListeners.length;
				if (n > 0) {
					for( var i = 0; i < n; ++i ) {
						var listener = this.wishListListeners[i];
						if (listener && listener.wishListChanged) {
							listener.wishListChanged(partCode, false);
						}
					}
				}
				this.log("Removed from wish list: " + partCode);
			}
		}
	},
	
	// To check if a part code is in the client's wish list.
	isInClientWishList: function(partCode) {
		if (this.wishList == null) {
			this.log("In wish list: not loaded.");
			return false;
		}
		
		if (partCode != null && this.wishList.partCodes[partCode]) {
			return true;
		}
		return false;
	}
}

// Create page class and initialize page when done loading.
var common = new AccessoryCommon();
Event.observe(window, "load", common.onLoad.bind(common));

