// Constants, but not really
var REGEX_VALID_EMAIL			= /\b[A-Z0-9._%-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b/i,
	REGEX_VALID_ZIPCODE_NL		= /[0-9]{4} ?[a-z]{2}/i,
	REGEX_NOT_EMPTY				= /.+/;


// Containers
var mod			= {},
	elements	= {};



/*
 *	jsEnabled()
 *	Sets the class "js" to the body element, so JavaScript dependant CSS can be applied
 */

function jsEnabled() {
	first("body", document).addClass("js");
}




/*
 *	createSocket()
 *	Returns a XmlHttpRequest object
 */

function createSocket() {
	// Native support for XmlHttpRequest
	try {
		return new XMLHttpRequest();
	}
	catch (exception) {}

	// XmlHttpRequest through ActiveX
	var xmlImplementations = [
		"MSXML2",
		"Microsoft",
		"MSXML",
		"MSXML3"
	];

	for (var i = 0; i < xmlImplementations.length; i++) {
		try {
			return new ActiveXObject(xmlImplementations[i] + ".XMLHTTP");
		}
		catch (exception) {}
	}

	// XmlHttpRequest is not supported
	return null;
}




/*
 *	require()
 *	Loads a .js module
 */

function require(moduleName) {
	// Check if the module is already loaded
	if (mod[moduleName]) {
		return true;
	}

	var socket	= createSocket();

	if (!socket) {
		return false;
	}

	// Get the requested module
	socket.open("GET", "/javascript/default/modules/mod." + moduleName.toLowerCase() + ".js", false);
	socket.send(null);

	// When found, eval the module to make it available
	if (socket.status == 200) {
		eval(socket.responseText);

		// Check if the loaded module has dependencies
		if (typeof dependencies != "undefined") {
			for (var i = 0; dependencies[i] !== undefined; i++) {
				require(dependencies[i]);
			}
		}

		return true;
	}

	// Unable to load the module
	return false;
}




/*
 *	element()
 *	document.getElementById() with caching and element extension
 */

function element(id, overwriteCache) {
	// Remove item from cache if needed
	if (overwriteCache && elements[id]) {
		delete elements[id];
	}

	// Check if the element is cached
	if (elements[id]) {
		return elements[id];
	}

	// Check if the element exists, cache it and return it
	if ((elements[id] = document.getElementById(id))) {
		extend(elements[id]);

		return elements[id];
	}

	return null;
}




/*
 *	newElement()
 *	Creates a new element based on the supplied node name, its attributes and contents (an array)
 */

function newElement(nodeName, attributes, contents) {
    if (!nodeName) {
        return false;
    }

    var element = document.createElement(nodeName);

    // Content is either added as a new textNode or as an existing DOM node
    if (contents && contents.constructor == Array) {
		for (var i = 0; contents[i] !== undefined; i++) {
			if (typeof contents[i] !== "object") {
				element.appendChild(document.createTextNode(contents[i]));
			} else if (contents[i].nodeType && contents[i].nodeType == 1) {
				element.appendChild(contents[i]);
			}
		}
    }

	// Apply the supplied attributes to the new element
    if (attributes) {
        for (var attribute in attributes) {
            element[attribute] = attributes[attribute];
        }
    }

    return extend(element);
}




/*
 *	first()
 *	Get the first childNode of an element with a specific nodeName
 */

function first(nodeName, parentNode) {
	// When called as an element method, set parentNode to that element
	if (this.nodeType && this.nodeType == 1) {
		parentNode = this;
	}

	if (!parentNode) {
		return null;
	}

	var element;

	if ((element = parentNode.getElementsByTagName(nodeName)[0])) {
		return extend(element)
	}

	return null;
}




/*
 *	addClass()
 *	Add a class to the element
 */

function addClass(className, element) {
	// When called as an element method, set element to that element
	if (this.nodeType && this.nodeType == 1) {
		element = this;
	}

	if (!element) {
		return false;
	}

	element.className += (element.className == "" ? "" : " ") + className;

	return true;
}




/*
 *	removeClass()
 *	Remove a class set to the element
 */

function removeClass(className, element) {
	// When called as an element method, set element to that element
	if (this.nodeType && this.nodeType == 1) {
		element = this;
	}

	if (!element) {
		return false;
	}

	var classRegExp = new RegExp(" ?" + className, "g");

	element.className = element.className.replace(classRegExp, "");

	return true;
}




/*
 *	hasClass()
 *	Check if a specific class was set to the element
 */

function hasClass(className, element) {
	// When called as an element method, set element to that element
	if (this.nodeType && this.nodeType == 1) {
		element = this;
	}

	if (!element) {
		return false;
	}

	var classRegExp = new RegExp("(^|[ ])(" + className + ")($|[ ])", "g");

	return element.className.match(classRegExp) ? true : false;
}




/*
 *	getPosition()
 *	Get the absolute position of the element
 */
 
function getPosition(element) {
	if (this.nodeType && this.nodeType == 1) {
		element = this;
	}

	var position = {
		x : 0,
		y : 0
	}

	if (element.offsetParent) {
		position.x = element.offsetLeft;
		position.y = element.offsetTop;

		while ((element = element.offsetParent)) {
			position.x += element.offsetLeft;
			position.y += element.offsetTop;
		}

		return position;
	}

	return null;
}




/*
 *	extend()
 *	Extends a DOM node with various methods
 */

function extend(element) {
	element.first		= first;
	element.addClass	= addClass;
	element.removeClass	= removeClass;
	element.hasClass	= hasClass;
	element.getPosition	= getPosition;

	return element;
}




/*
 *	flashObject()
 *	Inserts a Flash object. Thanks Eolas, job well done.
 */

function flashObject(container, file, width, height) {
	var replacement;

	replacement =	'<object type="application/x-shockwave-flash" data="' + file + '" width="' + width + '" height="' + height + '">';
	replacement +=	'<param name="movie" value="' + file + '" />';
	replacement +=	'</object>';

	container.innerHTML = replacement;
}




/*
 *	fade()
 *	Disables the page
 */

function fade() {
	var html = first("html", document),
		body = first("body", document);

	if (element("fade_overlay", true)) {
		body.removeChild(element("fade_overlay"));
	} else {
		body.appendChild(newElement("div", {id : "fade_overlay"}, null));
	}
}




/*
 *	preload()
 *	Preload images
 */

function preload() {
	for (var i = 0; arguments[i] !== undefined; i++) {
		newElement(
			"img",
			{
				src : arguments[i],
				alt : ""
			},
			null
		);
	}
}

/**
 * EventDispatcher
 */
function EventDispatcher()
{
	// Public
	this.addEventListener		= addEventListener;
	this.removeEventListener	= removeEventListener;
	this.dispatchEvent			= dispatchEvent;

	// Private
	var events = {};

	function addEventListener(type, listener)
	{
		if (typeof listener != "function")
		{
			throw new Error("Invalid argument: listener is missing or not a function");
		}

		if (typeof events[type] == "undefined")
		{
			events[type] = [];
		}

		events[type].push(listener);
	}

	function removeEventListener(type, listener)
	{
		if (events[type] != undefined)
		{
			for (var i = 0; i < events[type].length; i++)
			{
				if (events[type][i] == listener)
				{
					delete events[type][i];

					break;
				}
			}
		}
	}

	function dispatchEvent(event)
	{
		if (event.constructor != CustomEvent)
		{
			throw new Error("Invalid argument: event is not an instance of CustomEvent");
		}

		var type = event.type;

		if (events[type] != undefined)
		{
			for (var i = 0; i < events[type].length; i++)
			{
				events[type][i](event);
			}
		}
	}
}




/**
 * CustomEvent
 */
function CustomEvent(type, target)
{
	// Public
	this.type	= type;
	this.target	= target;
}




/**
 * Page
 * @inherits	EventDispatcher
 */
function Page()
{
	// Call the prototype's constructor
	EventDispatcher.call(this);

	// Private
	var self				= this,
		pageLoaded			= false,
		requestVariables	= [];

	// Public
	this.setQueryString			= setQueryString;
	this.getRequestVariables	= getRequestVariables;
	this.getCookies				= getCookies;
	this.setCookie				= setCookie;
	this.removeCookie			= removeCookie;

	this.queryString			= {};
	this.cookies				= {};

	// Constructor
	setPageLoadedListeners();
	getQueryString();
	parseRequest();
	getCookies();

	/**
	 * Checks if the DOM has loaded in Mozilla (Firefox), Opera 9 and Internet Explorer
	 * Source: http://dean.edwards.name/weblog/2006/06/again/#comment5338
	 */
	function setPageLoadedListeners()
	{
		// W3C event model compliant user agent
		if (document.addEventListener)
		{
			// Mozilla/Opera 9
			document.addEventListener("DOMContentLoaded", dispatchPageLoaded, false);

			// Every other browser (except for IE)
			window.addEventListener("load", dispatchPageLoaded, false);
		}

		// Internet Explorer
		if (window.attachEvent)
		{
			window.attachEvent("onload", dispatchPageLoaded);
		}
	}

	function dispatchPageLoaded(event)
	{
		if (pageLoaded == false)
		{
			pageLoaded = true;

			self.dispatchEvent(new CustomEvent(Page.LOADED, self));
		}
	}

	function getQueryString()
	{
		if (location.search != "")
		{
			var queryString = location.search.substring(1).split("&"),
				keyValuePair;

			for (var i = 0; i < queryString.length; i++)
			{
				keyValuePair = queryString[i].split("=");

				self.queryString[keyValuePair[0]] = keyValuePair[1];
			}
		}
	}

	function setQueryString()
	{
		var queryString = "?";

		for (var key in self.queryString)
		{
			queryString += key + "=" + self.queryString[key] + "&";
		}

		location.search = queryString.substring(0, queryString.length - 1);
	}

	/**
	 * Parse the URL into request variables
	 */
	function parseRequest()
	{
		var path	= unescape(location.pathname).split("/"),
			regEx	= /^[0-9]+$/;

		if (path[0] == "")
		{
			path.shift();
		}

		for (var i = path.length - 1; i != 0; --i)
		{
			if (regEx.test(path[i]) == false)
			{
				path.pop();
			}
		}

		requestVariables = path;
	}

	/**
	 * Get the request variables
	 * @return {Array} The request variables in an array, starting with the page name.
	 */
	function getRequestVariables()
	{
		return requestVariables;
	}

	function getCookies()
	{
		if (document.cookie != "")
		{
			var cookies			= document.cookie.split(";"),
				trimRegEx		= /^\s|\s$/g,
				keyValuePair	= null;

			for (var i = 0; i < cookies.length; i++)
			{
				keyValuePair = cookies[i].split("=");

				self.cookies[keyValuePair[0].replace(trimRegEx, "")] = keyValuePair[1];
			}
		}
	}

	function setCookie(name, value, expiration)
	{
		if (typeof expiration == "number")
		{
			var expirationDate = new Date();

			expirationDate.setMinutes(expirationDate.getMinutes() + expiration);

			expiration = "; expires=" + expirationDate.toUTCString();
		}
		else
		{
			expiration = "";
		}

		document.cookie = name + "=" + escape(value) + expiration;

		getCookies();
	}

	function removeCookie(name)
	{
		setCookie(name, "", -1);

		getCookies();
	}
}

// Inheritance
Page.prototype = new EventDispatcher();

// Static
Page.LOADED = "loaded";



function Socket()
{
	// Call the prototype's constructor
	EventDispatcher.call(this);


	// Private
	var self			= this,
		socket			= null,
		requestHeaders	= {};

	// Public
	this.request			= request;
	this.getStatus			= getStatus;
	this.getStatusText		= getStatusText;
	this.getResponseText	= getResponseText;
	this.getResponseXML		= getResponseXML;
	this.setRequestHeader	= setRequestHeader;
	this.getResponseHeader	= getResponseHeader;


	// Try XMLHttpRequest as a native object
	try
	{
		socket = new XMLHttpRequest();
	} 
	catch (exception)
	{
	}

	// Try XMLHttpRequest as an ActiveX object
	var xmlImplementations = ["MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0"];

	for (var i = 0; i < xmlImplementations.length; i++) 
	{
		try
		{
			socket = new ActiveXObject(xmlImplementations[i]);
		} 
		catch (exception)
		{}
	}


	/**
	 * Perform an HTTP request, using either the GET or POST method, passing along optional parameters.
	 * @param {String} URI The URI to retrieve.
	 * @param {Object} [parameters] Parameters to send along with the request.
	 * @param {Boolean} [asynchronous] Perform the request in an asynchronous manner, dispatching events to callback functions.
	 * @param {Boolean} [post] Use the POST method instead of GET.
	 * @return {Object} SocketResponse Returns a SocketResponse object.
	 * @method
	 */
	function request(URI, parameters, asynchronous, post)
	{
		// Check arguments
		if (typeof URI != "string")
		{
			throw new Error("Invalid argument: URI is missing or not a string");
		}

		if (post == true && (parameters == undefined || parameters == null))
		{
			throw new Error("Invalid argument: parameters is missing or not an object");
		}

		if (typeof asynchronous != "boolean")
		{
			asynchronous = true;
		}

		if (typeof post != "boolean")
		{
			post = false;
		}


		// If there's a request in progress, abort it
		if (socket.readyState != 0)
		{
			socket.abort();
		}


		var requestData = "";


		// If parameters where given...
		if (parameters != undefined && parameters != null) 
		{
			// ...and if it's an object, go through its properties and build up a valid (URI encoded) string for use in the request
			if (typeof parameters == "object")
			{
				// In a GET request, the requestData string is appended to the URI. However, the URI might already contain a query string
				if (post == false) 
				{
					// There's no query string, begin with a question mark
					if (URI.indexOf("?") == -1)
					{
						requestData = "?";
					}
					// There's an existing query string, to which the requestData string will be appended. Begin with an ampersand
					else 
					{
						requestData = "&";
					}
				}

				// Loop through the parameters and URI encode both the name and the value
				for (var parameter in parameters)
				{
					requestData += encodeURIComponent(parameter) + "=" + encodeURIComponent(parameters[parameter]) + "&";
				}

				// Remove the ampsersand at the end (as a result of the loop above)
				requestData = requestData.substring(0, requestData.length - 1);
			}
			else
			{
				throw new Error("Invalid argument: parameters is not an object");
			}
		}

		// Handle the request asynchronous by dispatching events
		socket.onreadystatechange = function()
		{
			switch (socket.readyState)
			{
				case 0:
					self.dispatchEvent(new CustomEvent(Socket.UNINITIALIZED, self));
					break;
				case 1:
					self.dispatchEvent(new CustomEvent(Socket.OPEN, self));
					break;
				case 2:
					self.dispatchEvent(new CustomEvent(Socket.SENT, self));
					break;
				case 3:
					self.dispatchEvent(new CustomEvent(Socket.RECEIVING, self));
					break;
				case 4:
					self.dispatchEvent(new CustomEvent(Socket.LOADED, self));
			}
		}

		// The POST request requires some additional headers
		if (post == true)
		{
			socket.open("POST", URI, asynchronous);
			socket.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			socket.setRequestHeader("Content-Length", requestData.length);
			socket.send(requestData);
		}
		else
		{
			socket.open("GET", URI + requestData, asynchronous);

			for (var name in requestHeaders)
			{
				socket.setRequestHeader(name, requestHeaders[name]);
			}

			socket.send(null);
		}
	}


	/**
	 * Returns the response status code.
	 * @return {Number} HTTP status code of the response (e.g. 200, 404, 500).
	 * @method
	 */
	function getStatus()
	{
		return socket.status;
	}

	/**
	 * Returns the response status description.
	 * @return {String} HTTP status description of the response (e.g. OK, Not Found, Internal Server Error).
	 * @method
	 */
	function getStatusText()
	{
		return socket.statusText;
	}

	/**
	 * Returns the response as a string.
	 * @return {String} Response to a request as a string.
	 * @method
	 */
	function getResponseText()
	{
		return socket.responseText;
	}

	/**
	 * Returns the response as an XML document.
	 * @return {Object} Response to a request as an XML document.
	 * @method
	 */
	function getResponseXML()
	{
		return socket.responseXML;
	}

	/**
	 * Sets a custum request header.
	 * @param {String} name Name of the request header.
	 * @param {String} value Value of the request header.
	 * @method
	 */
	function setRequestHeader(name, value)
	{
		requestHeaders[name] = value;
	}

	/**
	 * Returns the value of a response header.
	 * @param {String} name Name of a response header.
	 * @return {String} Value of a response header.
	 * @method
	 */
	function getResponseHeader(name)
	{
		return socket.getResponseHeader(name);
	}
}

// Inheritance
Socket.prototype = new EventDispatcher();

// Static
Socket.UNINITIALIZED	= "uninitialized";
Socket.OPEN				= "open";
Socket.SENT				= "sent";
Socket.RECEIVING		= "receiving";
Socket.LOADED			= "loaded";

// It's French
function getVousOuTu(vouvoyer, tutoyer)
{
	if (typeof tutoiement == "undefined" || tutoiement == false)
	{
		return vouvoyer;
	}

	return tutoyer;
}



/**
 * Controller
 */
var controller = new function()
{
	var self 				= this,
		parameters 			= {},
		pageObjectReference = new Page();

	this.setPageObject 			= setPageObject;
	this.getPageObject 			= getPageObject;
	this.setParameter 			= setParameter;
	this.removeParameter 		= removeParameter;
	this.getParameter			= getParameter;
	this.getParameters			= getParameters;
	this.registerCommonObject	= registerCommonObject;

	function setPageObject(pageObject)
	{
		if (pageObject instanceof Page)
		{
			pageObjectReference	= pageObject;
		}
	}

	function getPageObject()
	{
		return pageObjectReference;
	}

	function registerCommonObject(commonObject)
	{
		if (typeof commonObject != "function") 
		{
			throw new Error("Invalid argument: commonObject is missing or not a valid function");
		}

		pageObjectReference.addEventListener(Page.LOADED, commonObject, false);
	}

	function setParameter(parameterName, parameterValue)
	{
		if (typeof parameterName != "string") 
		{
			throw new Error("Invalid argument: parameterName is missing or not a string");
		}

		if (typeof parameterValue != "string")
		{
			throw new Error("Invalid argument: parameterValue is missing or not a string");
		}

		parameters[parameterName] = parameterValue;
	}

	function getParameter(parameterName)
	{
		if (typeof parameterName != "string") 
		{
			throw new Error("Invalid argument: parameterName is missing or not a string");
		}

		if (parameters[parameterName] != "undefined") 
		{
			return parameters[parameterName];
		}
		else
		{
			return false;
		}
	}

	function removeParameter(parameterName)
	{
		delete parameters[parameterName];
	}

	function getParameters()
	{
		return parameters;
	}
}