/////////////////////////////////////////////////////////////////////////////
//
// Gladiator Components 
//
// http://www.gladiatorweb.com
//
// (c) 2006 by Edward H. Trager .  All Rights Reserved
//
// NAME: gladiatorCore.js
//
// DESCRIPTION: Implements core functions, methods, and objects for the
//              Gladiator components toolkit.
//
// AUTHOR: Edward H. Trager
//
// LAST UPDATE: 2006.03.08
//
// NOTE: SEE "LICENSE" FILE FOR LICENSING TERMS
//
//////////////////////////////////////////////////////////////////////////////

//
// 
//
var GLADIATOR_CORE_INCLUDED=true;

//////////////////////////////////////////////////////
//
// Simple Utilitarian convenience functions
//
// Most of these were inspired by Douglas
// Crockford's "Remedial Javascript" page on
// http://www.crockford.com ...
// See: http://www.crockford.com/javascript/remedial.html
//
/////////////////////////////////////////////////////

//
// isArray()
//
function isArray(a) {
	
	return isObject(a) && a.constructor == Array;
	
}

//
// isBoolean()
//
function isBoolean(a) {
	return typeof a == 'boolean';
}

//
// isEmpty()
//
function isEmpty(o) {
	var i, v;
	if (isObject(o)) {
		for (i in o) {
			v = o[i];
			if (isUndefined(v) && isFunction(v)) {
				return false;
			}
		}
	}
	return true;
}

//
// isFunction()
//
function isFunction(a){
	return (typeof a == 'function');
}

//
// isNull()
//
function isNull(a) {
	return typeof a == 'object' && !a;
}

//
// isNumber()
//
function isNumber(a) {
	return typeof a == 'number' && isFinite(a);
}

//
// isObject()
//
function isObject(a) {
	return (a && typeof a == 'object') || isFunction(a);
}

//
// isString()
//
function isString(a) {
    return typeof a == 'string';
}

//
// isUndefined()
//
function isUndefined(a){
	return typeof a == 'undefined';
}

//
//
//
function isDefined(a){
	return !isUndefined(a);
}

//
// This "method" method from Douglas Crockford adds
// a method to a class.  Returning the "this" parameter
// allows one to "cascade" definitions:
//
Function.prototype.addMethod = function (name, func) {
    this.prototype[name] = func;
    return this;
};

//
// Add "trim" method to String class:
//
String.addMethod( 'trim',function () { return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"); } ); 

//
// Add "max" method to Array class:
//
Array.addMethod('max', function () { return Math.max.apply({},this); } );

//
// Add "min" method to Array class:
//
Array.addMethod('min',function () { return Math.min.apply({},this); } );

Array.addMethod('indexOf',function(v,b,s){ for(var i=+b || 0,l=this.length;i<l;i++){ if(this[i]==v || s && this[i]==v) return i; } return -1; });

//
// findPosition(obj): Returns an array containing the left and top positions
//                    of an element
// 
// -> Thanks to http://www.quirksmode.org/js/findpos.html for putting us on
//    the right track!
//
function findPosition(obj){
	
	var _curLeft=0;
	var _curTop =0;
	if(obj.offsetParent){
		_curLeft=obj.offsetLeft;
		_curTop =obj.offsetTop;
		while(obj = obj.offsetparent){
			_curLeft += obj.offsetLeft;
			_curTop  += obj.offsetTop;
		}
	}
	return [ _curLeft , _curTop ];
}


/////////////////////////////////////////////////////////////////////////////
//
// getElementStyle() : wrapper around getComputedStyle().getPropertyValue()
//                     which is just too long to write repeatedly ...
//
/////////////////////////////////////////////////////////////////////////////
//
// getElementStyle():
//
function getElementStyle(elem,property){
	
	return document.defaultView.getComputedStyle(elem,null).getPropertyValue(property);
	
}

//
// getComputedWidthAndHeight
//
// get actual height and width using DOM methods. Because we want to
// use DOM methods, we are not using clientWidth or clientHeight.
//
function getComputedWidthAndHeight(elem){
	
	var style  = document.defaultView.getComputedStyle(elem,null);
	
	//
	// Note: Instead of taking (border-left + border-right)/2
	//       we just take border-left.  This assumes border-left
	//       and border-right are equal, a reasonable assumption
	//       for table cells at least.
	//
	//       Similar story for height calculation.
	//
	//       Why minus 2 ? Because it seems to give the right
	//       visual answer. 
	//
	
	var _width = parseInt(style.getPropertyValue("width"));
	             
	var _height= parseInt(style.getPropertyValue("height"));
	
	if(BROWSERID.code != "O"){             
		_width += parseInt(style.getPropertyValue("padding-left"))  +
			parseInt(style.getPropertyValue("padding-right")) + 
			parseInt(style.getPropertyValue("border-left-width")); 
		_height += parseInt(style.getPropertyValue("padding-top"))    +
			parseInt(style.getPropertyValue("padding-bottom")) +
			parseInt(style.getPropertyValue("border-top-width"));
	}else _width -= parseInt(style.getPropertyValue("border-left-width"))/2 ; 
	return [ _width , _height ];
	
}

///////////////////////////
//
// Miscellaneous Functions:
//
///////////////////////////

//
// padWithZeros()
//
// Arguments: number: value to display; length: total length to display;
// Return: number padded out with zeros;
//
function padWithZeros(number,length) {
	var str = "" + number;
	while( str.length < length ) str = '0' + str;
	return str;
}

//
// appendToParentNode():
//
// When the parent node "parentNode" exists, this function 
// appends the DOM node "myNode" to the parent node. Otherwise,
// the DOM node is appended directly to the body of the document.
//
function appendToParentNode( myNode,parentNode){
	
	if(parentNode){
		parentNode.appendChild( myNode );
	}else{
		// The W3C standards way to get the document body:
		var bodyRef = document.getElementsByTagName("body").item(0);
		bodyRef.appendChild( myNode );
	}
	
}


/////////////////////////////////////////
//
// ANIMATIONS AND TRANSITIONS FUNCTIONS
//
/////////////////////////////////////////

///////////////////
//
// function fadeIn
//
///////////////////
function fadeIn(objectId,opacity) {
	
	var object = document.getElementById(objectId);
	
	if(document.body.style.opacity==null){
		//
		// Handle the case of browsers that have not 
		// implemented CSS3 opacity property:
		//
		object.style.display = "block";
		
	}else{
		if(object.style.display == "none"){
			object.style.display="block";
		}
		//
		// Get here if opacity has been implemented:
		//
		if (opacity < 1.0 ) {
			
			opacity += 0.1;
			
			/* CSS3 compatible */
			object.style.opacity = opacity;
				
			window.setTimeout("fadeIn('"+objectId+"',"+opacity+")", 30);
		}
	}
}

////////////////////
//
// function fadeOut
//
////////////////////
function fadeOut(objectId,opacity) {
	
	var object = document.getElementById(objectId);
	
	//if(document.body.style.opacity==null){
	//	//
	//	// Handle the case of browsers that have not 
	//	// implemented CSS3 opacity property:
	//	//
	//	object.style.display = "none";
	//	
	//}else{
	if(true){
		//
		// Get here if opacity has been implemented:
		//
		if (opacity > 0.1 ) {
			
			opacity -= 0.1;
			if(opacity<0.01) opacity=0.0;
			
			/* CSS3 compatible */
			object.style.opacity = opacity;
				
			window.setTimeout("fadeOut('"+objectId+"',"+opacity+")", 30);
		}else{
			//
			// Really close it:
			//
			object.style.opacity = 0.0;
			object.style.display="none";
		}
	}
}

/////////////////////////////
//
// MISCELLANEOUS DOM NODE 
// MANIPULATION FUNCTIONS
//
/////////////////////////////

//
// removeChildNodes()
//
function removeChildNodes( parent ){
	//
	// remove all children from parent:
	//
	while (parent.firstChild) {
		parent.removeChild(parent.firstChild);
	}
}

/////////////////////////////////////////////////////////////////////
//
// CORE CLASSES -- Especially single-instance global object classes
//
/////////////////////////////////////////////////////////////////////


///////////////
//
// gEvent
//
//////////////

//
// gEvent(): Adds "xPosition" and "yPosition" as normalized x/y members
//           of a W3C event object.
//
function gEvent(e){
	
	//
	// W3C DOM event :
	// ==> We add attributes to normalize mouse positioning:
	//
	e.xPosition = e.clientX;
	e.yPosition = e.clientY;
	//
	// The code is written this way
	// so it will work in all DOCTYPES:
	// XML, XHTML, SVG, and mixed XML/XHTML/SVG:
	//
	if(document.body && document.body.scrollLeft){
		e.xPosition+=document.body.scrollLeft;
		e.yPosition+=document.body.scrollTop;
	}
	
	//
	// Fix for KHTML (Konqueror and Safari) bug:
	// 
	// Since reassigning e.target doesn't seem to work,
	// we create a new property, e.targetElement and use
	// that instead:
	//
	if(e.target){
		// Clicking on the text portion in KHTML 
		// returns that node instead of the parent:
		e.targetElement=(e.target.nodeType==3)?e.target.parentNode:e.target;
	}else{
		// IE:
		e.targetElement=e.srcElement;
	}
	
	return e;
}

//////////////////////////////
//
// Global Objects:
//
//////////////////////////////

/////////////////////////////////////////////////////////////
//
// Global NS (NAMESPACES) convenience object:
//
/////////////////////////////////////////////////////////////
var NS   = new Object;
NS.xhtml = "http://www.w3.org/1999/xhtml";
NS.svg   = "http://www.w3.org/2000/svg";

/////////////////////////////////////////////////////////////
//
// Global KB (KEYBOARD) convenience object:
//
/////////////////////////////////////////////////////////////
var KB   = new Object;
KB.backspace = 8;
KB.tab = 9;
KB.space = 32;
KB.del = 46;
KB.fstart = 112;
KB.fend = 123;
KB.enter = 13;
KB.down = 40;
KB.up = 38;
KB.left=37;
KB.right=39;
KB.escape=27;

/////////////////////////////////////////////////////////////
//
// Global POSITION convenience object:
//
/////////////////////////////////////////////////////////////
var POSITION = new Object;
//
// Treat these as an ENUM of symbolic constants:
// The negative enumerations are negative for a reason --
// don't change unless you really know what you are doing!
//
POSITION.center =  0;
POSITION.left   = -1;
POSITION.top    = -2;
POSITION.right  = -3;
POSITION.bottom = -4;

/////////////////////////////////////////////////////////////
//
// gDimensions class : Normalized browser window dimensions
//                     calculated on load and on resize 
//                     events.
//
/////////////////////////////////////////////////////////////
function gDimensions(){
	
	this.width  = 0;
	this.height = 0;
	this.minimumPadding=12;
	this.padding=24;
	
	this.horizontalCenter=0;
	this.verticalCenter=0;
	
	//
	// By convention, set a "self" variable to use in place of "this" ...
	//
	// From www.crockford.com:
	// =======================
	//
	// This is used to make the object available to the private methods. 
	// This is a workaround for an error in the ECMAScript Language 
	// Specification which causes "this" to be set incorrectly for inner 
	// functions.
	//
	var _mySelf=this;
	
	//
	// Private method setDimensions
	// ==> Call on construction and on window
	// resize events:
	//
	// ==> As a result, we will now have "width" and "height"
	//     as publically-accessible members which are 
	//     dynamically updated:
	//
	this.setDimensions = function(){

		_mySelf.width  = window.innerWidth;
		_mySelf.height = window.innerHeight;
		_mySelf.horizontalCenter = parseInt(0.5*_mySelf.width );
		_mySelf.verticalCenter   = parseInt(0.5*_mySelf.height);
		//if(self.innerHeight){ // All except Explorer ...
		//	_mySelf.width  = self.innerWidth;
		//	_mySelf.height = self.innerHeight;
		//}else if(document.documentElement && document.documentElement.clientHeight){ // Explorer strict mode ...
		//	_mySelf.width  = document.documentElement.clientWidth;
		//	_mySelf.height = document.documentElement.clientHeight;
		//}else if(document.body){ // Other Explorers ...
		//	_mySelf.width  = document.body.clientWidth;
		//	_mySelf.height = document.body.clientHeight;
		//}
	}
	
	//
	// init: This is required for some browsers such as Opera where 
	// Javascript files may only be loaded when methods from those files
	// are called explicitly .
	//
	this.init = function(){
		
		if(!(_mySelf.width && _mySelf.height))	
			_mySelf.setDimensions();
	
	}

	//
	// Reset dimensions whenever window is resized:
	//
	//if(window.addEventListener){
	window.addEventListener("load"  ,_mySelf.setDimensions,false);
	window.addEventListener("resize",_mySelf.setDimensions,false);
	//}else if(window.attachEvent){
	//	window.attachEvent("onload",setDimensions  );
	//	window.attachEvent("onresize",setDimensions);
	//}
	
}

/////////////////////////////////////////////////////////////
//
// gBrowserDetect class : See http://www.quirksmode.org/js/detect.html
//                        if more complete detection is needed.
//                        
//                        Currently we just want to be able to
//                        differentiate between the latest versions of
//                        certain browsers like Opera and Firefox
//                        which are known to have subtle differences
//                        which are difficult to deal with using
//                        object detection alone:
//
/////////////////////////////////////////////////////////////
function gBrowserDetector(){
	
	this.browser = ".";
	this.code    = ".";
	var _self = this;
	
	//
	// browser Meta Data
	//
	var _database = new Array();
	_database[0] = new Object();
	_database[0].string    = navigator.vendor;
	_database[0].subString = "Apple";
	_database[0].identity  = "Safari";
	_database[0].code      = "S";
	
	_database[1] = new Object();
	_database[1].prop      = window.opera;
	_database[1].identity  = "Opera";
	_database[1].code      = "O";
	
	_database[2] = new Object();
	_database[2].string    = navigator.vendor;
	_database[2].subString = "KDE";
	_database[2].identity  = "Konqueror";
	_database[2].code      = "K";
	
	_database[3] = new Object();
	_database[3].string    = navigator.userAgent;
	_database[3].subString = "Firefox";
	_database[3].identity  = "Firefox";
	_database[3].code      = "F";
	
	_database[4] = new Object();
	_database[4].string    = navigator.userAgent;
	_database[4].subString = "Gecko";
	_database[4].identity  = "Mozilla";
	_database[4].code      = "M";
	
	_database[5] = new Object();
	_database[5].string    = navigator.userAgent;
	_database[5].subString = "MSIE";
	_database[5].identity  = "Explorer";
	_database[5].code      = "E";
	
	//
	// searchDatabase
	//
	function _searchDatabase() {
		
		for (var i=0;i<_database.length;i++){
			
			var dataString = _database[i].string;
			var dataProp   = _database[i].prop;
			
			if (dataString) {
				if (dataString.indexOf(_database[i].subString) != -1){
					_self.browser=_database[i].identity;
					_self.code   =_database[i].code;
					return;
				}
			}else if (dataProp){
				_self.browser=_database[i].identity;
				_self.code   =_database[i].code;
				return;
			}
		}
		
	}
	
	//
	// Call the search method:
	//
	_searchDatabase();
	
};

/////////////////////////
//
// CSS-Related Functions
//
/////////////////////////


//
// getStyleSheet:
//
function getStyleSheet( name ){
	
	for(var i=0;i<document.styleSheets.length;i++){
		if(document.styleSheets[i].href.indexOf(name) != -1){
			return document.styleSheets[i];
		}
	}
	// Couldn't find style sheet:
	return false;
}

//
// getCSSClassStyle
//
function getCSSClassStyle(styleSheet,cssClass){
	
	for(i=0;i<styleSheet.cssRules.length;i++){
		
		if(styleSheet.cssRules[i].selectorText==cssClass){
			
			return styleSheet.cssRules[i].style;
			
		}
	}
	//
	// Not found:
	//
	return false;
	
}

//
// getCSSClassRuleValue
//
// styleSheet: a StyleSheet object (such as returned by getStyleSheet() above).
//
// CSSClass: For a CSSClass, be sure to include the leading dot, i.e., ".gMenuItem".
//           This could also be a built-in HTML or SVG element class, like "p" or "circle".
//
// rule: The CSS rule that you want to find out about, i.e., "border", "height", "width", etc.
//
// Return Value: a string value such as "1px solid red", "1.5cm", or "23px"
//
function getCSSClassRuleValue(styleSheet,cssClass,rule){
	
	for(var i=0;i<styleSheet.cssRules.length;i++){
		
		if(styleSheet.cssRules[i].selectorText==cssClass){
			
			var cssText = styleSheet.cssRules[i].cssText;
			if(!cssText) cssText = styleSheet.cssRules[i].style.cssText;
			
			// Prefix the rule with a space and then
			// also add the colon to the rule string
			// so that we get exact matching, i.e.
			// we only match on "padding" and not
			// on something like "padding-top":
			rule = " "+rule;
			rule += ":";
			
			//
			// Find the rule that we want:
			//
			var idxStt = cssText.indexOf(rule);
			
			if(idxStt==-1) return false;
			
			// Skip past the rule key:
			idxStt+=rule.length;
			//
			// Find the ";" which terminates this rule:
			//
			var idxEnd = cssText.substr(idxStt).indexOf(";");
			// Trim the value:
			return cssText.substr(idxStt,idxEnd).trim();
		}
	}
	// Not found:
	return false;
	
}

//////////////////////////////
//
// Global Objects:
//
//////////////////////////////

//
// Global model-normalized window dimensions object:
//
var DIMENSIONS = new gDimensions();

//
// Global NS (NAMESPACES) convenience object:
//
var NS   = new Object;
NS.xhtml = "http://www.w3.org/1999/xhtml";
NS.svg   = "http://www.w3.org/2000/svg";

//
// Global BROWSERID object:
//
var BROWSERID = new gBrowserDetector();

