if (!maj) {
	"use strict";

	// Init the majestri namespace.
	var maj = {};

	// Take this opportunity to create any HTML 5 elements we may need to work around problems in IE.
	// Reference: http://ejohn.org/blog/html5-shiv/
	// Reference: http://remysharp.com/2009/01/07/html5-enabling-script/
	document.createElement("article");
	document.createElement("footer");
	document.createElement("header");
	document.createElement("hgroup");
	document.createElement("nav");

	// indexOf is super handy, let's extend Array if it's missing.
	if (!Array.prototype.indexOf) {
		Array.prototype.indexOf = function(item) {
			for (var liIdx=0; liIdx<this.length; liIdx++) {
				if (this[liIdx]===item) return liIdx;
			}
			return -1;
		};
	}
	
	// ---------------------------------------------------------------------
	// Extension of Function object.
	// ---------------------------------------------------------------------
	if(!Function.prototype.inherits) {
		Function.prototype.inherits = function (parentType){ 
			this.prototype = parentType;
			this.prototype.constructor = this;
			if (typeof(parentType.constructor) === "function" || parentType.constructor === Function) { 
				// Normal inheritance. Allows the creation of a subclass from a base class that is itself
				// able to be instantiated.
				this.prototype.superClass = parentType.prototype;
				this.baseConstructor = parentType;
			} else { 
				// Abstract inheritance. The superclass cannot itself be instantiated. It just provides a definition.
				this.prototype.superClass = parentType;
			} 
			return this;
		};
	}
	
	// extend maj and jQuery using an anonymous function to ensure variable scope safety.
	// this also allows us to change the maj namespace if we ever want to.
	(function(maj){
		// Global variables available to maj library and jQuery maj plugin.
		var moMajArrays = [];
		var moQueueProcessor;

		//------------------------------------------------------------------------------
		// START: maj Library Definition
		//------------------------------------------------------------------------------

		//------------------------------------------------------------------------------
		// START: maj.math Library Definition
		//------------------------------------------------------------------------------
		maj.math = {};

		// parses value as a number.
		// decimalPlaces: specifies the number of decimal places the number can have.
		// defaultValue: the value to fall back to if the parse of value to a number fails.  If not provided, the default will be 0.
		maj.math.parseNumber = function(value, decimalPlaces, defaultValue) {
			if (defaultValue===undefined) {
				defaultValue = 0;
			}

			if (decimalPlaces) {
				value = parseFloat(value);
			} else {
				value = parseInt(value);
			}

			if (isNaN(value)) {
				value = defaultValue;
			}

			if (decimalPlaces) {
				value = maj.math.round(value, decimalPlaces);
			}

			return value;
		};

		// Rounds a value to a specified number of decimal places.
		// value:
		// decimalPlaces:
		maj.math.round = function(value, decimalPlaces) {
			if (decimalPlaces) {
				value=parseFloat(value.toFixed(decimalPlaces));
			} else {
				return Math.round(value);
			}
		};

		//------------------------------------------------------------------------------
		// END: maj.math Library Definition
		//------------------------------------------------------------------------------

		//------------------------------------------------------------------------------
		// START: AutoSuggest Providers and Renderers Definition
		//------------------------------------------------------------------------------

		// ADAPTORS

		/* Adaptor Factory */
		maj.autoSuggestAdaptorFactory = function($containerElement, settings) {
				switch (settings.adaptorType) {
						case maj.enums.autoSuggestAdaptorType.majestri:
								return new maj.autoSuggestMajestriAdaptor($containerElement, settings);
						case maj.enums.autoSuggestAdaptorType.address:
								return new maj.autoSuggestAddressAdaptor($containerElement, settings);
						default:
				}
		};

		/* Adaptor Base */
		maj.autoSuggestAdaptorBase = function($containerElement, settings) {
				this.$containerElement = $containerElement;
				this.settings = settings;

				this.getProvider = function() {
						// Use factory class.
						return maj.autoSuggestProviderFactory(settings);
				};

				this.getRenderer = function() {
						// Use factory class.
						return maj.autoSuggestRendererFactory(settings.rendererType, $containerElement);
				};
		};

		maj.autoSuggestAdaptorBase.autoSuggest = function(matchText) {
				alert("AutoSuggest!");
		};

		maj.autoSuggestAdaptorBase.teardown = function() {
				alert("Teardown");
		};

		/* Majestri Adaptor */
		maj.autoSuggestMajestriAdaptor = function($containerElement, settings) {
				// Invoke the constructor on our base class.
				maj.autoSuggestMajestriAdaptor.baseConstructor.call(this, $containerElement, settings);

				// Override the autoSuggest method on our base class.
				maj.autoSuggestMajestriAdaptor.prototype.autoSuggest = function(matchText) {
						loProvider.fetchSuggestions(matchText, handleSuggestionResponse);
				};

				// Override the teardown method on our base class.
				maj.autoSuggestMajestriAdaptor.prototype.teardown = function() {
						loRenderer.clearSuggestions();
				};

				var loProvider = this.getProvider();
				var loRenderer = this.getRenderer();

				var handleSuggestionResponse = function(response) {
						loRenderer.renderSuggestions(response);
				};

				var selectSuggestion = function(selectedItem) {
				};
		};
		maj.autoSuggestMajestriAdaptor.inherits(maj.autoSuggestAdaptorBase);


		/* Address Adaptor */
		maj.autoSuggestAddressAdaptor = function($containerElement, settings) {
				var loProvider = this.getProvider();
				var loRenderer = this.getRenderer();

				// Invoke the constructor on our base class.
				maj.autoSuggestAddressAdaptor.baseConstructor.call(this, $containerElement, settings);

				// Override the autoSuggest method on our base class.
				maj.autoSuggestAddressAdaptor.prototype.autoSuggest = function(matchText) {
						loProvider.fetchSuggestions(matchText, handleSuggestionResponse);
				};

				// Override the teardown method on our base class.
				maj.autoSuggestAddressAdaptor.prototype.teardown = function() {
				};

				var handleSuggestionResponse = function(response, status) {
						alert("Handling");
						if (status === google.maps.GeocoderStatus.OK && response[0]) {
								//loGeocode.firstItem = response[0];
								// clearListItems();
								var loResults = [];
								var liResponseLength = response.length;
								for (var i = 0; i < liResponseLength; i++) {
										loResults.push(response[i]);
								}
								alert(loResults.length);
								loRenderer.renderSuggestions(loResults, selectSuggestion);
						} else if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
								//document.getElementById("suggest_list").innerHTML =  "?";
								alert("Zero results");
								moGeocoder.resultAddress = "";
								moGeocoder.resultBounds = null;
						} else {
								//document.getElementById("suggest_list").innerHTML =  status;
								alert("Other shit");
								moGeocoder.resultAddress = "";
								moGeocoder.resultBounds = null;
						}
				};

				var selectSuggestion = function(selectedItem) {
						alert("Suggestion selected");
				};
		};
		maj.autoSuggestAddressAdaptor.inherits(maj.autoSuggestAdaptorBase);

		// PROVIDERS
		/* Provider Factory */
		maj.autoSuggestProviderFactory = function(settings) {
				switch (settings.providerType) {
						case maj.enums.autoSuggestProviderType.majestri:
								return new maj.majicSuggestionProvider(settings.providerId);
						case maj.enums.autoSuggestProviderType.address:
								return new maj.addressSuggestionProvider();
						default:
				}
		};

		/* Provider Base */
		maj.autoSuggestProviderBase = {
				fetchSuggestions: function(matchText, responseCallback) {
				}
		};

		/* Majestri Provider */
		maj.majicSuggestionProvider = function(providerId) {
				maj.majicSuggestionProvider.prototype.fetchSuggestions = function(matchText, responseCallback) {
						var loOptions = {
								url: "svcSuggestionProvider.aspx",
								successCallback: responseCallback,
								failureCallback: responseCallback,
								parameters: { providerId: providerId, searchText: matchText }
						}
						maj.ajax.submitJob(loOptions);
				}
		}
		maj.majicSuggestionProvider.inherits(maj.autoSuggestProviderBase);

		/* Address Provider */
		maj.addressSuggestionProvider = function() {
				var moGeocoder = new google.maps.Geocoder();
				var moMap;

				var init = (function() {
						var loCoordinate = new google.maps.LatLng(-34.397, 150.644);
						var loOptions = {
								zoom: 8,
								center: loCoordinate,
								mapTypeId: google.maps.MapTypeId.ROADMAP
						}
						moMap = new google.maps.Map($('#mapAddress')[0], loOptions);
				})();

				maj.addressSuggestionProvider.prototype.fetchSuggestions = function(matchText, responseCallback) {
						moGeocoder.geocode({ 'address': matchText }, responseCallback);
				}
		};
		maj.addressSuggestionProvider.inherits(maj.autoSuggestProviderBase);


		// RENDERERS
		// AutoSuggest Renderer Factory class.
		maj.autoSuggestRendererFactory = function(rendererType, $containerElement) {
				switch (rendererType) {
						case maj.enums.autoSuggestRendererType.standard:
								return new maj.standardSuggestionRenderer($containerElement);
						case maj.enums.autoSuggestRendererType.address:
								return new maj.addressSuggestionRenderer($containerElement);
						default:

				}
		};

		maj.suggestionRendererBase = {
				renderSuggestions: function(suggestions, selectItemCallback) {
				},
				clearSuggestions: function() {
				}
		};
		maj.addressSuggestionRenderer = function($containerElement) {
				maj.addressSuggestionRenderer.prototype.renderSuggestions = function(suggestions, selectItemCallback) {
						var $loContainerElement = $('#txtSearch_cnt');
						var $loSuggestionContainer = $loContainerElement.find('.suggestions');
						var loHTML = "";
						for (i = 0; i < suggestions.length; i++) {
								loHTML = loHTML + '<div>' + suggestions[i].formatted_address + '</div>';
						}
						$loSuggestionContainer.html(loHTML);
						$loSuggestionContainer.maj('toggleVisibility', true);
				}
		}
		maj.addressSuggestionRenderer.inherits(maj.suggestionRendererBase);

		maj.standardSuggestionRenderer = function($containerElement) {
				maj.standardSuggestionRenderer.prototype.renderSuggestions = function(suggestions, selectItemCallback) {
						var $loSuggestionContainer = $containerElement.find('.suggestions');
						//var lsHTML = "";
						//for (i = 0; i < suggestions.length; i++) {
						//    lsHTML += '<div class=\"suggestion\">' + suggestions[i].WebPageTitle + '</div>';
						//}
						alert(suggestions);
						$loSuggestionContainer.html(suggestions);
						$loSuggestionContainer.maj('toggleVisibility', true);
						var loSettings = {
								elementHoverCallback: onElementMouseOver,
								elementClickCallback: onElementSelect
						}
						$loSuggestionContainer.maj('exclusiveElementActive', loSettings);
				}

				maj.standardSuggestionRenderer.prototype.clearSuggestions = function() {
						var $loSuggestionContainer = $containerElement.find('.suggestions');
						$loSuggestionContainer.html("");
						$loSuggestionContainer.maj('toggleVisibility', false);
				}

				var onElementMouseOver = function(elementText) {
						var $loInput = $containerElement.find('#txtMemberSearch');
						$loInput.val(elementText);
				}

				var onElementSelect = function(elementText) {
						alert(elementText);
				}
		}

		//------------------------------------------------------------------------------
		// END: AutoSuggest Providers and Renderers Definition
		//------------------------------------------------------------------------------
		

		//------------------------------------------------------------------------------
		// START: Central Array Management
		//------------------------------------------------------------------------------
		maj.getArray = function(key) {
				if (moMajArrays[key]) {
						return moMajArrays[key];
				}
				var loNewArray = [];
				moMajArrays[key] = loNewArray;
				return loNewArray;
		};
		//------------------------------------------------------------------------------
		// END: Central Array Management
		//------------------------------------------------------------------------------

		//------------------------------------------------------------------------------
		// START: Base 64 Encoding
		//------------------------------------------------------------------------------
		maj.base64Encoder = {

				// Private Property
				msKeyString: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

				// Public method for encoding
				encode: function(input) {
						var lsOutput = "";
						var lsChar1, lsChar2, lsChar3, liEnc1, liEnc2, liEnc3, liEnc4;
						var i = 0;

						input = this.utf8Encode(input);

						while (i < input.length) {

								lsChar1 = input.charCodeAt(i++);
								lsChar2 = input.charCodeAt(i++);
								lsChar3 = input.charCodeAt(i++);

								liEnc1 = lsChar1 >> 2;
								liEnc2 = ((lsChar1 & 3) << 4) | (lsChar2 >> 4);
								liEnc3 = ((lsChar2 & 15) << 2) | (lsChar3 >> 6);
								liEnc4 = lsChar3 & 63;

								if (isNaN(lsChar2)) {
										liEnc3 = liEnc4 = 64;
								} else if (isNaN(lsChar3)) {
										liEnc4 = 64;
								}

								lsOutput = lsOutput +
				this.msKeyString.charAt(liEnc1) + this.msKeyString.charAt(liEnc2) +
				this.msKeyString.charAt(liEnc3) + this.msKeyString.charAt(liEnc4);
						}

						return lsOutput;
				},

				// Public method for decoding
				decode: function(input) {
						var lsOutput = "";
						var lsChar1, lsChar2, lsChar3, liEnc1, liEnc2, liEnc3, liEnc4;
						var i = 0;

						input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

						while (i < input.length) {

								liEnc1 = this.msKeyString.indexOf(input.charAt(i++));
								liEnc2 = this.msKeyString.indexOf(input.charAt(i++));
								liEnc3 = this.msKeyString.indexOf(input.charAt(i++));
								liEnc4 = this.msKeyString.indexOf(input.charAt(i++));

								lsChar1 = (liEnc1 << 2) | (liEnc2 >> 4);
								lsChar2 = ((liEnc2 & 15) << 4) | (liEnc3 >> 2);
								lsChar3 = ((liEnc3 & 3) << 6) | liEnc4;

								lsOutput = lsOutput + String.fromCharCode(lsChar1);

								if (liEnc3 != 64) {
										lsOutput = lsOutput + String.fromCharCode(lsChar2);
								}
								if (liEnc4 != 64) {
										lsOutput = lsOutput + String.fromCharCode(lsChar3);
								}

						}

						lsOutput = maj.base64Encoder.utf8Decode(lsOutput);

						return lsOutput;

				},

				// Private method for UTF-8 encoding
				utf8Encode: function(string) {
						string = string.replace(/\r\n/g, "\n");
						var lsUTFText = "";

						for (var n = 0; n < string.length; n++) {

								var c = string.charCodeAt(n);

								if (c < 128) {
										lsUTFText += String.fromCharCode(c);
								}
								else if ((c > 127) && (c < 2048)) {
										lsUTFText += String.fromCharCode((c >> 6) | 192);
										lsUTFText += String.fromCharCode((c & 63) | 128);
								}
								else {
										lsUTFText += String.fromCharCode((c >> 12) | 224);
										lsUTFText += String.fromCharCode(((c >> 6) & 63) | 128);
										lsUTFText += String.fromCharCode((c & 63) | 128);
								}

						}

						return lsUTFText;
				},

				// Private method for UTF-8 decoding
				utf8Decode: function(utftext) {
						var lsString = "";
						var i = 0;
						var c = c1 = c2 = 0;

						while (i < utftext.length) {

								c = utftext.charCodeAt(i);

								if (c < 128) {
										lsString += String.fromCharCode(c);
										i++;
								}
								else if ((c > 191) && (c < 224)) {
										c2 = utftext.charCodeAt(i + 1);
										lsString += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
										i += 2;
								}
								else {
										c2 = utftext.charCodeAt(i + 1);
										c3 = utftext.charCodeAt(i + 2);
										lsString += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
										i += 3;
								}

						}

						return lsString;
				}
		}
		//------------------------------------------------------------------------------
		// END: Base64 Encoding
		//------------------------------------------------------------------------------

		//------------------------------------------------------------------------------
		// START: State Data Management
		//------------------------------------------------------------------------------
		maj.viewStateData = {
				setValue: function(key, value) {
						moMajData[key] = value;
				},

				getValue: function(key) {
						return moMajData[key];
				},

				toJSON: function() {
						return JSON.stringify(moMajData);
				}
		}
		//------------------------------------------------------------------------------
		// END: State Data Management
		//------------------------------------------------------------------------------


		var $moModalPageElement, $moModalPageContainer, moModalResult, moModalCloseCallback;
		maj.popupModalPage = function(pageUrl, closeCallback) {
			if (!$moModalPageElement) {
				$moModalPageElement= $('<div id="majModalPage" class="modalShadow" hidden style="display:none;">' +
																 '<div id="majModalPage_cnt" hidden style="float:left;display:none;width:100%;"></div>' +
																 '<a class="close jqmClose" style="position:absolute;right:10px;top:10px;" href="#" class="jqmClose" style="text-align:right;">Close</a>' +
															 '</div>').appendTo('body');
				$moModalPageContainer = $('#majModalPage_cnt');

				var loModalCloseCallback = function(hash) {
					hash.o.remove();
					hash.w.hide();
					if (moModalCloseCallback){
						moModalCloseCallback();
					} 
				}

				$moModalPageElement.jqm({trigger:'', modal:true, onHide:loModalCloseCallback });
			}

			moModalCloseCallback = closeCallback;
			$moModalPageContainer.hide();
			$moModalPageContainer.children().remove();
			$moModalPageElement.addClass('loading');

			if (!$moModalPageElement.is(':visible')) {
				// If the modal popup is not currently visible then center it and show it.
				moModalResult = null;
				$moModalPageElement.maj('center').jqmShow();
			}

			var loPreReadyCallback = function(ajaxRequest) {
				$moModalPageContainer.fadeIn();
				$moModalPageElement.maj('center', true).removeClass('loading');
			}

			$moModalPageContainer.maj('loadContent', {url:pageUrl,preReadyCallback:loPreReadyCallback});
		};


		// Ensures value lies within minValue and maxValue.
		// value: 
		// minValue:
		// maxValue:
		maj.placeInRange = function(value, minValue, maxValue) {
			if (minValue!==undefined && value<minValue) {
				return minValue;
			} else if (maxValue!==undefined && value>maxValue) {
				return maxValue;
			}

			return value;
		};

		// Determines if the type of value is number.
		// value: the value to test.
		maj.isNumber = function(value) {
			return (typeof(value)==='number');
		};

		// Determines if the passed day/month/year combination is a valid date.
		maj.isValidDate = function(day, month, year) {
			var ldDate = new Date(year, month-1, day);
			return ((ldDate.getDate()==day) && (ldDate.getMonth()+1==month) && (ldDate.getFullYear()==year));
		};

		// Builds the url to return a certain size of a given image.
		// baseUrl: the base url of the image.
		// width: either the width of the image to request, or a valid jQuery selector (in which case width and height will be derived from this).
		maj.sizedImageUrl = function(baseUrl, width, height) {
			var lsBaseUrl = baseUrl.toLowerCase();
			if ((lsBaseUrl.indexOf('svcImgFetch.aspx')>=0) || (lsBaseUrl.indexOf(':')>=0)) {
				// ToDo: strip the url portion from the query string and make that the baseUrl.
				return baseUrl;
			}
			var loStr=[];
			loStr.push('svcImgFetch.aspx?u=');
			loStr.push(encodeURIComponent(baseUrl));

			var lbWidthIsNumber=maj.isNumber(width);
			if (!lbWidthIsNumber) {
				var $loWidth=$(width);
				width=$loWidth.width();
				lbWidthIsNumber=maj.isNumber(width);
				if (lbWidthIsNumber) {
					height=$loWidth.height();
				}
			}

			if (lbWidthIsNumber) {
				loStr.push('&w=');
				loStr.push(width);
			}

			if (maj.isNumber(height)) {
				loStr.push('&h=');
				loStr.push(height);
			}
			return loStr.join('');
		};

		//------------------------------------------------------------------------------
		// START: maj.ajax Library Definition
		//------------------------------------------------------------------------------
		maj.ajax = {
				////////////////////////////////////////////////////////////////////////////////
				// map:
				//	url - where to get the server content from.
				//	parameters - a dynamic object containing any parameters that need to be submitted.
				//	successCallback - method to invoke when the request has been processed.
				//	failureCallback - method to invoke when the request fails.
				//	commonCallback - method to invoke regardless of success of failure.  This is invoked after any success/failure callbacks.
				////////////////////////////////////////////////////////////////////////////////
				submitJob: function(map) {
						if (!moQueueProcessor) {
								moQueueProcessor = new maj.ajax.queueProcessor();
								moQueueProcessor.start();
						}
						return moQueueProcessor.submit(map);
				},

				// Create a new ajax queue processor.
				queueProcessor: function() {
						var moQueueItems = [];
						var mbIsProcessing = false;
						var moParams = {};

						this.submit = function(map) {
								var loQueueItem = new queueItem(map);
								moQueueItems.push(loQueueItem);
								return loQueueItem;
						}

						this.process = function() {
								var loThis = this;
								if (moQueueItems.length && !mbIsProcessing) {
										mbIsProcessing = true;
										while (moQueueItems.length) {
												var loJob = this.getNextJob();
												loJob.processJob();
										}
										mbIsProcessing = false;
								}
								setTimeout(function() { loThis.process(); }, 500);
						}

						this.start = function() {
								this.process();
						};

						this.getNextJob = function() {
								return moQueueItems.pop();
						}

						var queueItem = function(map) {
								var loThis = this;
								
								this.ajaxRequest = maj.ajax.createRequest();
						
								this.abort = function() {
									if (this.ajaxRequest) {
										this.ajaxRequest.abort();
										this.ajaxRequest = undefined;
									};
								};
								
								this.processJob = function() {
										var loAjaxRequest = loThis.ajaxRequest; //maj.ajax.createRequest();
										if (!loAjaxRequest) return;
										
										loAjaxRequest.open("POST", map.url, true);
										loAjaxRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
										loAjaxRequest.onreadystatechange = function() {
											switch (loAjaxRequest.readyState) {
												case 0: //request not initialized
													break;
												case 1: //server connection established
													break;
												case 2: //request received 
													break;
												case 3: //processing request 
													break;
												case 4: //request finished and response is ready
													try {
														if ($.isFunction(map.successCallback)) {
															map.successCallback(loAjaxRequest.responseText);
														}
													} catch(err) {
													}

													if ($.isFunction(map.commonCallback)) {
														map.commonCallback();
													}

													break;
											}
										}

										var loParamText = [];
										for (lsPropertyName in map.parameters) {
											var loValue = map.parameters[lsPropertyName];

											if (loParamText.length) {
												loParamText.push('&');
											}
											loParamText.push(encodeURIComponent(lsPropertyName));
											loParamText.push('=');

											if(typeof(map.parameters[lsPropertyName]) === 'object') {
												loParamText.push(maj.base64Encoder.encode(JSON.stringify(loValue)));
											} else {
												loParamText.push(encodeURIComponent(loValue));
											}
										}
										loAjaxRequest.send(loParamText.join(''));
								}
						}
				},

				// Function must be called to ensure correct AJAX component is instantiated.
				createRequest: function() {
						var loXMLHTTPRequest;
						if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
								loXMLHTTPRequest = new XMLHttpRequest();
						} else { // code for IE6, IE5
								loXMLHTTPRequest = new ActiveXObject("Microsoft.XMLHTTP");
						}
						return loXMLHTTPRequest;
				}
		};


		//------------------------------------------------------------------------------
		// END: maj.ajax Library Definition
		//------------------------------------------------------------------------------

		//------------------------------------------------------------------------------
		// END: maj Library Definition
		//------------------------------------------------------------------------------

	})(maj);
}

