window.kgst = window.kgst || (function() {
	var jLog = null,
		consoleAvailable = (typeof console != 'undefined' && console.log),
		Stopwatch = function() {
			this.startTime = +new Date();
		}
	
		Stopwatch.prototype = {
			reset: function() {
				this.startTime = +new Date();
			},
			getTime: function(resetAfter) {
				var t = ((+new Date()) - this.startTime);
				if (resetAfter) this.reset();
				return t;
			}
		};

	return {
		DEFAULT_POPUP_SETTINGS: 'location=yes,menubar=no,resizable=yes,scrollbars=no,status=yes,height=500,width=1000',
		PRINT_URL: '/meinKGSt/adminVR?rmtparams=cm10YWM9YWRtaW5Ja29uJnJtdGF1PWh0dHAlM0ElMkYlMkZsb2NhbGhvc3QlM0E1Nzg4MCUyRmtn_c3Qtd2ViYXBwJTJGYWRtaW5VYmVyc2NocmlmdFp1b3JkbnVuZyUyRnByaW50',
		IS_LOCAL_DEV: !!top.location.href.match(/http:\/\/[^\/\d:]*:\d+\/kgst-webapp\//),
		DEBUG: false,
		LOGGER: {
			log: function(s) {
				if (!kgst.DEBUG) return;
				if (consoleAvailable) {
					console.log.apply(this, arguments);
					return;
				} else if (!jLog) {
					jLog = $('<textarea ondblclick="this.value=\'\'" id="treeLogger" style="font-size:10px;font-family:tahoma;color:red;height:100px;width:180px"></textarea>');
					$('#kgst-logo-subline').html('').append(jLog);
				}
				jLog.append( '** ' + s + "\n<br/>" );
			}
		},
		Stopwatch: Stopwatch
	};

})();

kgst.Tools = {
	clearInput: function(field) {
        if (field.defaultValue==field.value)
            field.value = "";
    },


	/*
	 * Adds a nocache parameter to the given URL.
	 * @param {string} url
	 * @return {string} url with ?/&x=1234..
	 */
	antiCacheURL: function(url) {
		var time = (+new Date),
			c = (url.indexOf('?')>=0)? '&' : '?',
			param = c + 'x=' + time.toString(16);

		return url + param;
	},

	/**
	 * Jumps to the login page.
	 */
	jumpToLogin: function() {
		top.location.href = '/login/anmelden.dot';
	},

    fillInput: function(field) {
        if (field.value == "")
            field.value = field.defaultValue;
    },

    updateProvinces: function(province) {
        var provinceSelect = province;
        var country = document.getElementById("country").value;
        /* Delete old options */
        provinceSelect.length = 0;
        /* Add new options */
        if (country != "") {
            var newProvinceID = "provinces-for-" + country;
            var newLabelID = "province-label-" + country;
            var newSelectOptions = document.getElementById(newProvinceID).options;
            for (i=0; i < newSelectOptions.length; i++) {
                provinceSelect.options[provinceSelect.options.length] = new Option(newSelectOptions[i].text,newSelectOptions[i].value,newSelectOptions[i].name);
            }
            /* Changing the Label */
            document.getElementById("province-name").innerHTML = document.getElementById(newLabelID).innerHTML;
        }
    },

    updateDistricts: function(province, district) {
        var districtSelect = district;
        var currentID = province.value;
        /** Delete old district options **/
        districtSelect.length = 0;
        if (currentID != "") {
            var newDistrictID = "districtId-for-" + currentID;
            var newSelectOptions = document.getElementById(newDistrictID).options;
            for (i=0; i < newSelectOptions.length; i++) {
                districtSelect.options[districtSelect.options.length] = new Option(newSelectOptions[i].text,newSelectOptions[i].value,newSelectOptions[i].name);
            }
            
        }
    //alert(districtSelect + ":" + currentID);
    },

    clearDistricts: function(district) {
        var districtSelect = district;
        districtSelect.length = 0;
    },
    
    urlencode: function(str) {
        // http://kevin.vanzonneveld.net
        // +   original by: Philip Peterson
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: AJ
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +      input by: travc
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Lars Fischer
        // %          note 1: info on what encoding functions to use from: http://xkr.us/articles/javascript/encode-compare/
        // *     example 1: urlencode('Kevin van Zonneveld!');
        // *     returns 1: 'Kevin+van+Zonneveld%21'
        // *     example 2: urlencode('http://kevin.vanzonneveld.net/');
        // *     returns 2: 'http%3A%2F%2Fkevin.vanzonneveld.net%2F'
        // *     example 3: urlencode('http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a');
        // *     returns 3: 'http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a'

		// The histogram is identical to the one in urldecode.
		// modified LeP: separated keys/vals to avoid false malware detection by McAfee GW-Edition
        var histoKeys = ["'",'(',')','*','~','!','%20','\u00DC','\u00FC','\u00C4','\u00E4','\u00D6','\u00F6','\u00DF','\u20AC','\u0081','\u201A','\u0192','\u201E','\u2026','\u2020','\u2021','\u02C6','\u2030','\u0160','\u2039','\u0152','\u008D','\u017D','\u008F','\u0090','\u2018','\u2019','\u201C','\u201D','\u2022','\u2013','\u2014','\u02DC','\u2122','\u0161','\u203A','\u0153','\u009D','\u017E','\u0178'],
			histoVals = ['%27','%28','%29','%2A','%7E','%21','+','%DC','%FC','%D4','%E4','%D6','%F6','%DF','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F'],
			histogram = {},
			unicodeStr = '',
			hexEscStr = '',
			ret = (str+'').toString(),
			replacer = function(search, replace, str) {
				var tmp_arr = [];
				tmp_arr = str.split(search);
				return tmp_arr.join(replace);
			};

		for (var i=0; i<histoKeys.length; i++) {
			histogram[histoKeys[i]] = histoVals[i];
		}

        // Begin with encodeURIComponent, which most resembles PHP's encoding functions
        ret = encodeURIComponent(ret);

        for (unicodeStr in histogram) {
            hexEscStr = histogram[unicodeStr];
            ret = replacer(unicodeStr, hexEscStr, ret); // Custom replace. No regexing
        }

        // Uppercase for full PHP compatibility
        return ret.replace(/(\%([a-z0-9]{2}))/g, function(full, m1, m2) {
            return "%"+m2.toUpperCase();
        });
    },

    highlightInvalidElement: function(element) {
        element.style.border ="1px solid red";
        element.style.color = "red";
    },

	updateHitsPerPage: function(hitsPerPage) {
		var hpp_s = document.getElementById('hitsPerPage_s');
		if (hpp_s) {
			hpp_s.value = hitsPerPage;
		}
    },


	/**
	 * Makes a table (its TRs) selectable (*** EXPERIMENTAL ***)
	 * @param {String} tableSelector the jQuery selector of the table
	 * @todo do sensible stuff, add parameter for hidden field(s) etc.
	 */
	makeSelectable: function(tableSelector) {
		var console = (typeof console=='undefined')? {log:function(){}} : console;

		$(tableSelector).selectable({
			filter: 'tr',
			cancel: 'a',
			selecting: function(e,ui) {
				console.log('selecting %o', ui.selecting);
			},
			selected: function(e,ui) {
				var tr = ui.selected;
				console.log(e);
				console.log(ui);
				if (e.ctrlKey && $(tr).hasClass('previouslySelected')) {
					$(tr).removeClass('ui-selected').removeClass('previouslySelected');
				} else {
					$(tr).addClass('previouslySelected');
				}
			},
			unselected: function(e,ui) {
				var tr = ui.unselected;
				console.log('unselected %o', ui.unselected);
				$(tr).removeClass('previouslySelected');
			}
		});
	},
	

	form2action: function(f) {
		var formGetURL = $('a.formURL:first', f).attr('href') || 0,
			dataParams = $(f).serialize(),
			match;
		
		if (!( match = formGetURL.match(/^(.*rmtparams=)(.*)$/) )) return false;

		var baseURL = match[1],
			base64encodedParams = match[2],
			base64decodedParams = decodeBase64(base64encodedParams);

		if (!( match = base64decodedParams.match(/rmtau=([^&]*)/) )) return false;

		var encodedURL = match[1],
			decodedURL = decodeURIComponent(encodedURL),
			PARAM_CHAR = (decodedURL.indexOf('?')<0)? '?' : '&',
			newURL = decodedURL + PARAM_CHAR + dataParams,
			newURLEncoded = encodeURIComponent(newURL),
			newBase64DecodedParams = base64decodedParams.replace(/rmtau=[^&]*/, 'rmtau='+newURLEncoded),
			newBase64EncodedParams = encodeBase64(newBase64DecodedParams),
			newActionURL = baseURL + newBase64EncodedParams;


		return newActionURL;
	},

	
	
	/**
	 * Opens a popup with the given name.
	 * @return true if successful, otherwise false
	 */
	openWertePopup: function(name, f) {
		f.method = 'post';

		try {
			var url = kgst.Tools.form2action(f);
			if (!url) throw 'invalid url';
			var popup = window.open(url, name||'werteErfassen', kgst.DEFAULT_POPUP_SETTINGS);
			popup.focus()
			return false;
		} catch (e) {
			alert(e);
			return true;
		}
	},
	
	openPopup: function(name, a) {
		try {
			if (!a || !a.href) throw 'invalid url';
			var popup = window.open(a.href, name||'ikonPopup', kgst.DEFAULT_POPUP_SETTINGS);
			popup.focus()
			return false;
		} catch (e) {
			alert(e);
			if (a) {
				a.target = '_blank';
			}
			return true;
		}
	},
	
	/**
	 * Translates a given dotCMS url into a desciptor object like {controller:'abc',action:'def',id:'123}.
	 * @params {string} url
	 * @return {object|null}
	 */
	url2object: function(url) {
		if (!/rmtparams=(.*)/.exec(url)) return null;
		var decodedURL = decodeURIComponent(decodeBase64(RegExp.$1)) || '';
		var matches = decodedURL.match(/kgst-webapp\/([^\/]+)\/([^\/]+)\/(\d*)/);
		if (!matches || matches.length < 4) return null;
		return {
			controller: matches[1],
			action: matches[2],
			id: matches[3]
		};
	},

	/**
	 * Deletes a Jobangebot's PDF attachment.
	 * @param {String} DEL_PDF_URL the ajax url for the delete call
	 * @return false (always)
	 */
	deleteJobangebotPdfAnhang: function(DEL_PDF_URL) {
		if (!confirm('Möchten Sie die PDF-Datei wirklich löschen?')) return false;
		var jDiv = $('#delPdfDiv');
		jDiv.html('<img src="/kgst-webapp/images/spinner.gif" alt="Bitte warten" />');
		$.getJSON(DEL_PDF_URL, {}, function(data) {
			data = data || {'error':'Es ist ein Fehler aufgetreten.'}
			if (data.error) {
				jDiv.html(data.error).css('color','red');
			} else {
				jDiv.html(data.success).css('color','green');
				$('#pdfNotice').fadeOut(400);
			}
		});
		return false;
	},

	/**
	 * Extends the (dotcms-generated) breadcrumb with LIs of a given list.
	 * @param {String} listID the id of the items to add to the BC
	 * @param {String} baseURL (optional) URL for the previously non-linked last breadcrumb item.
	 */
	extendBreadcrumb: function(listID, baseURL) {
		var jBCDiv = $('#breadcrumb'),
			jBC = $('#breadcrumb ul:first'),
			jLastLI = (jBC.length)? $('li:last', jBC) : 0,
			jMoreItems = $('#'+listID + ' li');
		
		if (!jLastLI || !jMoreItems.length) return;

		var sepImg = $('img:first',jBC).get(0) || $('<img alt="ct_img" src="/global/images/template/breadcrumb-seperator.gif"/>')[0],
			INIT_HEIGHT = jBCDiv.height();
			
		if (baseURL && !$('a',jLastLI).length) {
			var jA = $('<a>').attr('href', baseURL).html( jLastLI.html() );
			jLastLI.html(jA);
		}

		var prevLI = jLastLI[0];
		jMoreItems.each( function(i,li) {
			prevLI.appendChild( sepImg.cloneNode(1) );
			jBC.append(li);
			prevLI = li;
		});
		
		var fixRuns = 100,
			liIndex = -1,
			newHeight,
			REDUCE_STEP = 13,
			totalBytes = 0,
			orderedItems = [];

		jMoreItems.each( function(i, li) {
			var jLI = $(li),
				len = (jLI.text()||'').length;
			totalBytes += len;
			orderedItems[orderedItems.length] = {
				len: len,
				cycles: 0,
				jLI: jLI
			};
		});

		orderedItems = orderedItems.sort(function(a,b) {
			return (a.len < b.len)? 1 : -1;	// rev-sort by length
		});

		$.each(orderedItems, function(i,o) {
			o.cycles = Math.round( (o.len/totalBytes) * (totalBytes/REDUCE_STEP) );
		});

		// console.log('orderedItems: %o', orderedItems);

		var itemIndex = -1,
			cyclesLeft = 0,
			elem = null,
			html = '';

		while(--fixRuns > 0 && (newHeight=jBCDiv.height()) > INIT_HEIGHT) {
			if (!cyclesLeft) {
				itemIndex = (++itemIndex) % orderedItems.length;
				var item = orderedItems[itemIndex];
				cyclesLeft = item.cycles;
				elem = item.jLI[0].getElementsByTagName('a')[0] || item.jLI[0];
			}
			cyclesLeft--;

			html = elem.innerHTML;

			// console.log('itemIndex: %n, cycles: %n, elem: %o', itemIndex, cyclesLeft, elem);

			if (html.length < REDUCE_STEP) continue;

			html = html.substring(0, html.length-REDUCE_STEP) + ' [...]';
			// console.log('html: %s', html);

			if (!elem.title) {
				elem.title = $(elem).text();
			}
			
			elem.innerHTML = html;
		}
	}
};

kgst.Erfassen = (function() {

	var LIMIT = 500,
		lastLength = 0,
		jRemainDiv = null,
		remaining = 0,
		jTextarea = null,
		jSaveButton = 0,
		jResetButton = 0,
		jButtonDiv = 0,
		OLDVAL_ATTRIB = 'oldValue',
		AJAX_SUCCESS_STRING = 'ajaxWerteSavedOK',
		jSpinner = null,
		loading = 0,
		TEXTS = {
			OK: 'Noch <b style="color:#000">%n</b> Zeichen.',
			BAD: 'Der Text ist <b style="color:red">%n</b> Zeichen zu lang.'
		},
		
		saveOldValue = function() {
			jTextarea.data(OLDVAL_ATTRIB, jTextarea.val());
		},

		loadOldValue = function() {
			jTextarea.val( jTextarea.data(OLDVAL_ATTRIB) ).trigger('keyup');
		},

		setWait = function(on) {
			if (on) {
				loading = 1;
				jRemainDiv.html('').append(jSpinner, ' Bitte warten...');
				return;
			}
			// set off
			loading = 0;
			jRemainDiv.html('');
		},

		pendingAjaxWarning = function() {
			if (!loading) return false;
			alert('Ein Speichervorgang ist noch nicht abgeschlossen.');	
			return true;
		},

		HANDLERS = {
			// When thickbox is opened
			THICKBOX_SHOW: function() {
				jTextarea = $('#TB_ajaxContent textarea')
					.bind('keyup', HANDLERS.TEXTAREA_KEYUP)
					.bind('mousemove',HANDLERS.TEXTAREA_KEYUP);

				if (typeof jTextarea.data(OLDVAL_ATTRIB) == 'undefined') {
					saveOldValue();
				}
				loadOldValue();
				lastLength = -1;
				$('#TB_ajaxContent').append(jRemainDiv, jButtonDiv);
				lastLength = -1;
				jTextarea.trigger('keyup');
			},

			// on key events on textarea 
			TEXTAREA_KEYUP: function() {
				var chars = ($(this).val()||'').length;
				if (chars==lastLength) {
					return;
				}
				remaining = LIMIT - chars;
				var	ok = (remaining >= 0),
					text = (ok)? TEXTS.OK : TEXTS.BAD,
					valForText = (ok)? remaining : remaining*-1;
				jRemainDiv.html( text.replace('%n', valForText) );
				lastLength = chars;
			},

			AJAX_SUCCESS: function(data) {
				setWait(0);
				var success = (''+data).indexOf(AJAX_SUCCESS_STRING) >= 0;
				if (success) {
					saveOldValue();
					updateErlauterungIcons();
					jRemainDiv.html('<span style="color:green">Die Änderungen wurden gespeichert.</span>');
					return;
				}
				HANDLERS.AJAX_ERROR();
			},

			AJAX_ERROR: function(e) {
				setWait(0);
				alert('Die Änderungen konnten nicht gespeichert werden.');
			},

			// on click on Speichern button
			SAVE_CLICK: function() {
				if (pendingAjaxWarning()) return false;
				
				if (remaining < 0) {
					alert('Der eingegebene Text ist zu lang!');
					return false;
				}

				var params = {isAjaxSaveRequest:1},
					formURL = $('#ikonEditForm').attr('action');

				// populate params object to be sent as payload on ajax request
				params[jTextarea[0].name] = jTextarea.val();
				$('#ikonEditForm > input:hidden').each( function(i,inp) {
					var pName = inp.name || '';
					if (!pName || pName.indexOf('scroll')>=0) return;
					params[pName] = inp.value;
				});

				setWait(1);
				$.ajax({
					url: formURL,
					type: 'POST',
					data: params,
					cache: false,
					success: HANDLERS.AJAX_SUCCESS,
					error: HANDLERS.AJAX_ERROR
				});

				return false;
			},
			
			RESET_CLICK: function() {
				if (pendingAjaxWarning()) return false;
				if (!confirm('Alle nicht gespeicherten Änderungen gehen verloren. Fortfahren?')) return;
				loadOldValue();
				jTextarea.trigger('keyup');
				return false;
			}
		},

		updateErlauterungIcons = function() {
			var IMG_RED = '/kgst-webapp/images/skin/database_edit_red.gif',
				IMG_NORM = '/kgst-webapp/images/skin/database_edit.gif';

			$('td.erl').each( function(i,td) {
				var textarea = $('textarea:first', this)[0] || jTextarea[0],
					hasValue = !!$(textarea).val();
				($('a.thickbox img:first', this)[0]||{}).src = (hasValue)? IMG_RED : IMG_NORM;
			});
		},

		initStaticGz = function() {
			var inputHandler = function(e) {
					$(this.parentNode).nextAll('td.static-disabled').find('input').val(this.value);
				},
				selectHandler = function(e) {
					$(this.parentNode).nextAll('td.static-disabled').find('select').val(this.value);
				};

			$('td.static').each( function(i,td) {
				// Enable auto fill for "statische" Grundzahlen
				$('input', td).bind('keydown keyup keypress change select click focus', inputHandler);
				$('select', td).bind('change', selectHandler);
			});

			// Disable obsolete inputs for "statische" Grundzahlen
			$('.static-disabled').find('input,select').attr('disabled', 'disabled');
		},

		/**
		 * Adjusts the width of the dropdowns of the ObjekteigenschaftenErfassen-Dialog.
		 */
		initObjEigErfassen = function() {
			var SELECTOR = {
					SELECT: '#objEigTable td.value select',
					INPUT: '#objEigTable td.value input:text',
					VALUECELL: '#objEigTable td.value, #objEigTable th.value'
				},
				TXT_INPUT_PADDING = (kgst.isIE7OrBelow)? 5 : 3,
				isChanged = 0,
				newWidth = 123;

			$(SELECTOR.SELECT).css('width','auto').each(function(i,sel) {
				var w = $(sel).width();
				if (w < newWidth) return;
				newWidth = w;
				isChanged = 1;
			});

			if (isChanged) {
				// extra padding right
				newWidth += 13;
			}

			$(SELECTOR.SELECT).css({
				width: newWidth+'px'
			});

			$(SELECTOR.INPUT).css({
				width: (newWidth-TXT_INPUT_PADDING) + 'px',
				paddingLeft: TXT_INPUT_PADDING + 'px'
			});

			$(SELECTOR.VALUECELL).css({
				width: newWidth+'px',
				paddingLeft: 0,
				paddingRight: 0,
				textAlign:'left'
			});
		},

		init = function() {
			var wrapStart = ($.browser.msie)? '<span class="ikonButtonIE6" style="float:right">' : '',
				wrapEnd = ($.browser.msie)? '</span>' : '',
				floatCSS = ($.browser.msie)? '' : 'float:right"';
			jRemainDiv = $('<div id="remainingChars" style="width:50%">&nbsp;</div>');
			jResetButton = $(wrapStart + '<a href="#" class="ikonActionLink" style="color:#fff;'+ floatCSS +'">Abbrechen</a>' + wrapEnd).bind('click', HANDLERS.RESET_CLICK);
			jSaveButton = $(wrapStart + '<a href="#" class="ikonActionLink" style="color:#fff;'+ floatCSS +'">Speichern</a>' + wrapEnd).bind('click', HANDLERS.SAVE_CLICK);
			jButtonDiv = $('<div>').append(jSaveButton, jResetButton, '<div class="break"></div>');
			jSpinner = $('<img src="/kgst-webapp/images/trans.gif" alt="" class="spinner" style="vertical-align:middle" />');

			$('td.erl a.thickbox').bind('click', HANDLERS.THICKBOX_SHOW);
			updateErlauterungIcons();
		};

	return {
		init: init,
		initStaticGz: initStaticGz,
		initObjEigErfassen: initObjEigErfassen
	};

})();


/**
 * Singleton managing the splitter layout.
 * Use by calling prepare() BEFORE splitpane HTML and init() AFTER splitpane HTML.
 */
kgst.Layout = (function() {

	var COOKIE_NAME = 'ikonLayout',
		COMPACT_MODE_SUPPORTED = 1,
		COMPACT_COOKIE_NAME = 'ikonCompact',
		MIN_LEFT_WIDTH = 100,
		MAX_LEFT_WIDTH = 600,
		HANDLE_WIDTH = 3,	// width of the vertical split handle line
		TOTAL_WIDTH = 940,	// width of the entire splitPane
		logger = kgst.LOGGER,
		FOOTER_HEIGHT = 25,
		ADDITIONAL_MARGIN = 115,
		ADDITIONAL_MARGIN_COMPACT = 85,
		jSplitPane = null,
		jLeftPane = null,
		optiTimer = 0,
		OPTI_DELAY = 100,	// delay to wait after last resize event before adapting the change
		state = {
			leftWidth: 200,
			leftHeight:400
		},

		features = [],  // array of functions to be executed after init()

		/**
		 * Adds a feature function to the {features} list
		 */
		addFeature = function(fn) {
			var fType = typeof fn;
			if (fType != 'function') {
				alert('Feature must be a function but is: ' + typeof fType);
				return;
			}
			features[features.length] = fn;
		},

		/**
		 * Reads the last state from the cookie.
		 */
		restoreStateFromCookie = function() {
			var cookie = $.cookie(COOKIE_NAME),
				obj = null;
			
			if (!cookie) return null;
			try {
				obj = eval(cookie);
				$.extend(state, obj);
			} catch(ignore) {
				logger.log('invalid layout cookie');
			}
			return obj;
		},

		/**
		 * Determines the current state of the splitpanes and saves it into the cookie.
		 * The state object will be updated to actual current left pane dimensions.
		 * @param {boolean} force (optional) set TRUE to force renewal of cookie even if no values have changed
		 */
		saveStateToCookie = function(force) {
			var hasChanged = false,
				ready = !!(jLeftPane && jLeftPane.length),
				leftWidth = (ready)? jLeftPane.width() : state.leftWidth,
				leftHeight = (ready)? jLeftPane.height() : state.leftHeight,
				hasChanged = (leftWidth!=state.leftWidth || leftHeight!=state.leftHeight);

			logger.log('layout::saveStateToCookie -> hasChanged: ' + hasChanged + ', forced:'+ !!force);

			if (hasChanged || force) {
				state.leftWidth = leftWidth;
				state.leftHeight = leftHeight;
				var cookieValue = '({leftWidth:' + leftWidth + ',leftHeight:' + leftHeight+ '})';
				$.cookie(COOKIE_NAME, cookieValue, {path:'/'});
			}
		},

		/**
		 * Adjusts the vertical size of the splitpane to fit perfectly into the viewport without vertical scrollbars.
		 * Used also as event handler on window:resize events.
		 */
		setOptimalHeight = function() {
			optiTimer = 0;
			var headerHeight = $('#header').height(),
				footerHeight = FOOTER_HEIGHT, // $('#footer').height()
				winHeight = $(window).height(),
				margin = (isInCompactMode())? ADDITIONAL_MARGIN_COMPACT : ADDITIONAL_MARGIN,
				newHeight = winHeight - headerHeight - footerHeight - margin;

			// logger.log('header:'+headerHeight+', footer:'+footerHeight+', win:'+winHeight+',newHeight:'+newHeight );
			$('#splitPane, #splitPane .vsplitbar:first, #leftPane').css({
				height: newHeight + 'px',
				minHeight: newHeight + 'px'
			});

			var rightTopH = $('#topPane > div:first').height();
			$('#bottomPane').css('overflow','auto').height(newHeight - rightTopH + 'px');
			jSplitPane.trigger('resize'); // otherwise weird height effects in IE+FF on resize of left column
		},

		scheduleSetOptimalHeight = function() {
			if (optiTimer) clearTimeout(optiTimer);
			optiTimer = setTimeout(setOptimalHeight, OPTI_DELAY);
		},

		/**
		 * Event handler for resize events on splitPane.
		 */
		handleResize = function(e) {
			logger.log('resized pane ' + e.target.id);
			if (e.target!=jSplitPane[0] && e.target!=jLeftPane[0]) return;
			logger.log('pane '+ e.target.id +' resize %o', e);
			saveStateToCookie();
		},
		
		checkCompactMode = function() {
			if (!COMPACT_MODE_SUPPORTED) return;
			var isCompact = !!$.cookie(COMPACT_COOKIE_NAME);
			var jNormal = $('<a id="aNormalView" href="#">Normalansicht</a>').bind('click', function(){
					$(document.body).removeClass('compactMode');
					$(window).trigger('resize');
					$.cookie(COMPACT_COOKIE_NAME, null, {path:'/'});
					return false;
				}),
				jCompact = $('<a id="aCompactView" href="#">Kompaktansicht</a>').bind('click', function() {
					$(document.body).addClass('compactMode');
					$(window).trigger('resize');
					$.cookie(COMPACT_COOKIE_NAME, '1', {path:'/'});
					return false;
				}),
				jViewmodeSwitch = $('<div id="viewmode"></div>').append(jNormal,jCompact);

				if (typeof dotCmsHelpPopup != 'undefined' && dotCmsHelpPopup) {
					var jHelp = $('<a id="aHelpView" href="' + dotCmsHelpPopup + '" target="_blank">Hilfe</a>');
					jViewmodeSwitch.append(' | ', jHelp);
				}
			if (isCompact) {
				$(document.body).addClass('compactMode');
			}
			$('#header').append(jViewmodeSwitch);
		},
		
		isInCompactMode = function() {
			return $(document.body).hasClass('compactMode');
		},

		/**
		 * Checks the cookie for initial size of the left pane.
		 * Inserts cookie or default dimensions as CSS rules (Anti-Ruckel).
		 * To be called inline BEFORE the pane is rendered.
		 */
		prepare = function() {
			//this.stopper = new kgst.Stopwatch();
			restoreStateFromCookie();
			var rightPos = state.leftWidth + HANDLE_WIDTH,
				rightWidth = TOTAL_WIDTH - rightPos,
				jStyle = $('<style id="panePrepareCSS" type="text/css">'+
						'/* splitPane CSS dynamically inserted by kgst.Layout.prepare() */' + "\n" + 
						'#splitPane {position:relative} ' +
						'#rightPane {position:absolute; left:'+ rightPos +'px; top:0; height:'+ state.leftHeight +'px; width:'+ rightWidth +'px; overflow:hidden !important}' +
						'#leftPane {width: '+ state.leftWidth +'px; height: '+ state.leftHeight +'px;overflow:hidden !important}'+
						'#tree {display:none !important}' +
						'</style>');
			document.getElementsByTagName('head')[0].appendChild(jStyle[0]);
			//logger.log('Layout.prepare took ' + this.stopper.getTime() + 'ms');

			checkCompactMode();
		},

		/**
		 * Initializes the Splitter layout.
		 * To be called AFTER the pane is rendered (yet BEFORE domready).
		 */
		init = function() {
			jLeftPane = $('#leftPane');

			var frag, tree, treeContainer;
			if ($.browser.msie) {
				/**
				 * IE rendering performance boost.
				 * Take tree off the DOM while initing splitpane (otherwise takes seconds in IE!).
				 */
				frag = document.createDocumentFragment();
				tree = document.getElementById('tree');
				treeContainer = tree.parentNode;
				frag.appendChild(tree);
			}

			jSplitPane = $("#splitPane").css('overflow','visible').splitter({
				splitVertical: true,
				outline: true,
				sizeLeft: state.leftWidth,
				minLeft: MIN_LEFT_WIDTH,
				maxLeft: MAX_LEFT_WIDTH
			}).bind('resize', handleResize);

			if (!jSplitPane.length || !jLeftPane.length) {
				alert('Whoops, invalid splitpane HTML!?');
				return;
			}

			// set optimal height "near" domload
			$(window).bind('resize', scheduleSetOptimalHeight);
			setOptimalHeight();
			
			$('#panePrepareCSS').remove();
			
			saveStateToCookie(true);
			
			if ($.browser.msie) {
				setTimeout(function() {
					treeContainer.appendChild(frag);
					kgst.Tree2.init();
				}, 1);
			} else {
				kgst.Tree2.init();
			}

			$('#ikonEditForm input[type=text]:first').each(function(){
				$(this).trigger('focus')[0].focus();
			});

			for (var i=0; i<features.length; i++) {
				features[i]();
			}
		};

	// --------------------------
	return {
		prepare: prepare,
		init: init,
		setOptimalHeight: setOptimalHeight,
		addFeature: addFeature
	};
})();


kgst.PopupLayout = (function() {
	
	var ADDITIONAL_V_MARGIN = ($.browser.msie)? 50 : 50,
		ADDITIONAL_H_MARGIN = 20,
		LOGGER = kgst.LOGGER,
		resizeHandlers = [],
		cssDiv = 0,
		resizeTimer = 0,

		prepare = function() {
			$('head:first').append( $('<style type="text/css">html,body{overflow:hidden}</style>') );
			$('#ikonPageMenu').remove();
			$(document.body).css({
				backgroundImage:'url(/kgst-webapp/images/spinnerBig.gif)',
				backgroundPosition:'center center',
				backgroundRepeat:'no-repeat'
			});
			$(function() {
				$('#header,#breadcrumb').remove();
				document.body.style.backgroundImage = 'none';
				$('#bottomPane, #ikonButtonPane').show();
				setOptimalSize(true);
				updateTooltips();
			});
		},

		init = function() {
			$(window).bind('resize', setOptimalSize);
			$('form.werteErfassen td a').attr('tabindex',-1);
			if (document.all) {
				/*
				$('.ikonWerte table thead th').each(function(){
					this.innerHTML = (this.innerHTML||'').replace(/([^\s]+)\s+([^\s]+)/g,"$1&nbsp;$2");
				});
				*/
				$('#o2gAssignTable').slideDown(20);
			}
		},
		
		doResize = function() {
			setOptimalSize(true);
		},

		setOptimalSize = function(now) {
			if (now!==true) {
				if (resizeTimer) {
					clearTimeout(resizeTimer);
					resizeTimer = 0;
				}
				resizeTimer = setTimeout(doResize,60);
				return;
			}

			var topHeight = $('#topPane > div:first').height() || 0,
				winH = $(window).height() || 0,
				winW = $(window).width() || 0,
				buttonsH = $('#ikonButtonPane').height() || 0,
				bottomHeight = winH - topHeight - buttonsH - ADDITIONAL_V_MARGIN,
				w = winW - ADDITIONAL_H_MARGIN,
				css = [];

			if (!cssDiv) {
				cssDiv = $('<div style="display:none"></div>')[0];
				document.body.appendChild(cssDiv);
			}

			css[css.length] = '#page,#column-right {width:' + (w-ADDITIONAL_H_MARGIN)+'px !important; padding:0 !important}';
			css[css.length] = '#bottomPane {width:'+w+'px;height:'+bottomHeight+'px;overflow:auto;}';
			css[css.length] = '#topPane {width:'+w+'px}';
			
			cssDiv.innerHTML = '';
			cssDiv.appendChild( $('<style type="text/css">'+ css.join('') +'</style>')[0] );

			for (var i=0; i<resizeHandlers.length; i++) {
				resizeHandlers[i]();
			}
		},
		
		/**
		 * Adds a callback to be called whenever setOptimalSize is executed.
		 * @param {function} fn
		 */
		addResizeHandler = function(fn) {
			if (typeof fn != 'function') {
				alert('resizeHandler must be a function!');
				return;
			}
			resizeHandlers[resizeHandlers.length] = fn;
		},

		/**
		 * Sets up correct tooltips for all data cells of the table.
		 * Head may contain rowspanned cells and colspanned cells.
		 * @todo FIX for Objektmatrix
		 */
		updateTooltips = function() {
			var jForm = $('form.werteErfassen');
			if (!jForm.length) {
				// alert('Whoops - .werteErfassen-Form?!');
				return;
			}

			var xHeadlines = [],
				metaColsCount = $('table:first tbody tr:first td.name, table:first tbody tr:first td.action', jForm).length, // #cols before first td.value
				dataColsCount = $('table:first tbody tr:first td.value', jForm).length, // #cols of type td.value
				headRowsCount = $('table:first thead tr', jForm).length,
				BREAK_CHAR = ($.browser.msie)? "\n" : ' -> ',
				rowspanOffset = 0,
				colIndex = -1;

			LOGGER.log('metaCols: ' + metaColsCount);
			LOGGER.log('dataCols: ' + dataColsCount);
			LOGGER.log('headRows: ' + headRowsCount);
		
			/**
			 * iterate THs of first header line
			 * meta columns MUST NOT be colspanned but may be rowspanned
			 * data columns MUST NOT be rowspanned but may be colspanned
			 */
			$('table:first thead tr:eq(0) th', jForm).each( function(x, th) {
				if (x < metaColsCount) {
					colIndex++;
					if ($(th).attr('rowspan')==2) rowspanOffset++;
					return;
				}
				LOGGER.log('rowspanOffset: %i', rowspanOffset);

				var num = parseInt($(th).attr('colspan')||1) || 1;
				LOGGER.log('colspan for "%s" == %i', th.innerHTML,num);
				while (num--) {
					xHeadlines[++colIndex] = th.innerHTML;
					LOGGER.log('setting xHeadlines[%i] to %s', colIndex, th.innerHTML);
				}
			});
			
			LOGGER.log(xHeadlines);

			// iterate second header row
			$('table:first thead tr:eq(1) th', jForm).each( function(x, th) {
				var offs = x + rowspanOffset;
				if (offs < metaColsCount) return;
				xHeadlines[x + rowspanOffset] += BREAK_CHAR + th.innerHTML;
			});
		
			LOGGER.log(xHeadlines);

			$('table:first tbody tr', jForm).each( function(y,tr) {
				var rowHeadline = '';
				$('td', tr).each( function(x,td) {
					if (x==0) {
						rowHeadline = this.innerHTML;
					}
					if (!xHeadlines[x]) return;
					this.setAttribute('title', xHeadlines[x] + BREAK_CHAR + rowHeadline);
					LOGGER.log('setting title '+y+':'+x+' to ' + this.getAttribute('title'));
				});
			});
		};

	return {
		init: init,
		prepare: prepare,
		addResizeHandler: addResizeHandler
	}
})();



/**
 * The Tree singleton for use in new splitter layout.
 * Usage: call kgst.Tree2.init() BEFORE domReady but AFTER splitter layout has been initialized.
 */
kgst.Tree2 = kgst.Tree2 || (function() {

	var COOKIE_NAME = 'ikonTree',
		MAP_COOKIE_NAME = 'treeMap2',
		LOAD_TIMEOUT = 10000,
		LOAD_FLAG_ATTRIBUTE = '_kgstload',
        RETRY_FLAG = '_kgstRetry',
		SAVE_COOKIE_INTERVAL = 700,
		EMULATE_IE6_HOVER = true,
		saveCookieTimer = 0,
		treeInitialized = false,
		treeActionMap = null,

		jTree = null,
		jStyle = $('<style type="text/css"></style>'),
		state = {
			scrollLeft: 0,
			scrollTop: 0
		},

		logger = kgst.LOGGER,

		getTreeLoadAjaxUrl = function(str) {
			var url = dotCmsAjaxURLTreeLoadAjax;
			return (isDotCms(url))? getDotCmsAjaxUrl(url, str) : (url+'/'+str);
		},
		
		getTreeNodeOpenAjaxUrl = function(str) {
			var url = dotCmsAjaxURLTreeNodeOpenAjax;
			return (isDotCms(url))? getDotCmsAjaxUrl(url, str) : (url+'/'+str);
		},

		getTreeNodeCloseAjaxUrl = function(str) {
			var url = dotCmsAjaxURLTreeNodeCloseAjax;
			return (isDotCms(url))? getDotCmsAjaxUrl(url, str) : (url+'/'+str);
		},

        getTreeNodePageUrl = function(str) {
            if (treeBaseUrl == null) {
                treeBaseUrl = $('#treeHomeUrl').attr('href').replace('adminVR?rmtparams=','adminVR/?rmtparams=');
            }
            var url = treeBaseUrl;
			return (isDotCms(url))? getDotCmsPageUrl(url, str) : treeBaseUrlPrefix + '/' + str;
        },

		loadSubTree = function(id, li) {
			var jWait = $('<div class="waitDiv">Lade...</div>'),
				nodeURL = kgst.Tools.antiCacheURL( getTreeLoadAjaxUrl(id) ),
				requestTime = new Date();
			$(li).append(jWait);

			$.ajax({
				url: nodeURL,
				dataType: 'html',
				timeout: LOAD_TIMEOUT,
				success: function(data) {
					jWait.remove();
					li.removeAttribute(RETRY_FLAG);
                    li.removeAttribute(LOAD_FLAG_ATTRIBUTE);
					if (data && $.trim(data).length) {
						// if user has logged out meanwhile, returned data is the login page WITHOUT proper HTTP status code
						if (data.indexOf('<form')>=0 ) {
							alert('Sie sind nicht eingeloggt. Sie werden nun zur Login-Seite weitergeleitet.');
							kgst.Tools.jumpToLogin();
							return;
						}
						$(li).append(data);
					} else {
						// logged('empty data returned -> probably no children?' );
						$(li).children('img:first').remove();
					}
				},
				error: function(xmlHttpRequest, textStatus, errorThrown) {
					jWait.remove();
					li.removeAttribute(LOAD_FLAG_ATTRIBUTE);
					$('> img:first',li).removeClass('close');
					if (!li.getAttribute(RETRY_FLAG)) {
						// $('>div', li).remove();
						li.setAttribute(RETRY_FLAG, true);
						setTimeout(function() {
							loadSubTree(id,li);
						},50);
					} else {
						$(li).append('<div>Fehler</div>');
						li.removeAttribute(RETRY_FLAG);

						// error logging..
						var statusCode = xmlHttpRequest.status || 'unbekannt',
							textStatus = textStatus || '',
							time = new Date,
							hexTime = (+time).toString(16),
							secondsAgo = '' + Math.round( (time-requestTime)/1000 ),
							requestedURL = nodeURL || '',
							traceImgURL =	'/kgst-webapp/images/TREE_ERROR_'+hexTime+'.gif?' +
											'statusCode=' + encodeURIComponent(''+statusCode) +
											'&textStatus=' + encodeURIComponent(''+textStatus) +
											'&secondsAgo=' + secondsAgo +
											'&requestURL=' + encodeBase64(requestedURL),
							jTraceImg = $('<img>').hide().attr('src',traceImgURL);
						
						$(document.body).append(jTraceImg);

						alert(	'Es ist ein Fehler aufgetreten.' + 
								"\nFehlercode -> " + statusCode + 
								"\nTextStatus -> " + textStatus +
								"\nlokale Zeit -> " + time.toString() +
								"\nAnfrage-URL -> " + requestedURL
						);
					}
				}
			});
		},

		saveStatus = function(url) {
			$.ajax({url: url, success: HANDLERS.NOP});
		},
		
		HANDLERS = {
			/**
			 * Click on +/- toggler icon of a node.
			 * (this: IMG)
			 */
			TOGGLE_CLICK: function() {
				var id = this.parentNode.className.match(/id([^ ]*)/)[1],
					jImg = $(this);	
					jUL = jImg.siblings('ul:first'),
				
				logger.log('toggle img clicked -- ID is ' + id);

				if (!jUL.length) {
					var li = this.parentNode,
						isLoading = !!li.getAttribute(LOAD_FLAG_ATTRIBUTE);
					if (isLoading) {
						// Subtree was already requested (probably double-clicked on +/- icon)
						$('> .waitDiv', li).css({
							color: 'red',
							letterSpacing: '1px'
						});
						return;
					}
					li.setAttribute(LOAD_FLAG_ATTRIBUTE, 1);
					$('>div', li).remove();
					jImg.addClass('close');
					loadSubTree(id, li);
				} else {
					if (jUL.hasClass('clsd')) {
						jUL.removeClass('clsd');
						jImg.addClass('close');
						saveStatus( getTreeNodeOpenAjaxUrl(id) );
					} else {
						jUL.addClass('clsd');
						jImg.removeClass('close');
						saveStatus( getTreeNodeCloseAjaxUrl(id) );
					}
				}
			},

			/**
			 * Mousedown on a node link (right- or leftclick, enabling "open new tab/window")
			 * (this: A)
			 */
			LINK_MOUSEDOWN: function() {
				loadTreeActionMap();
				var explNodePathParam = '',
					split = this.name.split('/'),
					obj = {
						controller: split[0],
						action: split[1],
						id: split[2]
					},
					hash = urlObj2Hash(obj),
					mapObj = treeActionMap[hash],
					isSelected = $(this).hasClass('selected'),
					hasNutzerTab = /#N$/.test(this.href),	// only if a Bereich-link has this URL, mapObj may redirect to the "Nutzer"-Tab 
					tabRedirectForbidden = (mapObj)? (mapObj.c=='adminBereich' && mapObj.a=='showUser' && !hasNutzerTab) : false;

				if (mapObj && (isSelected || tabRedirectForbidden)) {
					mapObj = null;
					delete treeActionMap[hash];
					saveTreeActionMap();
				}
				
				if (mapObj) {
					explNodePathParam = '?explNodePath=' + this.name.replace(/\//g,'|');
					this.name = mapObj.c + '/' + mapObj.a + '/' + obj.id;
				}
				
				this.href = getTreeNodePageUrl(this.name + explNodePathParam);
			},
			
			IE6_TOGGLE_OVER: function() {this.id = 'ie6th'},
			IE6_TOGGLE_OUT: function() {this.id = ''},

			NOP: function() {return false},

			/**
			 * Click on a tab of the right pane.
			 * (this: A)
			 */
			TAB_CLICK: function() {
				var treeObj = selectedNode2object(),
					tabObj = kgst.Tools.url2object(this.href);
				
				if (!treeObj || !tabObj) return;

				loadTreeActionMap();

				var treeObjHash = urlObj2Hash(treeObj),
					mapObj = {c:tabObj.controller, a:tabObj.action},
					TREEMAP_WHITELIST = {
						'adminBereich_show': ['adminBereich/show','adminBereich/showBeschreibungen','adminTeilnehmer/listByBereich','adminBereich/showUser']
					};

				// Check whitelist of controller/action pairs allowed for a special tree node. Relevant only for nodes that
				// may remain 'selected' in the tree while actually leaving the Bereich context, KGST-2154)
				if (TREEMAP_WHITELIST[treeObjHash]) {
					var newMapObj = 0,
						mapStr = mapObj.c + '/' + mapObj.a;
					for (var i=TREEMAP_WHITELIST[treeObjHash].length-1; i>=0; i--) {
						if (mapStr != TREEMAP_WHITELIST[treeObjHash][i]) continue;
						newMapObj = mapObj;
						break;
					}
					mapObj = newMapObj;
				}

				if (mapObj) {
					treeActionMap[treeObjHash] = mapObj;
					saveTreeActionMap();
				}
			}
		},

		/**
		 * Reads the last state from the cookie.
		 */
		restoreStateFromCookie = function() {
			var cookie = $.cookie(COOKIE_NAME),
				obj = null;
			
			if (!cookie) return null;
			try {
				obj = eval(cookie);
				$.extend(state, obj);
			} catch(ignore) {
				logger.log('invalid tree cookie');
			}
			return obj;
		},

		/**
		 * Determines the current state of the tree and saves it into the cookie.
		 * The state object will be updated to actual current scroll positions etc.
		 * @param {boolean} force (optional) set TRUE to force renewal of cookie even if no values have changed
		 */
		saveStateToCookie = function(force) {
			var scrollLeft = jTree.scrollLeft(),
				scrollTop = jTree.scrollTop(),
				hasChanged = (scrollTop != state.scrollTop) || (scrollLeft != state.scrollLeft);
			
			if (hasChanged || force) {
				state.scrollLeft = scrollLeft;
				state.scrollTop = scrollTop;
				var cookieValue = '({scrollLeft:' + state.scrollLeft + ',scrollTop:' + state.scrollTop + '})';
				$.cookie(COOKIE_NAME, cookieValue, {path:'/'});
			}
		},

		/**
		 * Updates the Tree's scroll position based on the {state} properties.
		 * If the optional callback is given, smooth scrolling will be used.
		 * @param {function} callback (optional) function to be called when smooth scrolling has finished
		 */
		adaptScrollStatus = function(callback) {
			logger.log('adaptScrollStatus -> left:'+ state.scrollLeft +' , top:'+ state.scrollTop);
			if (!callback) {
				// simple case: just set scroll pos immediately
				jTree.scrollLeft(state.scrollLeft).scrollTop(state.scrollTop);
				return;
			} 
			// otherwise use smooth scrolling
			jTree.scrollTo({
				top: state.scrollTop,
				left: state.scrollLeft
			}, {
				duration: 300,
				onAfter: callback
			});
		},

		/**
		 * Determines whether the selected node is visible within the Tree viewport.
		 * If not, the viewport will be scrolled appropriately to reveal the node.
		 */
		assureSelectedInViewport = function() {
			// logger.log('Checking selected node is in viewport...');
			var jSelected = jTree.find('a.selected:first');
			if (jSelected.length) {
				var viewportHeight = jTree.height() - 18,	// 18 pixels for possible hori-scrollbar 
					viewportScroll = jTree.scrollTop(),
					selectedTop = jSelected.position().top || 0,
					selectedHeight = jSelected.height(),
					isInViewport = (selectedTop > 0 && selectedTop < (viewportHeight-selectedHeight));

				logger.log(	'viewportHeight=' + viewportHeight + ' | ' + 
							'viewportScroll=' + viewportScroll + ' | ' +
							'selectedTop=' + selectedTop + ' | ' +
							'selectedHeight=' + selectedHeight + ' | ' +
							'isInViewport=' + isInViewport);
				
				if (!isInViewport) {
					jTree.scrollTo('a.selected:first', {
						duration:300, 
						axis:'xy',
						offset: -23
					});
				}
			} else {
				logger.log('No node selected!');
				jTree.scrollTo({top:0,left:0}, {duration:300});
			}
		},

		/**
		 * Empties the tree cache. 
		 * (!) FOR DEBUG ONLY (!)
		 */
		emptyTreeCache = function() {
			$(function(){
				var iframe = $('<iframe>').attr('src', 'http://localhost:57880/kgst-webapp/adminTree/releaseTreeFromSession?' + (+new Date)).hide().bind('load', function(){
					logger.log('tree cache flushed');
				});
				$(document.body).append(iframe);
			});
		},

		loadTreeActionMap = function() {
			treeActionMap = $.evalJSON( $.cookie(MAP_COOKIE_NAME) || '{}' );
		},
		
		saveTreeActionMap = function() {
			$.cookie(MAP_COOKIE_NAME, $.toJSON(treeActionMap), {path:'/'});
			// console.log($.cookie(MAP_COOKIE_NAME));
		},

		urlObj2Hash = function(obj) {
			return obj.controller + '_' + obj.action;
		},

		bindEventHandlers = function() {
			logger.log('binding event handlers..');
			
			$('#tree img').live('click', HANDLERS.TOGGLE_CLICK);
			$('#tree a').live('mousedown', HANDLERS.LINK_MOUSEDOWN)
			
			if (EMULATE_IE6_HOVER && /msie|MSIE 6/.test(navigator.userAgent)) {
				$('#tree img').live('mouseover', HANDLERS.IE6_TOGGLE_OVER);
				$('#tree img').live('mouseout', HANDLERS.IE6_TOGGLE_OUT);
			}

			// save controller-dependent active tab
			$('#ikonPageTabs table td a').bind('click', HANDLERS.TAB_CLICK);
		},

		/**
		 * Fixes zombie-togglers ("pseudo-open" nodes with missing subtree UL)
		 */
		fixZombies = function() {
			var zombies;
			//var stopper = new kgst.Stopwatch();
			if (!$.browser.msie) {
				// selector too slow for IE (>200ms IE vs. <10ms FF)
				zombies = $('#tree img.close ~ a:last-child ').each( function(i,a) {
					$(a).prev()[0].className = '';
				});
			} else {
				zombies = {length:0};
				$('#tree img.close').each( function(i, img) {
					if ($(this).siblings('ul').length) return;
					this.className = '';
					zombies.length++;
				});
			}
			//logger.log(zombies.length + ' zombie togglers repaired. (' + stopper.getTime() + 'ms)');
		},
		
		/**
		 * Returns the currently selected tree node's link as an object like {controller:'abc',action:'def',id:'123}.
		 * Analog to {kgst.Tools.url2Object}.
		 * @return {object|null}
		 */
		selectedNode2object = function() {
			var a = jTree.find('a.selected')[0];
			if (!a) return null;
			var matches = (a.name || '').match(/([^\/]+)\/([^\/]+)\/(\d*)/);
			if (!matches || matches.length<4) return null;
			return {
				controller: matches[1],
				action: matches[2],
				id: matches[3]
			};
		};


	// Return the actual singleton...
	return {
		state: state,

		selectedNode2object: selectedNode2object,

		/**
		 * To be called inline before domready.
		 * @param {boolean} debug (optional) set TRUE to enable logging
		 */
		init: function(_debug) {
			if (_debug) DEBUG = _debug;
			logger.log('Tree.init()');

			jTree = $('#leftPane');

			if (!jTree.show().length) {
				return;
			}

			if (treeInitialized) {
				alert('Whoops, Tree2 already initialized!?');
				return;
			} else {
				treeInitialized = true;
			}
			
			document.getElementsByTagName('head')[0].appendChild(jStyle[0]);

			restoreStateFromCookie();
			adaptScrollStatus();
			assureSelectedInViewport();  // for animation orgy use assureSelectedInViewport as param for adaptScrollStatus()

			bindEventHandlers();
			
			fixZombies();

			saveStateToCookie(true);
			saveCookieTimer = window.setInterval(function(){saveStateToCookie()}, SAVE_COOKIE_INTERVAL);
			logger.log('Tree.init() done.');
		},
		
		/**
		 * Removes the treemap cookie from the previous login session.
		 */
		deleteCookies: function() {
			if ($.cookie(MAP_COOKIE_NAME)) {
				$.cookie(MAP_COOKIE_NAME, null, {path:'/'});
			}
			if ($.cookie(COOKIE_NAME)) {
				$.cookie(COOKIE_NAME, null, {path:'/'});
			}
		}
	}
})();






// ===================================================================================
//                        *** HELPER FUNCTIONS ***
// ===================================================================================


function urlDecode(str){
    str=str.replace(new RegExp('\\+','g'),' ');
    return unescape(str);
};

function urlEncode(str){
    str=escape(str);
    str=str.replace(new RegExp('\\+','g'),'%2B');
    return str.replace(new RegExp('%20','g'),'+');
};

var END_OF_INPUT = -1;

var base64Chars = new Array(
    'A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X',
    'Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n',
    'o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3',
    '4','5','6','7','8','9','+','/'
);

var reverseBase64Chars = new Array();
for (var i=0; i < base64Chars.length; i++){
    reverseBase64Chars[base64Chars[i]] = i;
};

var base64Str;
var base64Count;
function setBase64Str(str){
    base64Str = str;
    base64Count = 0;
};

function readBase64(){
    if (!base64Str) return END_OF_INPUT;
    if (base64Count >= base64Str.length) return END_OF_INPUT;
    var c = base64Str.charCodeAt(base64Count) & 0xff;
    base64Count++;
    return c;
};

function encodeBase64(str){
    setBase64Str(str);
    var result = '';
    var inBuffer = new Array(3);
    var lineCount = 0;
    var done = false;
    while (!done && (inBuffer[0] = readBase64()) != END_OF_INPUT){
        inBuffer[1] = readBase64();
        inBuffer[2] = readBase64();
        result += (base64Chars[ inBuffer[0] >> 2 ]);
        if (inBuffer[1] != END_OF_INPUT){
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30) | (inBuffer[1] >> 4) ]);
            if (inBuffer[2] != END_OF_INPUT){
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c) | (inBuffer[2] >> 6) ]);
                result += (base64Chars [inBuffer[2] & 0x3F]);
            } else {
                result += (base64Chars [((inBuffer[1] << 2) & 0x3c)]);
                result += ('=');
                done = true;
            }
        } else {
            result += (base64Chars [(( inBuffer[0] << 4 ) & 0x30)]);
            result += ('=');
            result += ('=');
            done = true;
        }
        lineCount += 4;
        if (lineCount >= 76){
            result += ('\n');
            lineCount = 0;
        }
    }
    return result;
};

function readReverseBase64(){
    if (!base64Str) return END_OF_INPUT;
    while (true){
        if (base64Count >= base64Str.length) return END_OF_INPUT;
        var nextCharacter = base64Str.charAt(base64Count);
        base64Count++;
        if (reverseBase64Chars[nextCharacter]){
            return reverseBase64Chars[nextCharacter];
        }
        if (nextCharacter == 'A') return 0;
    }
    return END_OF_INPUT;
};

function ntos(n){
    n=n.toString(16);
    if (n.length == 1) n="0"+n;
    n="%"+n;
    return unescape(n);
};

function decodeBase64(str){
    setBase64Str(str);
    var result = "";
    var inBuffer = new Array(4);
    var done = false;
    while (!done && (inBuffer[0] = readReverseBase64()) != END_OF_INPUT
        && (inBuffer[1] = readReverseBase64()) != END_OF_INPUT){
        inBuffer[2] = readReverseBase64();
        inBuffer[3] = readReverseBase64();
        result += ntos((((inBuffer[0] << 2) & 0xff)| inBuffer[1] >> 4));
        if (inBuffer[2] != END_OF_INPUT){
            result +=  ntos((((inBuffer[1] << 4) & 0xff)| inBuffer[2] >> 2));
            if (inBuffer[3] != END_OF_INPUT){
                result +=  ntos((((inBuffer[2] << 6)  & 0xff) | inBuffer[3]));
            } else {
                done = true;
            }
        } else {
            done = true;
        }
    }
    return result;
};

var digitArray = new Array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
function toHex(n){
    var result = ''
    var start = true;
    for (var i=32; i>0;){
        i-=4;
        var digit = (n>>i) & 0xf;
        if (!start || digit != 0){
            start = false;
            result += digitArray[digit];
        }
    }
    return (result==''?'0':result);
};

function pad(str, len, pad){
    var result = str;
    for (var i=str.length; i<len; i++){
        result = pad + result;
    }
    return result;
};

function encodeHex(str){
    var result = "";
    for (var i=0; i<str.length; i++){
        result += pad(toHex(str.charCodeAt(i)&0xff),2,'0');
    }
    return result;
};

var hexv = {
  "00":0,"01":1,"02":2,"03":3,"04":4,"05":5,"06":6,"07":7,"08":8,"09":9,"0A":10,"0B":11,"0C":12,"0D":13,"0E":14,"0F":15,
  "10":16,"11":17,"12":18,"13":19,"14":20,"15":21,"16":22,"17":23,"18":24,"19":25,"1A":26,"1B":27,"1C":28,"1D":29,"1E":30,"1F":31,
  "20":32,"21":33,"22":34,"23":35,"24":36,"25":37,"26":38,"27":39,"28":40,"29":41,"2A":42,"2B":43,"2C":44,"2D":45,"2E":46,"2F":47,
  "30":48,"31":49,"32":50,"33":51,"34":52,"35":53,"36":54,"37":55,"38":56,"39":57,"3A":58,"3B":59,"3C":60,"3D":61,"3E":62,"3F":63,
  "40":64,"41":65,"42":66,"43":67,"44":68,"45":69,"46":70,"47":71,"48":72,"49":73,"4A":74,"4B":75,"4C":76,"4D":77,"4E":78,"4F":79,
  "50":80,"51":81,"52":82,"53":83,"54":84,"55":85,"56":86,"57":87,"58":88,"59":89,"5A":90,"5B":91,"5C":92,"5D":93,"5E":94,"5F":95,
  "60":96,"61":97,"62":98,"63":99,"64":100,"65":101,"66":102,"67":103,"68":104,"69":105,"6A":106,"6B":107,"6C":108,"6D":109,"6E":110,"6F":111,
  "70":112,"71":113,"72":114,"73":115,"74":116,"75":117,"76":118,"77":119,"78":120,"79":121,"7A":122,"7B":123,"7C":124,"7D":125,"7E":126,"7F":127,
  "80":128,"81":129,"82":130,"83":131,"84":132,"85":133,"86":134,"87":135,"88":136,"89":137,"8A":138,"8B":139,"8C":140,"8D":141,"8E":142,"8F":143,
  "90":144,"91":145,"92":146,"93":147,"94":148,"95":149,"96":150,"97":151,"98":152,"99":153,"9A":154,"9B":155,"9C":156,"9D":157,"9E":158,"9F":159,
  "A0":160,"A1":161,"A2":162,"A3":163,"A4":164,"A5":165,"A6":166,"A7":167,"A8":168,"A9":169,"AA":170,"AB":171,"AC":172,"AD":173,"AE":174,"AF":175,
  "B0":176,"B1":177,"B2":178,"B3":179,"B4":180,"B5":181,"B6":182,"B7":183,"B8":184,"B9":185,"BA":186,"BB":187,"BC":188,"BD":189,"BE":190,"BF":191,
  "C0":192,"C1":193,"C2":194,"C3":195,"C4":196,"C5":197,"C6":198,"C7":199,"C8":200,"C9":201,"CA":202,"CB":203,"CC":204,"CD":205,"CE":206,"CF":207,
  "D0":208,"D1":209,"D2":210,"D3":211,"D4":212,"D5":213,"D6":214,"D7":215,"D8":216,"D9":217,"DA":218,"DB":219,"DC":220,"DD":221,"DE":222,"DF":223,
  "E0":224,"E1":225,"E2":226,"E3":227,"E4":228,"E5":229,"E6":230,"E7":231,"E8":232,"E9":233,"EA":234,"EB":235,"EC":236,"ED":237,"EE":238,"EF":239,
  "F0":240,"F1":241,"F2":242,"F3":243,"F4":244,"F5":245,"F6":246,"F7":247,"F8":248,"F9":249,"FA":250,"FB":251,"FC":252,"FD":253,"FE":254,"FF":255
};

function decodeHex(str){
    str = str.toUpperCase().replace(new RegExp("s/[^0-9A-Z]//g"));
    var result = "";
    var nextchar = "";
    for (var i=0; i<str.length; i++){
        nextchar += str.charAt(i);
        if (nextchar.length == 2){
            result += ntos(hexv[nextchar]);
            nextchar = "";
        }
    }
    return result;

};

String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

String.prototype.beginsWith = function(t, i) {
	if (i==false) {
		return (t == this.substring(0, t.length));
	} else {
		return (t.toLowerCase() == this.substring(0, t.length).toLowerCase());
	}
};

String.prototype.endsWith = function(t, i) {
	if (i==false) {
		return (t == this.substring(this.length - t.length));
	} else {
		return (t.toLowerCase() == this.substring(this.length - t.length).toLowerCase());
	}
};


/*
    var dotCmsAjaxURLStatus = "/servlets/plugins/kgst/post?rmtparams=cm10YWM9YWRtaW5Ja29uJnJtdGF1PWh0dHAlM0ElMkYlMkZsb2NhbGhvc3QlM0E4MDkwJTJGa2dz_dC13ZWJhcHAlMkZhZG1pbk5hY2hyaWNodCUyRnN0YXR1cw--";
    rmtac=adminIkon&rmtau=http%3A%2F%2Flocalhost%3A57880%2Fkgst-webapp%2FadminNachricht%2Fstatus
    command: >>> console.log(urlDecode(decodeBase64("cm10YWM9YWRtaW...3ZWJhcHAlMkZhZG1pbk5hY2hyaWNodCUyRnN0YXR1cw--")));
    rmtac=adminIkon&rmtau=http://localhost:57880/kgst-webapp/adminNachricht/status
*/

var RMTPARAMS = '?rmtparams=';

function isDotCms(str) {
    return str.indexOf(RMTPARAMS) > -1;
};

function getDotCmsAjaxUrl(url, str) {
    if (url == null) {
        return null;
    }

    var arrSplit = url.split(RMTPARAMS);

    if (arrSplit == null || arrSplit.length < 2) {
        return url + '/' + str;
    }

    var baseUrl = arrSplit[0] + RMTPARAMS;
    var params = decodeBase64(arrSplit[1]);

    arrSplit = params.split('&');

    var rmtac;
    var rmtau;

    for (var i=0;i < arrSplit.length;i++) {
        if (arrSplit[i].indexOf('rmtac') > -1) {
            rmtac = arrSplit[i];
        } else
        if (arrSplit[i].indexOf('rmtau') > -1) {
            rmtau = urlDecode(arrSplit[i]);
        }
    }

    rmtau += '/' + str;
    // params = rmtac + '&' + urlEncode(rmtau);
    params = rmtac + '&' + rmtau;
    params = encodeBase64(params);
    url = baseUrl + params;

    return url;
};

function getDotCmsPageUrl(url, str) {
    if (url == null) {
        return null;
    }

    var arrSplit = url.split(RMTPARAMS);

    if (arrSplit == null || arrSplit.length < 2) {
        return url + '/' + str;
    }

    var baseUrl = arrSplit[0] + RMTPARAMS;
    var params = decodeBase64(arrSplit[1]);

    arrSplit = params.split('&');

    var rmtac;
    var rmtau = null;

    for (var i=0;i < arrSplit.length;i++) {
        if (arrSplit[i].indexOf('rmtac') > -1) {
            rmtac = arrSplit[i];
        } else
        if (arrSplit[i].indexOf('rmtau') > -1) {
            rmtau = 'rmtau=' + urlEncode(treeBaseUrlPrefix + '/' + str);
        }
    }

    // params = rmtac + '&' + urlEncode(rmtau);
    params = rmtac + '&' + rmtau;
    params = encodeBase64(params);
    url = baseUrl + params;

    return url;
};


// Werteerfassen-Dialoge -> change class of Text-Inputs on focus/blur
kgst.Layout.addFeature( function() {
	$('form.werteErfassen table a').attr('tabIndex','-1');
	$('form.werteErfassen table input:text').attr('autocomplete','off');
	return;
	/*
	var jAssignForm = $('form.werteErfassen ');
	if (!jAssignForm.length) return;
	var KEY_UP = 38,
		KEY_DN = 40,
		KEY_TAB = 9,
		keyHandler = function(e) {
			var jTargetInput;
			switch (e.which) {
				case KEY_UP:
						return;
						// later
						var cellIndex = this.parentNode.cellIndex;
						jTargetInput = $(this).parents('tr:first').prev('tr:first').find('td:eq('+cellIndex+') input[type=text]:first');
						break;
				case KEY_DN:
						return;
						// later
						var cellIndex = this.parentNode.cellIndex;
						jTargetInput = $(this).parents('tr:first').next('tr:first').find('td:eq('+cellIndex+') input[type=text]:first');
						break;
				case KEY_TAB:
						if (! /inpIdx(\d+)/.exec(this.className||'') ) return;
						var ownIndex = parseInt(RegExp.$1);
							targetIndex = ownIndex + ((e.shiftKey)? -1 : 1);
						jTargetInput = $(this).parents('form:first').find('input.inpIdx'+targetIndex+':first');
						break;
				default: 
					return;
			}
			if (!jTargetInput || !jTargetInput.length) return;
			jTargetInput.trigger('focus');
			return false;
		};

	$('input[type=text]', jAssignForm).attr('autocomplete','off').bind('keydown', keyHandler).each(function(i,inp) {
		$(this).addClass('inpIdx'+i);
	});
	*/
});

if ((top.location.href||'').indexOf('http://localhost:8080/')==0)
$(function() {
	var url = top.location.href;
	if (!/rmtparams=(.*)/.exec(url)) return;
	var decodedURL = decodeURIComponent(decodeBase64(RegExp.$1));
	$(document.body).append( $('<div style="position:absolute;left:0;top:0;background:yellow">').html(decodedURL) );
});

// KGST-1765: focus first textarea in thickbox if exists
(function() {
    if (typeof tb_show == 'undefined') return;
    var oldFn = tb_show;
    tb_show = function() {
        oldFn.apply(top,arguments);
        $('#TB_ajaxContent textarea:first').each(function(){
              this.focus();
        });
    };
})();


/**
 * Adds a print link to a thickbox window if it contains some element with class=ajaxPrintable
 */
kgst.Tools.addTickboxPrintLink = function() {
	$('.ajaxPrintable:first', $('#TB_ajaxContent')).each( function(i,div) {
		var jPrintLink = $('<a target="_blank" href="'+ kgst.PRINT_URL +'">Drucken</a>');
		$('#TB_closeWindowButton').before(jPrintLink, ' | ');
	});;
};

kgst.Tools.getEmailStatus = function(dotCmsAjaxURLStatus, dotCmsJsURLList) {
    $.ajax({
        url: dotCmsAjaxURLStatus,
        success: function(data) {
            var topnav = $('#topnav');
            var isDotCms = !!topnav.length;
            var attrs = 'url="ikonMailInfo" id="ikonMailInfo"';

            if (isDotCms) {
                attrs += ' style="float:right;"';
                topnav.html(topnav.html() + '<a href="' + dotCmsJsURLList + '" ' + attrs + '></a>');
            } else {
                // isStandalone
                var header = $('.header').find('.userinfo');
                var isHeader = !!header.length;

                if (isHeader) {
                    header.html('<div><a href="' + dotCmsJsURLList + '" ' + attrs + '>' + data + '</a></div>' + header.html());
                } else {
                    var tree = $('#tree');
                    tree.html('<div><a href="' + dotCmsJsURLList + '" ' + attrs + '>' + data + '</a></div>' + tree.html());
                }
            }

            kgst.Tools.updateEmailStatus(dotCmsAjaxURLStatus);
            setInterval(function(){
                kgst.Tools.updateEmailStatus(dotCmsAjaxURLStatus);
            }, 60000); // every minute
        }
    });
};

kgst.Tools.updateEmailStatus = function(dotCmsAjaxURLStatus) {
    $.ajax({
        url: dotCmsAjaxURLStatus,
        dataType: 'html',
        success: function(data) {
            if (data && $.trim(data).length) {
                // if user has logged out meanwhile, returned data is the login page WITHOUT proper HTTP status code
                if (data.indexOf('<form')>=0 ) {
                    // alert('Sie sind nicht eingeloggt. Sie werden nun zur Login-Seite weitergeleitet.');
                    $('#ikonMailInfo').html('');
                    kgst.Tools.jumpToLogin();
                    return;
                } else {
                    $('#ikonMailInfo').html(data);
                }
            }
        }
    });
};

if (typeof _etc == 'undefined') {
	_etc = function(){}; // tracker function causing error when testing locally
}