//globals
var TPG;
if (!TPG) TPG = {};

TPG.K_ARROWLEFT = 37;
TPG.K_ARROWRIGHT = 39;
TPG.K_ARROWUP = 38;
TPG.K_ARROWDOWN = 40;
TPG.K_PAGEUP = 33;
TPG.K_PAGEDOWN = 34;
TPG.K_HOME = 36;
TPG.K_END = 35;
TPG.DEBUG = false;


// generic functions
function $(id) {
	return document.getElementById(id);
}

// string manipulation 

//format string (call as method on string). Takes any number of parameters. Each occurance of %s will be replaced with 
//a parameter
String.prototype.printf = function(){
	var args = arguments;
    var tokens = this.split('%s');
    var result = [];
	for (var i = 0; i < tokens.length - 1; i++) {
		result.push(tokens[i])
        result.push(args[i]);
    }
	result.push(tokens[i])
    // arguments that aren't specified are simply appended 
    for (var j = i; j < args.length; j++) {
        result.push(", " + args[j]);
    }
    return result.join('');
}

// array manipulation

TPG.inArray = function(ar, value) {
	for (var i =0; i < ar.length; i++) {
		if (ar[i] == value)
			return true;	
	}
	return false;
}

// class handling

TPG.getElementsByClassName = function(root,clsName,htmltag){ 
	var arr = []; 
	var elems = root.getElementsByTagName(htmltag);
	for ( var cls, i = 0; ( elem = elems[i] ); i++ ) {
		if ( elem.className.search("(\\b|^)" + clsName + "(\\b|$)") != -1 ) {
			arr[arr.length] = elem;
		}
	}
	return arr;
}

TPG.hasClassName = function(e,c) {
	if (typeof e == "string") e = $(e);
	classes = e.className;
	if (!classes) return false;
	if (classes == c) return true
	return e.className.search("\\b" + c + "\\b") != -1;
}

TPG.addClassName = function(e,c) {
	if (typeof e == "string") e = $(e);
	if (TPG.hasClassName(e, c)) return;
	if (e.className) e.className = e.className + " " + c;
}

TPG.removeClassName = function(e,c) {
	if (typeof e == "string") e = $(e);
	e.className = e.className.replace(new RegExp("\\b" + c + "\\b\\s*", "g"), "");
}

//looks for name-value pairs in classnames, useful for unobtrusive scripting. Pairs must be separated by a dash.
//If a class name contains multiple dashes, then the value will be an array of the remaining tokens (the first will be the var's name)
TPG.getVarsFromClass = function(node) {
	var classNames = node.className.split(' ');
	var vars = [];
	var tokens = [];
	for (var i = 0; i < classNames.length; i++) {
		tokens = classNames[i].split('-');
		if (tokens.length > 1)
			vars[tokens[0]] = tokens.length > 2 ? tokens.splice(1) : tokens[1]; // either store a single value ore multiple values as an array
	}
	return vars;
}

// focus handling
TPG.setFocusTo = function(nodeOrId) {
	var node = typeof nodeOrId == "string" ? $(nodeOrdid) : nodeOrId;
	if (!node) return;
	node.focus();
}

TPG.isFocusable = function(nodeOrId) {
	var focusableElements = ['a', 'input', 'textarea', 'select', 'button', 'body', 'frame', 'iframe', 'object', 'applet'];
	if (typeof nodeOrId == "string") nodeOrId = $(nodeOrId);
	if (!nodeOrId || nodeOrId.nodeType !== 1) return false;
	for (var i = 0; i < focusableElements; i++) {
		if (nodeOrId.nodeName.toLowerCase() == focusableElements[i])
			return true;
	}
	if (nodeOrId.getAttribute('tabindex') !== undefined && parseInt(nodeOrId.getAttribute('tabindex')) >= -1 && nodeOrId.style.display != "none") {
		return true;
	}
	return false;
}

TPG.getAdjacentFocusNode = function(node, backwards, stopAtBorders) {
	if (!node || !node.parentNode) return;
	var siblingCount = node.parentNode.childNodes.length;
	var startNode = node;
	if (siblingCount <= 0) return false;
	for (var i = 0; i < siblingCount; i++) {
		try {
			node = !backwards ? node.nextSibling : node.previousSibling;
		}
		catch(e) { //happens when edge node was used (first or last)
			if (!node && !stopAtBorders)
				node = !backwards ? startNode.parentNode.firstChild : startNode.parentNode.lastChild;
		}
		if (TPG.isFocusable(node)) 
		 return node;
	}
	return false ;
}

/***** EVENT HANDLING *****/

TPG.addHandler = function(element, type, handler, capture) {
	if (document.addEventListener)
		element.addEventListener(type, handler, capture);
	else if (document.attachEvent)
		element.attachEvent('on' + type, handler)
	else
		element['on' + type] = handler;
}

TPG.removeHandler = function(element, type, handler, capture ) {
	if (document.removeEventListener)
		element.removeEventListener(type, handler, capture);
	else if (document.detachEvent)
		element.detachEvent('on' + type, handler)
	else
		element['on' + type] = null;
}

// attempts to create an Xbrowser consistent event object, by adding custom properties
TPG.getEvent  = function(event) {
	var e = event || window.event;
	e.myTarget = e.target || e.srcElement;
	e.myKeyCode = e.keyCode || e.chsrCode;
	return e;
}

//Assigns event handlers to a nodelist 
TPG.addEventToNodes = function(rootNode, className, nodeName, eventType, handler ) {
	var nodes = getElementsByClassName(rootNode, className, nodeName);
	for (var i = 0; i < nodes.length; i++)
		nodes[i]['on' + eventType] = handler;
	nodes = undefined;
}

/***** KEY HANDLING *****/

TPG.isArrowKey = function(code) {
	return TPG.isVerticalArrowKey(code) || TPG.isHorizontalArrowKey(code);
}
TPG.isVerticalArrowKey= function(code) {
	return code == TPG.K_ARROWUP || code == TPG.K_ARROWDOWN;
}

TPG.isHorizontalArrowKey = function(code) {
	return code == TPG.K_ARROWLEFT || code == TPG.K_ARROWRIGHT;
}

/***** DEBUGGING *****/

TPG.enableLogger = function(yesOrNo) {
	if (TPG.DEBUG && !yesOrNo) {
		TPG.DEBUG = false;
		if ($("TPGLogPane"))
			$("TPGLogPane").parentNode.removeChild($("TPGLogPane"));
	}
	else if (!TPG.DEBUG && yesOrNo) {
		TPG.DEBUG = true;
		TPG.log("TPG.logger enabled");
	}
		
}
/* 
Uses Firebug if installed, otherwise creates a logger div.
Can be called with following parameters:
1 string param:  type param is not used (type will default to 'log'), instead it's treated as the omitted msg param
1 non-string param: value is treated as object, all properties are listed
2 string params: first param is type, which can be 'log', 'warn' or 'info' (add more if needed), which are accepted 
by firebug. If there's no firebug, a classname will be added to the log message (styles still have to be added).
3 or more params: For first to see above, all remaining params are used for String.printf feature
*/
TPG.log = function(typeOrValue, msg) {
	if (!TPG.DEBUG)
		return;
    var startCountAt = 2, myType = typeOrValue, myMsg = msg, consoleCall;
    var evalParams = "";
    if (typeOrValue != "log" && typeOrValue != "warn" && typeOrValue != "info") {
        myMsg = typeOrValue;
        myType = 'log';
        startCountAt = 1;
    }
	myMsg = myMsg + ""; //we need to run string methods on this later, to cast to string if needed
    if (arguments.length > startCountAt) {
       for (var i = startCountAt; i < arguments.length; i++) {
           evalParams += "'"+arguments[i]+"'";
           if (i < arguments.length -1)
               evalParams += ",";
       }
    }
    if (typeof console == "undefined") {
		try {// Should be more robust, use try catch for now
			if (!$('TPGLogPane')) {
				var logger = document.createElement('div');
				logger.id = "TPGLogPane";
				logger.style.position = "fixed";
				logger.style.bottom = "15px";
				logger.style.right = "15px";				
				logger.style.padding = "10px";			
				logger.style.width = "200px";
				logger.style.height = "150px";
				logger.style.overflow = "scroll";				
				logger.style.border = "1px solid black";
				logger.style.background = "white";				
				logger.style.font = "10px Arial";
				logger.style.color = "#333333";
				logger.style.zIndex = "999999";
				document.body.appendChild(logger);
			}
			var logPane = $('TPGLogPane');
            var logList = logPane.getElementsByTagName('OL')[0];
            if (!logList) {
                logPane.innerHTML = "<ol style='padding: 0px; margin: 0px;'></ol>";
                logList = logPane.getElementsByTagName('OL')[0];
				logList.style.listStyle = "none";
            }
            var now = new Date();
            var logItem = document.createElement('LI');
            var consoleCall = 'logItem.appendChild(document.createTextNode(now.toLocaleTimeString() +": " + myMsg.printf('+evalParams+')))';
            if (typeOrValue == "warn") logItem.className = "logWarning";

			eval(consoleCall);

            logList.appendChild(logItem);
            logItem.scrollIntoView();
        }
        catch(e){}
    }
    else {
        var now = new Date();
        evalParams = evalParams == "" ? "": "," + evalParams;
		consoleCall = "console."+myType+"(now.toLocaleTimeString() + ':' + '"+myMsg.replace(/'/, "\\'") +"'"+evalParams+")";
        
		try { eval(consoleCall);} catch(e){}//*/
    }
}

/***** Form Handling *****/
TPG.getCheckedValue = function(radioObj) {
	if(!radioObj)
		return "";
	var radioLength = radioObj.length;
	if(radioLength == undefined)
		if(radioObj.checked)
			return radioObj.value;
		else
			return "";
	for(var i = 0; i < radioLength; i++) {
		if(radioObj[i].checked) {
			return radioObj[i].value;
		}
	}
	return "";
}

TPG.setCheckedValue = function(radioObj, value) {
	if(!radioObj)
		return "";
	var valueFound = false;
	var radioLength = radioObj.length;
	for(var i = 0; i < radioLength; i++) {
		if(radioObj[i].value == value + "") { //convert value to string if needed
			radioObj[i].checked = true;
			valueFound = true;
		}
	}
	if (!valueFound)
		radioObj[0].checked = true;
}

TPG.toggleDisabled = function(element) {
	element.disabled =  !element.disabled;
}

TPG.setDisabled = function(element, makeDisabled) {
	element.disabled =  makeDisabled;
}

TPG.setDisabledFormSegment = function (rootElement, disable) {
	var elements = [];
	var i, j;
	if (typeof rootElement == "string")
		rootElement = $(rootElement);
	if (!rootElement) return;
	disable ? TPG.addClassName(rootElement, "disabled") : TPG.removeClassName(rootElement, "disabled");
	elements = elements.concat(rootElement.getElementsByTagName('INPUT'));
	elements = elements.concat(rootElement.getElementsByTagName('TEXTAREA'));
	elements = elements.concat(rootElement.getElementsByTagName('SELECT'));
	for (i = 0; i < elements.length; i++) {
		for (j in elements[i])
			elements[i][j].disabled = disable;
	}
	
}

/***** Math Handling *****/

TPG.getRnd = function(min, max) {
	return min + Math.ceil(Math.random() * max);
}


/***** Document Handling *****/

/*
Adds or replaces a part of the title. This considers the title to be a string split up into tokens based on a delimeter (which defaults to ' - ').
'levels' specifies how many tokens (starting from the right) should be replaced / removed. The 'newSuffix' parameter can be a single value or an array.
*/
TPG.changeTitleSuffix = function(newSuffix, levels, delimeter) {
	levels = levels > 0 ? levels : 0;
	delimeter = !delimeter ? ' - ' : delimeter;
	if (!(newSuffix instanceof Array )) newSuffix = [newSuffix];
	var title = document.title;
	var tokens = title.split(delimeter);
	var indicesToKeep = levels <= tokens.length ? tokens.length - levels: tokens.length;
	document.title = (tokens.splice(0, indicesToKeep).concat(newSuffix)).join(delimeter);
	return document.title;
}

/***** DOM HANDLING *****/


TPG.getFirstChild = function(node) {
	var children = node.childNodes;
	for (var i = 0; i < children.length; i++) {
		if (children[i].nodeType == 1)
			return children[i]
	}
	return false;
}

/***** Cookie Handling *****/


/* Code for the following cookie handler functions was created by David Flanagan: http://www.davidflanagan.com/javascript5/display.php?n=19-2&f=19/Cookie.js
*/

/**
 * This is the Cookie() constructor function.
 *
 * This constructor looks for a cookie with the specified name for the
 * current document.  If one exists, it parses its value into a set of
 * name/value pairs and stores those values as properties of the newly created
 * object.
 *
 * To store new data in the cookie, simply set properties of the Cookie
 * object.  Avoid properties named "store" and "remove" since these are 
 * reserved as method names.
 * 
 * To save cookie data in the web browser's local store, call store().
 * To remove cookie data from the browser's store, call remove().
 *
 * The static method Cookie.enabled() returns true if cookies are
 * enabled and returns false otherwise.
 */
TPG.Cookie = function(name) {
    this.$name = name;  // Remember the name of this cookie

    // First, get a list of all cookies that pertain to this document
    // We do this by reading the magic Document.cookie property
    // If there are no cookies, we don't have anything to do 
    var allcookies = document.cookie;
    if (allcookies == "") return;

    // Break the string of all cookies into individual cookie strings
    // Then loop through the cookie strings, looking for our name
    var cookies = allcookies.split(';');
    var cookie = null;
    for(var i = 0; i < cookies.length; i++) {
        // Does this cookie string begin with the name we want?
        if (cookies[i].substring(0, name.length+1) == (name + "=")) {
            cookie = cookies[i];
            break;
        }
    }

    // If we didn't find a matching cookie, quit now
    if (cookie == null) return;

    // The cookie value is the part after the equals sign
    var cookieval = cookie.substring(name.length+1);

    // Now that we've extracted the value of the named cookie, we
    // must break that value down into individual state variable 
    // names and values. The name/value pairs are separated from each
    // other by ampersands, and the individual names and values are
    // separated from each other by colons. We use the split() method
    // to parse everything.
    var a = cookieval.split('&'); // Break it into an array of name/value pairs
    for(var i=0; i < a.length; i++)  // Break each pair into an array
        a[i] = a[i].split(':');

    // Now that we've parsed the cookie value, set all the names and values
    // as properties of this Cookie object. Note that we decode
    // the property value because the store() method encodes it
    for(var i = 0; i < a.length; i++) {
        this[a[i][0]] = decodeURIComponent(a[i][1]);
    }
}

/**
Quick function that fetches a cookie value without having to store a separate cookie object first.
**/
TPG.Cookie.getCookieParam = function(cookieName, paramName, defaultValue, set) {
	var cookie = new TPG.Cookie(cookieName);
	var tempValue;
	var partOfSet = false;
	if (cookie[paramName]) {
		switch (typeof defaultValue) {
			case "string":
				tempValue = cookie[paramName];
				if (set && !TPG.inArray(set, tempValue)) tempValue = undefined
				return tempValue != undefined ? tempValue : (defaultValue != undefined ? defaultValue : ""); 	
			break;
			case "number":
				tempValue = parseInt(cookie[paramName]);
				if (set && !TPG.inArray(set, tempValue)) tempValue = undefined
				return !isNaN(tempValue)? tempValue : (defaultValue != undefined ? defaultValue : 0); 	
			break;
			case "boolean":
				return cookie[paramName] == "true" ? true : (defaultValue != undefined ? defaultValue : false);
			break;
			default://most likely case: "undefined"
				return cookie[paramName];
		}
	}
	else 
		return defaultValue != undefined ? defaultValue : false; 
}



/**
 * This function is the store() method of the Cookie object.
 *
 * Arguments:
 *
 *   daysToLive: the lifetime of the cookie, in days. If you set this
 *     to zero, the cookie will be deleted.  If you set it to null, or 
 *     omit this argument, the cookie will be a session cookie and will
 *     not be retained when the browser exits.  This argument is used to
 *     set the max-age attribute of the cookie.
 *   path: the value of the path attribute of the cookie
 *   domain: the value of the domain attribute of the cookie
 *   secure: if true, the secure attribute of the cookie will be set
 */
TPG.Cookie.prototype.store = function(daysToLive, path, domain, secure) {
    // First, loop through the properties of the Cookie object and
    // put together the value of the cookie. Since cookies use the
    // equals sign and semicolons as separators, we'll use colons
    // and ampersands for the individual state variables we store 
    // within a single cookie value. Note that we encode the value
    // of each property in case it contains punctuation or other
    // illegal characters.
    var cookieval = "";
    for(var prop in this) {
        // Ignore properties with names that begin with '$' and also methods
        if ((prop.charAt(0) == '$') || ((typeof this[prop]) == 'function')) 
            continue;
        if (cookieval != "") cookieval += '&';
        cookieval += prop + ':' + encodeURIComponent(this[prop]);
    }

    // Now that we have the value of the cookie, put together the 
    // complete cookie string, which includes the name and the various
    // attributes specified when the Cookie object was created
    var cookie = this.$name + '=' + cookieval;
    if (daysToLive || daysToLive == 0) { 
        cookie += "; max-age=" + (daysToLive*24*60*60);
    }

    if (path) cookie += "; path=" + path;
    if (domain) cookie += "; domain=" + domain;
    if (secure) cookie += "; secure";

    // Now store the cookie by setting the magic Document.cookie property
    document.cookie = cookie;
}

/**
 * This function is the remove() method of the Cookie object; it deletes the
 * properties of the object and removes the cookie from the browser's 
 * local store.
 * 
 * The arguments to this function are all optional, but to remove a cookie
 * you must pass the same values you passed to store().
 */
TPG.Cookie.prototype.remove = function(path, domain, secure) {
    // Delete the properties of the cookie
    for(var prop in this) {
        if (prop.charAt(0) != '$' && typeof this[prop] != 'function') 
            delete this[prop];
    }

    // Then, store the cookie with a lifetime of 0
    this.store(0, path, domain, secure);
}

/**
 * This static method attempts to determine whether cookies are enabled.
 * It returns true if they appear to be enabled and false otherwise.
 * A return value of true does not guarantee that cookies actually persist.
 * Nonpersistent session cookies may still work even if this method 
 * returns false.
 */
TPG.Cookie.enabled = function() {
    // Use navigator.cookieEnabled if this browser defines it
    if (navigator.cookieEnabled != undefined) return navigator.cookieEnabled;

    // If we've already cached a value, use that value
    if (Cookie.enabled.cache != undefined) return Cookie.enabled.cache;

    // Otherwise, create a test cookie with a lifetime
    document.cookie = "testcookie=test; max-age=10000";  // Set cookie

    // Now see if that cookie was saved
    var cookies = document.cookie;
    if (cookies.indexOf("testcookie=test") == -1) {
        // The cookie was not saved
        return Cookie.enabled.cache = false;
    }
    else {
        // Cookie was saved, so we've got to delete it before returning
        document.cookie = "testcookie=test; max-age=0";  // Delete cookie
        return Cookie.enabled.cache = true;
    }
}