// -- JSFCore - Basic functionality and tools

// -- This file is copyright (c) 2003-6 by Medialab Solutions BV, and may not be reproduced, 
// -- reused or distributed in any way. This applies to the whole file as well as any portion of it.

// ==================================================================
// Runtime Environment Check
// ==================================================================
var _nua = navigator.userAgent;

var UA_OPERA	= !!(window.opera);
var UA_MSIE		= (!UA_OPERA) && _nua.indexOf("MSIE")>-1;
var UA_SAFARI	= (!UA_OPERA) && _nua.indexOf("AppleWebKit")>-1;
var UA_MOZ		= (!UA_OPERA) && (!UA_SAFARI) && _nua.indexOf("Gecko")>-1;
var UA_OTHER	= !(UA_MSIE || UA_SAFARI || UA_OPERA || UA_MOZ);

var OS_WIN		= _nua.toLowerCase().indexOf("win")>-1;
var OS_MAC		= _nua.toLowerCase().indexOf("mac")>-1;
var OS_OTHER	= !(OS_WIN || OS_MAC);

var ELEMENT_NODE = 1;
var ATTRIBUTE_NODE = 2;
var TEXT_NODE = 3;
var PROCESSING_INSTRUCTION_NODE = 7;

// ==================================================================
// Constants altering system behaviour
// ==================================================================
var JSF_EXC_SHOW_ALL_EXCEPTIONS		= false;


// ==================================================================
// Base object additions
// ==================================================================
String.prototype.trim = function() {
	if(! this.length) return "";
	return this.replace(/^[ \t\n]*/, "").replace(/[ \t\n]*$/, "");
}

String.prototype.capitalize = function() {
	return this.substr(0,1).toUpperCase() + this.substr(1).toLowerCase();
}

Function.prototype.bind = function(thisValue) {
	var thisFunc = this;
	return function(){ return thisFunc.apply(thisValue, arguments); };
}


if(typeof(Array.prototype.indexOf) != "function")
	NATIVE_ARRAY_INDEXOF = false;
else
	NATIVE_ARRAY_INDEXOF = true;

function indexOf(a, searchVal, startIndex) {
	if (NATIVE_ARRAY_INDEXOF)
		return a.indexOf(searchVal, startIndex);
	
	if (startIndex == null)
		startIndex = 0;
	else
		startIndex = Math.floor(startIndex);

	if (!a.length || startIndex >= a.length)
		return -1;

	if (startIndex < 0) 
		startIndex = Math.Max(0, a.length + startIndex);

	for (var i = startIndex; i < a.length; ++i)
		if (a[i] == searchVal)
			return i;
	
	return -1;
}

function lastIndexOf(a, searchVal, startIndex) {
	if (NATIVE_ARRAY_INDEXOF)
		return a.lastIndexOf(searchVal, startIndex);

	if(null == startIndex)
		startIndex = a.length - 1;
	startIndex = Math.floor(startIndex);
	
	if (!a.length)
		return -1;
	if (startIndex > a.length)
		startIndex = a.length - 1;
	if (startIndex < 0)
		startIndex = a.length + startIndex;
	if (startIndex < 0)
		return -1;
		
	for (var i = startIndex; i >= 0; --i)
		if (a[i] === searchVal)
			return i;
	return -1;
		
}


// ==================================================================
// Debug utilities
// ==================================================================
function DumpObj(o, m, t) {
	var i=0, max = m || 25, s="";
	try {
		for(var e in o) {
			try {
				if(!t || (typeof(o[e])==t)) { s += e + " = " + o[e] + "\n"; i++; }
			} catch(r) {
				s += e + ": EXCEPTION: " + r.message + "\n"; i++;
			}
			if(i==max) { alert(s); s=""; i=0; }
		}
	}
	catch(r) {
		E_Raise("Exception in enum! " + r.message);
	}
	finally {
		if(i) alert(s);
	}
}

function DumpObjToString(o, m, t) {
	var i=0, max = m || 25, s="";
	try {
		for(var e in o) {
			try {
				if(!t || (typeof(o[e])==t)) { s += e + " = " + o[e] + "\n"; i++; }
			} catch(r) {
				s += e + ": EXCEPTION: " + r.message + "\n"; i++;
			}
			if(i==max) return s;
		}
	}
	catch(r) {
		s += "Exception in enum! " + r.message;
	}
	finally {
		if(i) return s;
	}
}


// ==================================================================
// String manipulation tools
// ==================================================================
function InlineJString(s) {
	return s.replace(/(['"])/g, function(A,m1){ return "\\"+m1; }).replace(/\r\n|\n|\r/g, "\\n");
}

function Trim(s) { /* old version for backward compat, use strobj.trim() instead */
	return (typeof(s) == "string") ? s.trim() : "";
}

function PadLeft(s, minlen, padstr) {
	while(s.length < minlen) s = padstr + s;
	return s;
}

function PadRight(s, minlen, padstr) {
	while(s.length < minlen) s = s + padstr;
	return s;
}

function ParsePropertyList(list, itemsep, valuesep) {
	var props = {}, pairs, i;

	list = list.trim(); if(0==list.length) return props;
	pairs = list.split(itemsep);

	for(i=0; i<pairs.length; i++) {
		var curprop = pairs[i].trim(), vs, temp;
		if(0==curprop.length) continue;

		vs = curprop.indexOf(valuesep);
		if(0==vs) curprop = curprop.substr(valuesep.length);
		if(vs > -1) {
			temp = curprop.substr(0, vs).trim();
			props[temp] = curprop.substr(vs+valuesep.length).trim();
		} else
			props[curprop] = curprop;
	}
	return props;
}

function BuildPropertyList(object, itemsep, valuesep, encodeValues) {
	var plist = "";
	if(null == encodeValues) encodeValues = true;
	var encFunc = encodeValues ? encodeURIComponent : (function(v){return v;});

	C_ForEach(object, function(v, k) {
		if(plist.length) plist += itemsep;
		plist += k + valuesep + encFunc(v);
	});

	return plist;
}


// ==================================================================
// [Core] Object and array manipulation tools
// ==================================================================
function C_ForEach(oa, lambda, thisObj) {
	if(null == oa) return;
	var i;
	if(thisObj) lambda = lambda.bind(thisObj);
	
	// DB: does this work for 'undefined' values?? need to do some tests
	if(null != oa.length) {
		for(i=0; i<oa.length; ++i) lambda(oa[i], i);
	} else {
		for(i in oa) lambda(oa[i], i);
	}
	return oa;
}

function C_MutateEach(oa, lambda, thisObj) {
	if(null == oa) return;
	var i;
	if(thisObj) lambda = lambda.bind(thisObj);
	if(null != oa.length) {
		for(i=0; i<oa.length; ++i) oa[i] = lambda(oa[i], i);
	} else {
		for(i in oa) oa[i] = lambda(oa[i], i);
	}
	return oa;
}

function C_MutateEachCopy(oa, lambda, thisObj) {
	if(null == oa) return;
	var i, dup;
	if(thisObj) lambda = lambda.bind(thisObj);
	if(null != oa.length) {
		dup = []; for(i=0; i<oa.length; ++i) dup[i] = lambda(oa[i], i);
	}
	else {
		 dup = {}; for(i in oa) dup[i] = lambda(oa[i], i);
	}
	return dup;
}

function C_ContainsKey(arr, key) {
	var contains = false;
	C_ForEach(arr, function(v,k){ if(k==key) contains = true; });
	return contains;
}

function C_ContainsValue(arr, val) {
	var contains = false;
	C_ForEach(arr, function(v,k){ if(v==val) contains = true; });
	return contains;
}

function C_FilterItems(oa, lambda, thisObj) {
	if(null == oa) return;
	var i, filtered;
	if(thisObj) lambda = lambda.bind(thisObj);
	if(null != oa.length) {
		filtered = [];
		for(i=0; i<oa.length; ++i)
			if(lambda(oa[i], i)) filtered.push(oa[i]);
	}
	else {
		 filtered = {};
		 for(i in oa)
		 	if(lambda(oa[i], i)) filtered[i] = oa[i];
	}
	return filtered;
}

function C_AnyMatch(oa, lambda, thisObj) {
	if(null == oa) return false;
	var i;
	if(thisObj) lambda = lambda.bind(thisObj);
	if(null != oa.length) {
		for(i=0; i<oa.length; ++i)
			if(lambda(oa[i], i)) return true;
	}
	else {
		 for(i in oa)
		 	if(lambda(oa[i], i)) return true;
	}
	return false;
}

// ==================================================================
// [Core] Extended variable type information
// ==================================================================
function C_GetEntityType(ent) {
	if(null == ent)
		return "void";
	var type = typeof(ent);
	if(type == "object") {
		if(null != ent.nodeType) type = "element";
		else if(null != ent.document && null != ent.top) type = "window";
		else if(null != ent.constructor) {
			if(Array == ent.constructor) type = "array";
			else if(RegExp == ent.constructor) type = "regexp";
		}
	}
	return type;
}

function C_EntityAsString(ent) {
	var s = "???";
	switch(C_GetEntityType(ent)) {
		case "void":
			s = "void";
			break;
		case "number":
		case "boolean":
			s = "" + ent;
			break;
		case "string":
			s = "\"" + ent + "\"";
			break;
		case "function":
			s = "[function " + (C_GetFunctionSpec(ent).name || "anonymous") + "]";
			break;
		case "object":
			var con = (null!=ent.constructor && typeof(ent.constructor)=="function")?(C_GetFunctionSpec(ent.constructor).name):"";
			if(con.length) con = " " + con;
			s = "[object" + con + "]";
			break;
		case "element":
			var nam = D_GetElemIDOrName(ent);
			if(nam.length) nam = " " + nam;
			s = "[element" + nam + "]";
			break;
		case "window":
			var ttl = (null!=ent.document)?(ent.document.title):"";
			if(ttl.length) ttl = " " + ttl;
			s = "[window" + ttl + "]";
			break;
		case "array":
			s = "[array (" + ent.length + ")]";
			break;
		case "regexp":
			s = "[regexp]";
			break;
	}
	return s;
}


// ==================================================================
// [Core] Function and callstack tools
// ==================================================================
function C_GetFunctionSpec(fn) {
	if(arguments.length != 1 || typeof(fn)!="function") return("Invalid parameter list. Required: function");

	var i;
	var spec = {};
	var sfn = fn.toString();
	sfn = sfn.substring("function".length+1, sfn.indexOf(")")+1).trim();
	spec.name = sfn.substring(0, sfn.indexOf("(")).trim(); // can be empty
	spec.argnames = [];
	spec.argvals = [];
	var arglist = sfn.substring(sfn.indexOf("(")+1,sfn.length-1).split(",");
	for(i=0; i<arglist.length; i++)
		spec.argnames[i] = arglist[i].trim();
	spec.argformalcnt = i;

	if(fn.arguments) {
		for(i=0; i<fn.arguments.length; i++) {
			spec.argvals[i] = fn.arguments[i];
			if(i>=spec.argformalcnt) spec.argnames[i] = "";
		}
	} else
		i = -1;
	spec.argactualcnt = i;

	sfn = fn.toString();
	spec.body = sfn.substring(sfn.indexOf("{")+1, sfn.lastIndexOf("}"));

	spec.ToString = function() {
		var s = (this.name || "anonymous") + " (";
		for(var i=0; i<this.argnames.length; i++) {
			if(this.argnames[i]) {
				s += this.argnames[i];
				if(this.argactualcnt > -1) s += ": ";
			}
	
			if(this.argactualcnt > -1)
				s += (this.argactualcnt > i) ? C_EntityAsString(this.argvals[i]) : "void";

			if(i<this.argnames.length-1) s += ", ";
		}
		return s + ")";
	}

	return spec;
}

function C_TagObjectFunctions(obj, prefix) {
    return;

    // DB: Dirty and should not be done
    
	if(UA_SAFARI || UA_OPERA) return;

	prefix = prefix.trim();
	C_MutateEach(obj, function(func, name) {
		if(typeof(func) != "function") return func;
		var spec = C_GetFunctionSpec(func);
		if(spec.name.length > 0) return func;
		try {
			eval("var nufunc = function " + prefix + name + "(" + spec.argnames.join(",") + ") {\n" + spec.body.trim() + "\n}");
		} catch(e) {
			return func;
		}
		return nufunc;
	});
}

function C_GetCallStack() {
    // DB
//    return;

	var s = "";
	var chk = {};
	var fn = arguments[0] || C_GetCallStack.caller;
	while(fn) {
		if(chk[fn]==1) {
			s += "[*] [Recursion used, cannot continue callstack trace.]"
			break;
		}
		chk[fn] = 1;
		
		if(typeof(fn) != "function") {
			// weird mozilla thingy
			break;
		}
		
		s += "[*] " + C_GetFunctionSpec(fn).ToString() + "\n";
		fn = fn.caller;
	}
	return s;
}

var __C_PATCH_COUNTER = 0;

function C_OverrideMethod(object, methodName, patchFunc) {
	var origFunc = object[methodName];
	if(typeof(origFunc) != "function") E_Raise("Method " + methodName + " does not exist in object " + C_EntityAsString(object));

	var origfs = C_GetFunctionSpec(origFunc);
	var patchfs = C_GetFunctionSpec(patchFunc);

	var args = origfs.argnames.join(",");
	var callargs = "";
	for(var i=0; i<origfs.argnames.length; ++i) { if(callargs.length) callargs += ","; callargs += "arguments[" + i + "]"; }
	
	var saveName = methodName + "_" + __C_PATCH_COUNTER;
	object[saveName] = origFunc;

	object[methodName] = new Function(args,
		"var self=this; function Inherited(){ return self[\"" + saveName + "\"](" + callargs + "); }\n" + patchfs.body);

	++__C_PATCH_COUNTER;
}


// ==================================================================
// [Exceptions] Creation and handling of standard exceptions
// ==================================================================
var __E_Raise_Recurse_Protect = 0;

function E_Raise(src) {
	__E_Raise_Recurse_Protect++;
	if(__E_Raise_Recurse_Protect > 1) {
		alert("E_Raise recursion detected !\nsrc = " + src);
		__E_Raise_Recurse_Protect--;
		return;
	}

	var e = "[!] An exception was raised by ";
	var raiser = E_Raise.caller;
	if(null==raiser)
		e += "a top level script.";
	else
		e += "function " + C_GetFunctionSpec(raiser).name + ".";
	e += "\n\n";

	if(typeof(src)=="object")
		e += "An " + C_EntityAsString(src) + " was passed:\n" + DumpObjToString(src, 10) + "\n";
	else
		e += "A " + C_GetEntityType(src) + " was passed:\n" + C_EntityAsString(src) + "\n\n";

	e += "Call stack:\n";
	if(raiser)
		e += C_GetCallStack(raiser);
	else
		e += "  [not available]";

	__E_Raise_Recurse_Protect--;

	if(JSF_EXC_SHOW_ALL_EXCEPTIONS) alert(e);
	if(window.Error) throw new Error(e);
}


// ==================================================================
// [DOM] Standard element accesses in a document or sub-element.
// D_Elem raises an exception if element is not found in target 
// document. Use D_ElemUnsafe if you just want it to return null.
// ==================================================================
function D_Elem(elemOrID, cont) {
	var doc = cont || document;
	if(null == doc.getElementById)
		E_Raise("The (passed) container is not a valid document object.");

	if(typeof(elemOrID) == "string") {
		elemOrID = elemOrID.trim();
		var e = doc.getElementById(elemOrID);
		if(!e) E_Raise("Element \"" + elemOrID + "\" does not exist.");
	} else
		return elemOrID;
	return e;
}

function D_ElemUnsafe(elemOrID, cont) {
	var doc = cont || document;
	if(null == doc.getElementById)
		E_Raise("The (passed) container is not a valid document object.");

	if(typeof(elemOrID) == "string")
		return doc.getElementById(elemOrID.trim());
	else
		return elemOrID;
}

function D_ElemsOfType(type, cont) {
	type = type.trim();
	var doc = cont || document;
	if(null==doc.getElementsByTagName)
		E_Raise("The (passed) object is not a valid element container.");

	var elems = doc.getElementsByTagName(type);

	if(("*"==type) && (0==elems.length)) {
		var i;
		elems = [];
		var base = doc; var todo = [], todoidx = -1;
		while(base) {
			if(base.childNodes && base.childNodes.length) {
				for(i=0; i<base.childNodes.length;i++)
					if(base.childNodes[i].nodeType == ELEMENT_NODE) {
						elems[elems.length] = base.childNodes[i];
						todo[++todoidx] = base.childNodes[i];
					}
			}
			base = (todoidx>-1)?todo[todoidx--]:null;
		}
	}

	return elems;
}

function D_GetElemIDOrName(elem) {
	var name = "";
	if(elem.id && elem.id.length)
		name = elem.id;
	else if(elem.name && elem.name.length)
		name = elem.name;
	return name;
}

function D_IsChildOf(child, parent) {
	do {
		if((child = child.parentNode) && child == parent) return true;
	} while(child);
	return false;
}


// ==================================================================
// [DOM] Event handling
// ==================================================================
function D_AddEvent(elemOrID, evtName, handler, thisObj) {
	var el = D_Elem(elemOrID);
	
	if(thisObj) handler = handler.bind(thisObj);
	evtName = evtName.toLowerCase().trim();
	if(evtName.substr(0,2) == "on") evtName = evtName.substr(2);

	var preprocessor = function() {
		var evt = arguments[0] || window.event;
		var tgt = evt.target || evt.srcElement;
		if(tgt && tgt.nodeType == TEXT_NODE) tgt = tgt.parentNode;
		return handler(evt, evtName, tgt);
	};

	if(el.addEventListener)
		el.addEventListener(evtName, preprocessor, false);
	else if(el.attachEvent)
		el.attachEvent("on" + evtName, preprocessor);
}


// ==================================================================
// [DOM] Standardized IFrame functions
// ==================================================================
function D_GetIFrameDocument(ifraID) {
	var ifra = D_Elem(ifraID);
	if(ifra.tagName.toLowerCase() != "iframe") E_Raise("Element with id = '" + ifraID + "' is NOT an iframe!");

	if(null != ifra.contentWindow) return ifra.contentWindow.document;
	if(null != ifra.contentDocument) return ifra.contentDocument;

	return null;
}

function D_GetIFrameWindow(ifraID) {
	var ifra = D_Elem(ifraID);
//	if(ifra.tagName.toLowerCase() != "iframe") E_Raise("Element with id = '" + ifraID + "' is NOT an iframe!");

	return ifra.contentWindow || null;
}

function D_SetIFrameLocation(ifraID, url, replace) {
	var ifra = D_GetIFrameDocument(ifraID);
	if(!ifra && UA_SAFARI) { setTimeout(function(){ if(!arguments[3]) D_SetIFrameLocation(ifraID, url, replace, true); }, 50); return; }

	// strip hash & args
	var uri = '';
	var args = '';
	var hash = '';
	
	var qm = url.indexOf("?");
	var hm = url.indexOf("#");
	
	if (qm != -1 && hm != -1) {
		uri = url.substring(0, qm);
		args = url.substring(qm, hm);
		hash = url.substring(hm, url.length);
	}
	else if (qm != -1) {
		uri = url.substring(0, qm);
		args = url.substring(qm + 1, url.length);
	}
	else if (hm != -1) {
		uri = url.substring(0, hm);
		hash = url.substring(hm + 1, url.length);
	}
	else {
		uri = url;
	}

	// -- explicitly supply referring domain + path for mixed external/ABL site solutions
	if(!uri.match(/^http(s)?\:/)) {
		var qmark = location.href.indexOf("?");
		var idx = location.href.lastIndexOf("/");
		if(idx > qmark) idx = location.href.substr(0, qmark).lastIndexOf("/");

		uri = location.href.substr(0, idx + 1) + uri;
	}

	// if federation frame is called, make sure it does not trigger the history
	var federationUpdate = ifraID == 'ifraFederation' ? true : false;
	
	if (!federationUpdate) {
		var frameid = new Date().getTime();
		hash += "&frame=" + frameid;
	}


	if (UA_SAFARI && !federationUpdate) {
		// safari does not keep history in iframes, except when using form submits.
		var df = ifra.createElement('form');
		df.setAttribute("method","get");
		df.setAttribute("action", uri + "#" + hash);
		var props = ParsePropertyList(args, "&", "=");
		C_ForEach(props, function(v, k) {
			var input = ifra.createElement('input');
			input.setAttribute('type', 'hidden');
			input.setAttribute('name', k);
			input.setAttribute('value', decodeURIComponent(v));
			df.appendChild(input);
		});
		
		ifra.body.appendChild(df);
		df.submit();
		ifra.body.removeChild(df);		
	}
	else {
		url = args.length ? uri + "?" + args : uri;
		url += "#" + hash;
		if (replace || false) 
		  ifra.location.replace(url);
		else 
		  ifra.location.href = url;
	}
	
}


// ==================================================================
// [DOM] CSS helpers
// ==================================================================
function D_UsesCSSClass(elemOrID, className) {
	var elem = D_Elem(elemOrID);
	return (" " + elem.className.trim() + " ").indexOf(" " + className.trim() + " ") > -1;
}

function D_HasCSSClass(elemOrID, className) {
	var elem = D_Elem(elemOrID);
	var classes = elem.className.split(/\s+/);
	return (indexOf(classes, className) > -1)
}


function D_AddCSSClass(elemOrID, className) {
	var elem = D_Elem(elemOrID);

	className = className.trim();
	if (D_HasCSSClass(elem, className))
		return false;
		

	elem.className += ( elem.className ? " " : "" ) + className;
	return true;
}

function D_RemoveCSSClass(elemOrID, className) {
	var elem = D_Elem(elemOrID);

	className = className.trim();

	if (!D_HasCSSClass(elem, className))
		return false;

	var classes = elem.className.split(/\s+/);
	var removed = classes.splice(indexOf(classes, className), 1);
	elem.className = classes.join(" ");
		
	return true;
}

function D_ReplaceCSSClass(elemOrID, fromClass, toClass) {
	D_RemoveCSSClass(elemOrID, fromClass);
	D_AddCSSClass(elemOrID, toClass);
//	if(D_RemoveCSSClass(elemOrID, fromClass)) D_AddCSSClass(elemOrID, toClass);
}

function D_CancelEvent(evt) {
	if(evt.preventDefault) evt.preventDefault();
	if(evt.stopPropagation) evt.stopPropagation();
	evt.cancelBubble = true;
	evt.returnValue = false;
}

String.prototype.chop = function(at, indicator) {
	if(at <= 0) return "";
	if(this.length > at)
		return this.substr(0, at - 1) + indicator;
	return "" + this;
};


// stole from http://cass-hacks.com/articles/code/js_url_encode_decode/
function NonUTF8URLDecode (encodedString) {
  var output = encodedString;
  var binVal, thisString;
  var myregexp = /(%[^%]{2})/;
  while ((match = myregexp.exec(output)) != null
             && match.length > 1
             && match[1] != '') {
    binVal = parseInt(match[1].substr(1),16);
    thisString = String.fromCharCode(binVal);
    output = output.replace(match[1], thisString);
  }
  return output;
}


window.JSF_CORE_LOADED = true;
