if (typeof Object.extend != "function") {
	if (typeof jQuery == "function") { // make it work with jQuery
		Object.extend = jQuery.extend;
	} else {
		throw new Error("DCookie.js: You must load DLib.js!");
	}
}

/*	When you create an instance of the DCookie class, you do not automatically create a cookie on the web page.
	To do that you must call the save method or the saveSingleValue method.
	Setting
	document.cookie = "test; path=<some-path>";
	does not generate a cookie with the name 'test' and no value, but a	cookie with no name (the empty string)
	and the value 'test'.	*/

function DCookie(name, config, win) {
	this.name = "anonymousDCookie"; // it is strongly discouraged to use the empty string for the name! Opera does not create a cookie with the empty string as the name.
	this.config = Object.extend(config || {}, DCookie.defaultConfig, false);
	if (typeof name == "string") {
		var found = /[;=]/g.exec(name);
		if (found != null) {
			throw new Error('DCookie.js: You cannot create an instance of the DCookie class with the character \'' + found[0] + '\' in the name!');
		}
		this.name = name;
	}
	// the win property gives you the possibility to create and read cookies in another window/frame.
	this.win = (win != null && win.document != null) ? win : window;
	this.expiryDate = (this.config.expiryDate && this.config.expiryDate.constructor == Date) ? this.config.expiryDate : null;
	this.maxAge = NaN;
	if (this.expiryDate) {
		var ms = this.expiryDate.getTime() - new Date().getTime();
		if (ms > 0) {
			this.maxAge = parseInt(ms / 1000, 10);
		}
	} else {
		var maxAge = parseInt(this.config.maxAge, 10);
		this.maxAge = (maxAge <= 0) ? NaN : maxAge;
	}
	this.path = (typeof this.config.path == "string" && this.config.path != "") ? this.config.path : DCookie.getDefaultPath(this.win);
	this.domain = (typeof this.config.domain == "string" && this.config.domain != "") ? this.config.domain : DCookie.getDefaultDomain(this.win);
	this.secure = (typeof this.config.secure == "boolean") ? this.config.secure : false;
}

DCookie.defaultConfig = {
	del1: "$&$",
	del2: "$:$",
	del3: "$#$",
	preSyl: "$DCookie$",
	expiryDate: null, // must be a date or use maxAge instead
	maxAge: NaN, // measured in seconds
	path: null, /* By default the cookie is only accessible to pages in the same directory as the page that created it and any subdirectories of that directory. This means that the path by default is the pathname of the location object except for a possible filename. */
	domain: null, /* By default the domain is the hostname property of the location object of the window for the cookie instance. The browser automatically prevents you from setting the domain to a domain that is not a parent domain of	the hosting webserver. */
	secure: false /* If secure the cookie is only visible if the page is visited using a secure protocol like https. Note that you can always create a secure cookie even though the page is not using a secure connection!	*/
}

DCookie.removeAll = function(win, path, domain, excludes) {
	function isExcluded(name) {
		if (excludes != null && excludes.constructor == Array && typeof name == "string" && name != "") {
			for (var j = 0, l = excludes.length; j < l; j++) {
				var exclude = excludes[j];
				if (typeof exclude == "string" && exclude == name) {
					return true;
				}
			}
		}
		return false;
	}
	if (win == null || win.document == null) {
		win = window;
	}
	var cookies = win.document.cookie.split("; ");
	for (var i = 0, l = cookies.length; i < l; i++) {
		var arr = cookies[i].split("=");
		if (arr.length == 1) {
			arr.unshift(""); // remove cookies with no name
		}
		if (arr.length > 1) {
			if (!isExcluded(arr[0])) {
				// no need for setting secure - if this connection is secure the cookie will be removed (secure or not)
				var cookie = new DCookie(arr[0], null, path, domain, null, win);
				cookie.remove();
			}
		}
	}
}

// returns null if a cookie with the specified name argument doesn't exist
DCookie.readCookie = function(name, win) {
	if (win == null || win.document == null) {
		win = window;
	}
	if (typeof name == "string" && win.document.cookie) {
		// all browsers automatically puts a space after the semicolon
		var cookieArray = win.document.cookie.split("; ");
		var value;
		for (var i = 0, l = cookieArray.length; i < l; i++) {
			var arr = cookieArray[i].split("=");
			// some forget to encode the value and just uses the equal sign (=) in the value :-/
			if (arr.length > 1 && arr[0] == name) {
				value = "";
				for (var j = 1, len = arr.length; j < len; j++) {
					var sep = (j > 1) ? "=" : "";
					value += sep + arr[j];
				}
				break;
			}
		}
		if (value != null) {
			return decodeURIComponent(value);
		}
	}
	return null;
}

/*	The default path is the pathname of the location object of the window
	object for the cookie except for a possible filename. */
DCookie.getDefaultPath = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var defaultPath = win.location.pathname;
	/* remove possible trailing slash or possible filename	*/
	var dotIndex = defaultPath.lastIndexOf(".");
	// if there is a dot assume the last dot is part of a filename in the pathname
	if (dotIndex > -1) {
		defaultPath = defaultPath.substring(0, dotIndex);
		var slashIndex = defaultPath.lastIndexOf("/");
		if (slashIndex > -1 && slashIndex < dotIndex) {
			defaultPath = defaultPath.substring(0, slashIndex + 1);
		}
	}
	return defaultPath;
}

/*	Method to ensure a visible path for the cookie.
	The default path is the pathname of the location object of the window
	object for the cookie except for the last slash and the possible filename. */
DCookie.isVisiblePath = function(path, win) {
	if (win == null || win.document == null) {
		win = window;
	}
	/*	Setting the path to the empty string makes no sense. Then no pages on your web server is
	 *	associated with your cookie. Make sure you don't make the cookie inaccessible to the page
	 *	- path must be contained in the beginning of defaultPath.	 */
	if (typeof path == "string" && path != "") {
		var defaultPath = DCookie.getDefaultPath(win);
		return (defaultPath.toLowerCase().indexOf(path.toLowerCase()) == 0);
	}
	return false;
}

DCookie.getDefaultDomain = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}	
	return win.document.domain; // the default value of document.domain is location.hostname
}

/*	Method to ensure a valid domain for the cookie.
	The domain must be contained in the end of location.hostname.
	If the hostname property does not contain at least two dots the domain cannot be changed.	*/
DCookie.isValidDomain = function(domain, win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var defaultDomain = DCookie.getDefaultDomain(win);
	var hostArray = defaultDomain.split(".");
	if (hostArray.length < 3) {
		return false;
	}
	if (typeof domain != "string" || domain == "") {
		return false;
	}
	var domArray = domain.split(".");
	if (domArray.length < 2 || domArray.length > hostArray.length) {
		return false;
	}
	for (var i = domArray.length - 1; i >= 0; i--) {
		// the new domain may start with a dot in which case domArray[i] is the empty string
		if (domArray[i] != "" && domArray[i] != hostArray[i + hostArray.length - domArray.length]) {
			return false;
		}
	}
	return true;
}

DCookie.prototype.toString = function() {
	var s = "[object DCookie]: " + this.name;
	s += "\nsecure: " + this.getSecure();
	s += "\ndomain: " + this.getDomain();
	s += "\npath: " + this.getPath();
	s += "\nexpires: " + ((this.expiryDate != null) ? this.expiryDate : "at end of session");
	return s;
}

DCookie.prototype.save = function(preserveOtherParameters) {
	if (typeof preserveOtherParameters != "boolean") {
		preserveOtherParameters = true;
	}
	var cookieValue = "";
	if (preserveOtherParameters) {
		var value = this.getValue();
		if (value != null) {
			var valuesArray = value.split(this.config.del1);
			var arr;
			for (var i = 0, l = valuesArray.length; i < l; i++) {
				arr = valuesArray[i].split(this.config.del2);
				if (arr.length == 2) {
					if (typeof this[this.config.preSyl + arr[0]] == "undefined") {
						cookieValue += arr[0] + this.config.del2 + arr[1];
					}
				}
			}
		}
	}
	for (var p in this)	{
		if (p.indexOf(this.config.preSyl) == 0 && typeof this[p] == "string") {
			if (cookieValue != "") {
				cookieValue += this.config.del1;
			}
			cookieValue += p.substring(this.config.preSyl.length) + this.config.del2 + this[p];
		}
	}
	cookieValue = this.name + "=" + cookieValue;
	cookieValue += this.getCookieSettings();
	this.win.document.cookie = cookieValue;
}

// The value is not saved in the DCookie instance and does not remove existing parameters!
DCookie.prototype.saveSingleValue = function(cookieValue, doEncode) {
	if (typeof cookieValue == "string" && cookieValue != "") {
		if (typeof doEncode != "boolean") {
			doEncode = true;
		}
		if (doEncode) {
			cookieValue = this.name + "=" + encodeURIComponent(cookieValue);
		} else {
			cookieValue = this.name + "=" + cookieValue;
		}
		cookieValue += this.getCookieSettings();
		this.win.document.cookie = cookieValue;
	}
}

DCookie.prototype.getCookieSettings = function() {
	var settings = "";
	if (this.expiryDate != null) {
		settings += "; expires=" + this.expiryDate.toUTCString();
	}
	if (!isNaN(this.maxAge)) {
		settings += "; max-age=" + this.maxAge;
	}
	if (this.path != null) {
		settings += "; path=" + this.path;
	}
	/*	If the domain does not contain a dot, Firefox and IE won't save the cookie.
		But they will display the domain without the dot when viewing the cookie in the browser.
		In Firefox domain must not equal location.hostname.	*/
	if (this.domain != null && this.domain.indexOf(".") > -1 && this.domain != location.hostname) {
		settings += "; domain=" + this.domain;
	}
	if (this.secure) {
		settings += "; secure";
	}
	return settings;
}

/*	For a given document cookies must have the same name, the same path and the same domain to be considered the same.
 *	It is not enough that they have the same name. IE seems to ignore the path when the expiry date is in the past.
 *	Note that this method cannot be named 'delete' (even though a better name than 'remove') because
 *	'delete' is a reserved identifier.
 *	Also note that you can only remove a cookie if you know the path and domain with which it was created!
 *	Furthermore if the cookie is secure you can only remove it if the current connection is secure.
 *	But you can remove an unsecure cookie using a secure connection!
 *	Be aware that neither the path, the domain nor the secure property can be read in the cookie in any way!
 */
DCookie.prototype.remove = function() {
	var maxAge = this.maxAge;
	var expiryDate = this.expiryDate;
	var aYearAgo = new Date();
	aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
	this.expiryDate = aYearAgo;
	this.maxAge = 0;
	this.save(false);
	this.expiryDate = expiryDate;
	this.maxAge = maxAge;
}

DCookie.prototype.getValue = function() {
	return DCookie.readCookie(this.name, this.win);
}

DCookie.prototype.getParameter = function(name) {
	if (typeof name == "string" && name != "") {
		var value = this.getValue();
		if (value != null) {
			var valuesArray = value.split(this.config.del1);
			for (var i = 0, l = valuesArray.length; i < l; i++) {
				var arr = valuesArray[i].split(this.config.del2);
				if (arr.length == 2) {
					if (arr[0] == name) {
						return decodeURIComponent(arr[1]);
					}
				}
			}
		}
	}
	return null;
}

/* Returns a parameter as an array, if the parameter was saved as an array. */
DCookie.prototype.getParameterValues = function(name) {
	var arrayString = this.getParameter(name);
	if (arrayString != null) {
		// if the delimiter is not found in the string, split just returns an array with one entry; the value of the string.
		return arrayString.split(this.config.del3);
	}
	return null;
}

DCookie.prototype.removeParameter = function(name) {
	this.setParameter(name, null);
}

/*	Method for assigning a name/value pair to the cookie.
	Note that the cookie instance must be saved before the parameter is
	actually written to the cookie in the web page. Setting a parameter to null
	removes it.	*/
DCookie.prototype.setParameter = function(name, value) {
	if (typeof value != "string" && (value != null || typeof value == "undefined")) {
		value = "" + value;
	}
	// if the parameter already exists it is overwritten
	this[this.config.preSyl + name] = (value != null) ? encodeURIComponent(value) : value;
}

/* Method for saving an array as a parameter.	*/
DCookie.prototype.setParameterValues = function(name, valueArray) {
	if (valueArray != null && valueArray.constructor == Array) {
		this.setParameter(name, valueArray.join(this.config.del3));
	}
}

DCookie.prototype.setExpiryDate = function(expiryDate) {
	if (expiryDate != null && expiryDate.constructor == Date) {
		this.expiryDate = expiryDate;
		var ms = this.expiryDate.getTime() - new Date().getTime();
		if (ms > 0) {
			this.maxAge = parseInt(ms / 1000, 10);
		} else {
			this.maxAge = 0;
		}
	}
}

/*	Note that it is possible to set a cookie that is invisible to the current page/location!	*/
DCookie.prototype.setPath = function(path) {
	if (typeof path == "string" && path != "") {
		this.path = path;
	}
}

/*	Note that you can only set the domain to a parent domain of the current location!
	If you wish to validate your value use DCookie.validateDomain.	*/
DCookie.prototype.setDomain = function(domain) {
	if (typeof domain == "string" && domain != "") {
		this.domain = domain;
	}
}

/*	If the current location is not secure the cookie will be invisible to this page/location,
	when secure is set to true.
	If secure is not specified, the cookie is visible to any type of connection.	*/
DCookie.prototype.setSecure = function(secure) {
	if (typeof secure == "boolean") {
		this.secure = secure;
	}
}