/**
 * Cookie-Klasse
 * 
 * @param {String} name Name des Cookies
 * @param {Number} expDays Gültigkeit in Tagen
 * @param {Function} warnFunc Funktion, die für das Erzeugen der Warnmeldung ausgeführt werden soll
 * @author Thomas Scholz
 * @created 22.12.08
 */
function Cookie(name, expDays, warnFunc)
{
	/** Parameter-Separator */
	this.PARAMETER_SEPARATOR = "|";
	/** Schlüssel-Wert-Separator */
	this.KEY_VALUE_SEPARATOR = ":";
	/** Parameter-Separator (zweite Ebene) */
	this.PARAMETER_SEPARATOR_2 = ",";
	/** Schlüssel-Wert-Separator (zweite Ebene) */
	this.KEY_VALUE_SEPARATOR_2 = "#";
	/** Gültigkeit in Tagen (Standardwert) */
	this.DEFAULT_EXP_DAYS = 365;
	
	/** Name des Cookies */
	var _name = name;
	/** aktuelles Datum */
	var _expDate = new Date();
	_expDate.setTime(_expDate.getTime() + (((isNaN(expDays)) ? this.DEFAULT_EXP_DAYS : expDays) * 1000 * 60 * 60 * 24));
	/** Ablaufdatum */
	var _expires = _expDate.toGMTString();
	/** */
	var is_warning = false;
	
	/**
	 * Dieser Hack ermöglicht die Verwendung öffentlicher Variable in priaten Methoden,
	 * was sonst aufgrund eines Fehlers in der ECMA-Spezifikation nicht funktioniert
	 */
	var self = this;
	
	
	// Gleich mal schauen, ob Cookies aktiviert sind
	if (!test())
		alert("Bitte erlauben Sie Cookies für diese Seite!");
			
	//----------------------//
	// öffentliche Methoden //
	//----------------------//
	
	/**
	 * Zeigt alle gesetzten Werte in einer Alertbox an
	 */
	this.alert = function()
	{
		if (!test())
			alert("Cookies deaktiviert!");
		else {
			var text = "Name:\t" + _name + "\nVerfall:\t" + _expDate.toLocaleString() + "\nInhalt:\n\n";
			var values = getValues();

			for (var key in values)
				text += key + " = " + values[key] + "\n";

			alert(text);
		}
	}

	/**
	 * Setzt einen Wert für einen Schlüssel und speichert den Cookie
	 *
	 * @param {String} key Schlüssel
	 * @param {String} val Wert
	 * @param {Boolean} noforce vorhandenen Wert nicht überschreiben
	 */
	this.set = function(key, val, noforce)
	{
		if (test()) {
			var values = getValues();
			var result = "";

			// Abbruch, wenn Schlüssel bereits vorhanden
			// und Überschreiben nicht erlaubt ist
			if (noforce && values[key])
				return;

			// Wert für übergebenen Schlüssel setzen
			values[key] = val;

			// Wertearray in String konvertieren
			for (var i in values)
				result += this.PARAMETER_SEPARATOR + i + this.KEY_VALUE_SEPARATOR + values[i];

			store(dropSeparator(result, this.PARAMETER_SEPARATOR));
		}
	}
	
	/**
	 * Fügt ein Schlüssel-/Wertepaar zweiter Ordnung ein
	 *
	 * @param {String} key1 Schlüssel1
	 * @param {String} key2 Schlüssel2
	 * @param {String} val2 Wert2
	 * 
	 * TODO: Einfügen ohne key2 implementieren
	 */
	this.add = function(key1, key2, val2)
	{
		if (test()) {
			this.set(
				key1, 
				dropSeparator(
					this.get(key1) + 
					this.PARAMETER_SEPARATOR_2 + encodeURIComponent(key2) +
					((val2)
						? this.KEY_VALUE_SEPARATOR_2 + encodeURIComponent(val2)
						: ""
					), 
					this.PARAMETER_SEPARATOR_2
				)
			);
			//this.alert();
		}
	}

	/**
	 * Gibt einen Wert eines Parameters zurück
	 * 
	 * @param {String} key1 Schlüssel1
	 * @param {String} key2 Schlüssel2 (optional)
	 * @return {String}
	 */
	this.get = function(key1, key2)
	{
		return find(key1, key2, true);
	}

	/**
	 * Gibt zurück, ob ein Parameter in einem Cookie existiert
	 * 
	 * @param {String} key1 Schlüssel1
	 * @param {String} key2 Schlüssel2 (optional)
	 * @return {Boolean}
	 */
	this.is = function(key1, key2)
	{
		return find(key1, key2, false);
	}

	/**
	 * Entfernt ein Schlüssel-/Wertepaar
	 * 
	 * @param {String} key1 erster Schlüssel
	 * @param {String} key2 zweiter Schlüssel (optional)
	 * @return {Boolean}
	 * 
	 * TODO: Löschen von key1 implementieren
	 */
	this.drop = function(key1, key2)
	{
		var result	= false;
		var values1	= this.get(key1);

		if (key2) {
			var values2		= getValues2(values1);
			var _values2	= "";
			var key, val;

			for (var key in values2) {
				val = values2[key];
				
				// Schlüssel2 = gesuchter Schlüssel => überspringen (=löschen)
				if (decodeURIComponent(key) == decodeURIComponent(key2))
					result = true;
					
				// ansonsten Schlüssel-/Wertepaar übernehmen
				else {
					_values2 += this.PARAMETER_SEPARATOR_2 + key + ((val != "") 
						? this.KEY_VALUE_SEPARATOR_2 + val
						: ""
					);
				}
			}
				
			this.set(key1, dropSeparator(_values2, this.PARAMETER_SEPARATOR_2));
			//this.alert();
		}

		
		return result;
	}
	
	/**
	 * Löscht den Inhalt eines Cookies. Liegt ein Begriffsarray
	 * vor, wird der Inhalt nur gelöscht, wenn der Cookie einen 
	 * der Begriffe enthält.
	 * 
	 * @param {Array} terms zu suchende Begriffe
	 */
	this.clear = function(terms)
	{
		if (terms) {
			var content = getContent();
			for (var i = 0, ii = terms.length; i < ii; i++) {
				if (content.indexOf(terms[i]) > 0) {
					store("");
					return;
				}
			}
		}
		else
			store("");
	}
	
	//------------------//
	// private Methoden //
	//------------------//

	/**
	 * Gibt zurück, ob mit Cookies gearbeitet werden kann
	 *
	 * @return {Boolean}
	 */
	function test()
	{
		// Schließt auch den Fall ein, dass die cookieEnabled-Methode
		// nicht unterstützt wird, dann gilt automatisch "false"
		return (navigator.cookieEnabled == true) ? true : false;
	}

	/**
	 * Gibt den Inhalt eines Cookies zurück
	 * 
	 * @return {String}
	 */
	function getContent()
	{
		var result = document.cookie;

		if (result == name)
			result = "";
		else if (result.indexOf(_name) >= 0) {
			// Zeichen vor dem Wert des gesuchten Cookies entfernen
			result = result.split(_name + "=")[1];
			// Zeichen nach dem Wert des gesuchten Cookies entfernen
			result = result.split(";")[0];
		}

		return result;
	}
	
	/**
	 * Gibt alle Schlüssel-/Wertepaare zurück
	 *
	 * @return {Array}
	 */
	function getValues()
	{
		var content	= getContent();
		var result	= {};
		var i, part, pos;

		if (content.length > 0) {
			// Cookieteile in Array einlesen
			content = content.split(self.PARAMETER_SEPARATOR);
			// Cookieteile in Schlüssel und Wert trennen
			for (i = 0, ii = content.length; i < ii; i++) {
				pos = content[i].indexOf(self.KEY_VALUE_SEPARATOR);
				result[content[i].slice(0, pos)] = content[i].slice(pos + 1);
			}
		}

		return result;
	}
	
	/**
	 * Gibt alle Schlüssel-/Wertepaare eines Parameters zweiter Ordnung zurück
	 *
	 * @param {String} content Wert eines Schlüssels
	 * @return {Array}
	 */
	function getValues2(content)
	{
		var result	= {};
		var i, pos, key, val;

		if (content.length > 0) {
			content = content.split(self.PARAMETER_SEPARATOR_2);

			for (i = 0, ii = content.length; i < ii; i++) {
				key = content[i];
				val = "";
				pos = key.indexOf(self.KEY_VALUE_SEPARATOR_2);
				
				// wenn ein Wert existiert, diesen separieren
				if (pos > -1) {
					val = key.slice(pos + 1);
					key = key.slice(0, pos);
				}

				result[key] = val;
			}
		}

		return result;
	}

	/**
	 * Sucht einen Parameter und gibt sein Vorhandensein
	 * oder seinen Wert zurück
	 * 
	 * @param {String} key1 Schlüssel1
	 * @param {String} key2 Schlüssel2
	 * @param {Boolean} getval Wert zurückgeben
	 * @return {Mixed}
	 */
	function find(key1, key2, getval)
	{
		var values1 = getValues();
		
		if (values1[key1]) {
			if (!key2)
				return (getval) ? values1[key1] : true;
			else {
				for (var key in getValues2(values1[key1]))
					if (decodeURIComponent(key) == decodeURIComponent(key2))
						return (getval) ? values2[key] : true; 
			}
		}
		
		return (getval) ? "" : false;
	}
	
	/**
	 * Speichert den Cookie mit den übergebenen Werten
	 *
	 * @param {String} values Cookiewerte
	 */
	function store(values)
	{
		//alert(document.cookie + "\n" + values);
		document.cookie = _name + "=" + values + "; expires=" + _expires + "; path=/";
		
		if (!document.cookie && !is_warning) {
			is_warning = true;
			
			// wenn eine Funktion für die Warnmeldung übergeben wurde, diese ausführen
			if (warnFunc && typeof warnFunc == "function")
				warnFunc();
			// ansonsten Standardmeldung anzeigen
			else
				alert("Bitte erlauben Sie Cookies für diese Seite!");
		}
	}
	
	/**
	 * Entfernt den führenden Separator aus einer Schlüssel-/Wertekette
	 * 
	 * @param {String} content Schlüssel-/Wertekette
	 * @param {String} separator Separator
	 */
	function dropSeparator(content, separator)
	{
		if (content.indexOf(separator) == 0)
			return content.slice(1);
		else
			return content;
	}
}
