/**
Toni Rantanen
http://www.kotimaa.fi/verkkopalvelut
*/

/* GENERIC FUNCTIONS */

function isset(obj)
{
	return typeof(obj) == 'undefined' || obj === null ? false : true;
}

function exists(obj)
{
	return typeof(obj) == 'undefined' ? false : true;
}

function getFuncName(fn)
{
	var name = (/\W*function\s+([\w\$]+)\s*\(/).exec(fn);
	if(!name)
		return '(Anonymous)';
	return name[1];
}

function getObjClass(obj)
{
	if(!isset(obj))
		return 'undefined';
	return getFuncName(obj.constructor);
}

/* LANGUAGE PROTOTYPES  */

// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach
if (!Array.prototype.forEach)
{
	Array.prototype.forEach = function(fun /*, thisp*/)
	{
		var len = this.length;
		if (typeof fun != "function")
			throw new TypeError();

		var thisp = arguments[1];
		for (var i = 0; i < len; i++)
		{
			if (i in this)
				fun.call(thisp, this[i], i, this);
		}
	};
}

// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:indexOf
if (!Array.prototype.indexOf)
{
	Array.prototype.indexOf = function(elt /*, from*/)
	{
		var len = this.length;

		var from = Number(arguments[1]) || 0;
		from = (from < 0)
				? Math.ceil(from)
				: Math.floor(from);
		if (from < 0)
			from += len;

		for (; from < len; from++)
		{
			if (from in this &&
					this[from] === elt)
				return from;
		}
		return -1;
	};
}

// http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:lastIndexOf
if (!Array.prototype.lastIndexOf)
{
	Array.prototype.lastIndexOf = function(elt /*, from*/)
	{
		var len = this.length;

		var from = Number(arguments[1]);
		if (isNaN(from))
		{
			from = len - 1;
		}
		else
		{
			from = (from < 0)
					? Math.ceil(from)
					: Math.floor(from);
			if (from < 0)
				from += len;
			else if (from >= len)
				from = len - 1;
		}

		for (; from > -1; from--)
		{
			if (from in this &&
					this[from] === elt)
				return from;
		}
		return -1;
	};
}

/* returns true if $needle exists in this array */
if (!Array.prototype.inArray)
{
	Array.prototype.inArray = function(needle, strict)
	{
		strict = isset(strict) ? strict : false;
		var result = false;
		
		if(strict)
		{
			this.forEach(function(value){
				if(value === needle)
					result = true;
			});
		}
		else
		{
			this.forEach(function(value){
				if(value == needle)
					result = true;
			});
		}
		
		return result;
	};
}

/* returns array with items from all provided $array[s] */
if (!Array.prototype.arrayMerge)
{
	Array.prototype.arrayMerge = function(/*array2, array3, ...*/)
	{
		if(arguments.length < 1)
			return false;
		
		var result = new Array();
		
		// add self
		this.forEach(function (val) {
			result.push(val);
		});
		// add args
		for(var i = 0; i < arguments.length; i++)
		{
			arguments[i].forEach(function (val) {
				result.push(val);
			});
		}
		
		return result;
	};
}

/* returns string with all array items seperated with $glue */
if (!Array.prototype.implode)
{
	Array.prototype.implode = function(glue)
	{
		var result = '';
		var i = false;
		
		this.forEach(function (id)
		{
			if(i)
				result += glue;
			else
				i = true;
			
			result += id;
		});
		
		return result;
	};
}

/* clones array and it's sub arrays */
if (!Array.prototype.clone)
{
	Array.prototype.clone = function()
	{
		var result = new Array();
		
		for(var key in this)
			if(typeof(this[key]) == 'object' && getObjClass(this[key]) == 'Array')
				result[key] = this[key].clone();
			else
				result[key] = this[key];
		
		return result;
	};
}

/* returns string with all array items seperated with $glue */
if (!String.prototype.repeat)
{
	String.prototype.repeat = function(times)
	{
		var result = '';
		
		for(var i = 0; i < times; i++)
			result += this;
		
		return result;
	};
}

/* COOKIES */

// based on: http://techpatterns.com/downloads/javascript_cookies.php
var cookieSet = function(name, value, expires, path, domain, secure)
{
	// set time, it's in milliseconds
	var today = new Date();
	today.setTime(today.getTime());
	
	var expiresDate = null;
	if(expires)
		expiresDate = new Date(today.getTime() + (expires * 1000));
	
	document.cookie = name +'='+ escape(value) +
		((expires) ? ';expires='+ expiresDate.toGMTString() : '') +
		((path) ? ';path='+ path : '') +
		((domain) ? ';domain='+ domain : '') +
		((secure) ? ';secure' : '');
}
var cookieGet = function (name)
{
	// split cookies up into name/value pairs
	var cookieList = document.cookie.split(';'); // document.cookie only returns name=value
	
	var cookie = '';
	var cookieName = '';
	var cookieValue = '';
	for(i = 0; i < cookieList.length; i++)
	{
		// split apart each name=value pair
		cookie = cookieList[i].split('=');
		
		// check cookie name
		cookieName = cookie[0].replace(/^\s+|\s+$/g, ''); // trim left/right whitespaces
		if(cookieName == name)
		{
			// check if cookie has no value
			if(cookie.length > 1)
			{
				cookieValue = unescape(cookie[1].replace(/^\s+|\s+$/g, '')); // trim left/right whitespaces
				if(cookieValue.length > 1)
					return cookieValue;
			}
			return null;
		}
	}
	return false;
}
var cookieDelete = function (name, path, domain)
{
	if(cookieGet(name) !== false)
	{
		document.cookie = name +'='+
			((path) ? ";path=" + path : '') +
			((domain) ? ";domain=" + domain : '') +
			';expires=Thu, 01-Jan-1970 00:00:01 GMT';
	}
}
// test if cookies are enabled
var cookieTest = function()
{
	var i = cookieGet('cookie');
	if(i === false)
	{
		cookieSet('cookie', '1');
		i = cookieGet('cookie');
	}
	if(i === false)
		return false;
	else
		return true;
}

/* OBJECTS */

/* returns value of specified $key from $obj or $def if key doesn't exist */
function getVar(obj, key, def)
{
	if(typeof(def) == 'undefined') def = null;
	
	if(typeof(obj) != 'object' || obj === null)
		return def;
	
	return typeof(obj[key]) != 'undefined' ? obj[key] : def;
}

/* returns array of object keys */
function getKeys(obj)
{
	if(typeof(obj) != 'object')
		return false;
	
	var keys = new Array();
	for(var key in obj)
		keys.push(key);
	
	return keys;
}

/* returns array of object values */
function getValues(obj)
{
	if(typeof(obj) != 'object')
		return false;
	
	var vals = new Array();
	for(var key in obj)
		vals.push(obj[key]);
	
	return vals;
}

/* create new object, by merging similar keys (todo: remove links!) */
function getMerged()//addAll)
{
	//addAll = isset(addAll) ? addAll : false;
	
	if(arguments.length < 2)
		return false;
	
	var result = {};
	var x = 0;
	for(var i = 0; i < arguments.length; i++)
	{
		if(typeof(arguments[i]) == 'object' && arguments[i] !== null)
		{
			for(var key in arguments[i])
				result[key] = arguments[i][key];
		}
		/*else if(addAll)
		{
			result[x] = arguments[i];
			x++;
		}*/
	}
	
	return result;
}

/* create new object with renamed key */
function getObjectRenamed(obj, keyOld, keyNew)
{
	var result = {};
	for(var key in obj)
	{
		if(key === keyOld)
			result[keyNew] = obj[key];
		else
			result[key] = obj[key];
	}
	return result;
}

// get value from $object tree by traveling into it according to $path
// or $def on failure
function getObjectPath(path, object, def)
{
	def = exists(def) ? def : null;
	var p = path.clone();
	
	var key = null;
	while(key = p.shift())
	{
		if(!isset(object[key])) // return when search fails
			return def;
		object = object[key]; // step into next
	}
	
	return object;
}

// set $value into $object tree by traveling into it according to $path
// path will be created, if path doesn't exist and $add is true
function setObjectPath(path, value, object, add)
{
	add = isset(add) ? add : false;
	var p = path.clone();
	
	// get target
	var trg = p.pop();
	if(trg === false || typeof(trg) == 'undefined')
		return false;
	
	// get parent
	while(1)
	{
		var key = p.shift();
		if(key === false || typeof(key) == 'undefined')
			break;
		
		if(!exists(object[key]))
			if(!add)
				return false; // return when search fails
			else
				object[key] = {}; // add missing
		
		object = object[key]; // step into next
	}
	
	// set value
	object[trg] = value;
	return true;
}

/* URLS */

// open page in a popup
function pagePopup(url, name, scrollbar, width, height)
{
	if(isset(name))
	{
		var date = new Date();
		name = date.getTime();
	}
	properties = "width="+ width +
		",height="+ height +
		",scrollbars="+ (scrollbar ? 'yes' : 'no') +
		",top="+ ((screen.width) ? (screen.width - width) / 2 : 100) +
		",left="+ ((screen.height) ? (screen.height - height) / 2 : 100);
	window.open(url, name, properties);
}

// redirect page to url with post method and vars
function pageRedirect(url, vars, method)
{
	var form = document.createElement('form');
	form.setAttribute('action', url);
	form.setAttribute('method', typeof(method) != 'undefined' ? method.toUpperCase() : 'POST');
	form.style.display = 'none';
	
	if(typeof(vars) != 'undefined' && vars !== null)
		for(var key in vars)
		{
			var field = document.createElement('input');
			field.setAttribute('type', 'hidden');
			field.setAttribute('name', key);
			field.setAttribute('value', vars[key]);
			form.appendChild(field);
		}
	
	document.body.appendChild(form);
	form.submit();
	return false;
}

// according to $trg get current 'url', url-'path' or url-'vars'
function urlGet(trg)
{
	switch(trg)
	{
		case 'path':
			return location.protocol +'//'+ location.host + location.pathname;
		case 'vars':
			return location.search.substring(1);
		case 'url':
		default:
			return location.href;
	}
}

// get path part from url
// url defaults to current url
function urlGetPath(url)
{
	if(!isset(url))
		return urlGet('path');
	
	var vars = url.split('?', 2);
	return vars[0];
}

// get vars part from url
// url defaults to current url
function urlGetVars(url)
{
	var result = {};
	
	if(!isset(url))
		url = urlGet('vars');
	else if((/^[a-z0-9]+:\/\//i).exec(url))
	{
		var tmp = url.split('?', 2);
		if(tmp.length == 2)
			url = tmp[1];
		else
			return result;
	}
	
	var vars = url.split('&');
	vars.forEach(function(rawvar){
		var pos = rawvar.indexOf('=');
		var key = rawvar.substring(0, pos);
		var val = unescape(rawvar.substring(pos + 1));
		result[key] = val;
	});
	
	return result;
}

// returns url vars string from $vars object
function urlObjectToVars(vars)
{
	if(typeof(vars) != 'object' && vars !== null)
		return false;
	
	var i = 0;
	var result = '';
	for(var key in vars)
	{
		if(i)
			result += '&';
		else
			i = 1;
		result += key +'='+ vars[key];
	}
	
	return result;
}

// overide url variables width $vars
function urlMergeVars(url, vars)
{
	var path = urlGetPath(url);
	var urlvars = urlGetVars(url);
	
	urlvars = getMerged(urlvars, vars);
	
	return path +'?'+ urlObjectToVars(urlvars);
}

/* DEBUGGING */

function dumpAlert(obj, maxDepth, keyList, keyMode)
{
	alert(dump(obj, maxDepth, keyList, keyMode));
}
function dumpDoc(obj, maxDepth, keyList, keyMode)
{
	var result = dump(obj, maxDepth, keyList);
	result = result.replace(/\n/g, "<br />\n");
	result = result.replace(/    /g, "&nbsp;&nbsp;&nbsp;&nbsp;");
	//document.write("<pre>"+ result +"</pre>");
	document.write(result);
	document.close();
	return true;
}
function dumpHtml(obj, doc, maxDepth)
{
	doc = isset(doc) ? doc : false;
	/* type filtering for the future ?
	var types = new Array(
		'HTMLBodyElement',
		'HTMLDocument',
		'NamedNodeMap',
		'NodeList',
		'CSSStyleDeclaration',
		'XULControllers' // mozilla
	);*/
	
	var keyList = new Array(
		'controllers', // mozilla xul controllers
		'parentNode',
		//'childNodes',
		'offsetParent',
		//'innerHTML',
		'firstChild',
		'lastChild',
		'previousSibling',
		'nextSibling',
		'ownerElement',
		'ownerDocument',
		'attributes',
		'style'
	);
	if(doc)
		return dumpDoc(obj, maxDepth, keyList, 2);
	else
		return dump(obj, maxDepth, keyList, 2);
	
	/*var keyList = new Array(
		'__dump__',
		'childNodes'
	);
	if(doc)
		return dumpDoc(obj, maxDepth, keyList, 1);
	else
		return dump(obj, maxDepth, keyList, 1);*/
}
function dump(obj, maxDepth, keyList, keyMode)
{
	var tmp = {'__dump__': obj};
	return _dump(tmp, maxDepth, keyList, keyMode);
}
// todo: filter objects, arrays, functions
function _dump(obj, maxDepth, keyList, keyMode, indent, depth)
{
	var ws = '    ';
	var result = '';
	
	keyMode = isset(keyMode) ? keyMode : 0;
	maxDepth = isset(maxDepth) ? maxDepth : 5;
	keyList = isset(keyList) ? keyList : new Array();
	indent = (!indent) ? 0 : indent;
	depth = (!depth) ? 0 : depth;
	
	if(depth > maxDepth)
		return ws.repeat(indent) +'*Maximum Depth*\n';
	
	for(var key in obj)
	{
		var keyType = '';
		try { keyType = typeof(obj[key]); }
		catch(e) { keyType = '*Unknown type*'; }
		
		var keyStr = '';
		try { keyStr = obj[key] === null ? 'null' : obj[key].toString(); }
		catch(e) { keyStr = '*Unknown content*'; }
		
		// print type and name
		var entry = ws.repeat(indent);
		entry += "'"+ key +"' ";
		if(keyType == 'object' && keyStr.indexOf('object ') == 1)
			entry += keyStr;
		else
			entry += '['+ keyType +'] ';
		entry += "=> ";
		
		if(!keyMode || (keyMode == 1 && keyList.inArray(key)) || (keyMode == 2 && !keyList.inArray(key)))
		{
			if(keyType == 'object')// Object/Array/Hash
			{
				if(keyStr == 'null')
					entry += 'null';
				else
				{
					entry += '\n'+ ws.repeat(indent) +'(\n';
					indent++;
					entry += _dump(obj[key], maxDepth, keyList, keyMode, indent, depth + 1);
					indent--;
					entry += ws.repeat(indent) +')';
				}
			}
			else // Function/String/Char/Number
			{
				if(keyType == 'function' && keyStr.indexOf('[native code]') != -1)
					entry += '*Native*';
				else if(keyType == 'string')
					entry += '"'+ keyStr +'"';
				else
					entry += keyStr;
			}
			result += entry +'\n';
		}
		/*else
		{
			entry += '*Filtered object*';
		}
		result += entry +'\n';*/
	}
	
	return result;
}
function dumpArray(obj, doc, html)
{
	html = isset(html) ? html : true;
	doc = isset(doc) ? doc : false;
	
	var spacer = html ? '<br />\n' : '\n';
	
	var result = _dumpArray([obj]);
	
	if(doc === true)
	{
		result = result.replace(/\n/g, "<br />\n");
		result = result.replace(/    /g, "&nbsp;&nbsp;&nbsp;&nbsp;");
		//document.write("<pre>"+ result +"</pre>");
		document.write(result);
		document.close();
		return true;
	}
	
	var el = document.getElementById(doc);
	if(el !== null)
	{
		if(el.innerHTML.length)
			el.innerHTML += spacer + result;
		else
			el.innerHTML = result;
	}
	else
		alert(result);
	return true;
}
function _dumpArray(obj, maxDepth, indent, depth)
{
	var ws = '    ';
	var result = '';
	
	maxDepth = isset(maxDepth) ? maxDepth : 5;
	indent = (!indent) ? 0 : indent;
	depth = (!depth) ? 0 : depth;
	
	if(depth > maxDepth)
		return ws.repeat(indent) +'*Maximum Depth*\n';
	
	if(typeof(obj['forEach']) == 'undefined')
		return ws.repeat(indent) +'*Object*\n';
	
	obj.forEach(function(item, key)
	{
		var keyType = '';
		try { keyType = typeof(obj[key]); }
		catch(e) { keyType = '*Unknown type*'; }
		//var keyType = typeof(item);
		
		var keyStr = '';
		try { keyStr = obj[key] === null ? 'null' : obj[key].toString(); }
		catch(e) { keyStr = '*Unknown content*'; }
		//var keyStr = item === null ? 'null' : item.toString();
		
		// print type and name
		var entry = ws.repeat(indent);
		entry += "'"+ key +"' ";
		if(keyType == 'object' && keyStr.indexOf('object ') == 1)
			entry += keyStr;
		else
			entry += '['+ keyType +'] ';
		entry += "=> ";
		
		if(keyType == 'object')// Object/Array/Hash
		{
			if(keyStr == 'null')
				entry += 'null';
			else
			{
				entry += '\n'+ ws.repeat(indent) +'(\n';
				indent++;
				entry += _dumpArray(item, maxDepth, indent, depth + 1);
				indent--;
				entry += ws.repeat(indent) +')';
			}
		}
		else // Function/String/Char/Number
		{
			if(keyType == 'function')
				entry += '*Function*';
			else if(keyType == 'string')
				entry += '"'+ keyStr +'"';
			else
				entry += keyStr;
		}
		result += entry +'\n';
	});
	
	return result;
}
