// used by makeClass

var Confex = new Function();
Confex.loaded = ['domcore'];


var global = this;


/* Parts from prototype.js */
Function.prototype.bind = function() {
	var __method = this, args = Array.from(arguments), object = args.shift();
	return function() {
		return __method.apply(object, args.concat(Array.from(arguments)));
	}
};

Array.from = function(iterable) {
	if (!iterable) return [];
	if (iterable.toArray) {
		return iterable.toArray();
	} else {
		var results = [];
		for (var i = 0; i < iterable.length; i++)
			results.push(iterable[i]);
		return results;
	}
}

/* End parts from prototype.js */





function gi(id) { return document.getElementById(id) }

function addEvent(el, evname, func, capture) {
	/* this code from Mishoo's DHTML Calendar 0.9.6. (LGPL Licensed, see http://www.fsf.org/licenses/lgpl.html) 
	 */
	if(arguments.length == 3) { capture = true; };
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, capture);
	} else {
		el["on" + evname] = func;
	}
}

function htree (nodename, attr, childs) { 
	var n = document.createElement(nodename);
	if(attr) { 
		for(var i in attr) { 
			n[i] = attr[i];
		}
	}
	if(childs) { 
		for(var i in childs) { 
			try { 
				n.appendChild(childs[i]);
			} catch(e) {
				//debugger;
				throw("DOM Exception: '" + e.message + "' while appending a " + childs[i] + " to a " + n);
			}
		}
	}
	return n;
}
function t(text) { return document.createTextNode(String(text)) }

function ascendGrep(matcher, node) { 
	
	while(node != null &&
			!matcher(node)) { 
		node = node.parentNode;
	}

	return node;
}

function domBreadthDescend (node, func) {
	func(node);
	for (var i = 0; i < node.childNodes.length; i++ ) { 
		if(domBreadthDescend(node.childNodes[i],func)) break;
	}
}

function domDepthDescend (node, func) {
	for (var i = 0; i < node.childNodes.length; i++) { 
		if(domBreadthDescend(node.childNodes[i],func)) break;
	}

	func(node);
}




/** makeClass
	
  	This function should be used to declare classes.  This handles all the dirty work
	of building the base class, inheritence, and what not.
  */
function makeClass(name,data,init,scope) {

	if(!scope) { scope = global; }
	if(name.match(/\./)) { 
		var parts = name.split(/\./);
		for(var i = 0; i < parts.length - 1; i++)  { 
			if(!scope[parts[i]]) { 
				scope[parts[i]] = {};
			}
			scope = scope[parts[i]];
		}
		name = parts[parts.length - 1];
	}

	var constructor;
	if(data.constructor && data.constructor.prototype != Object.prototype) {
		constructor = scope[name] = data.constructor;
		delete data.constructor;
	} else {
		constructor = scope[name] = new Function();
	}

	if(data.prototype) {
		for(var i in data.prototype)  {
			constructor.prototype[i] = data.prototype[i];
		}
		delete data.prototype;
	}
	if(data.isa) {
		var isa = data.isa;
		delete data.isa;

		for(var i in isa)  {
			for(var field in isa[i].prototype) { 
				constructor.prototype[field] = isa[i].prototype[field];
			}
		}
	}

	for(var i in data)
		constructor.prototype[i] = data[i];

	if(init) { init(constructor); }

	return constructor;
}


/**
	analagous to perl's bless.

	Instanciates a new object, and copies given data into the
	new object.
  
  */
function bless(data,obj) { 
	var self = new obj();

	for(var i in data) self[i] = data[i];	
	
	if(self.onblessed) { self.onblessed(data,obj); }

	return self;
}




function toJSON(arg) { 
	var i, outputBuf, value;
	switch (typeof arg) {
		case 'object':
			if (arg) {
				if (arg.constructor == Array) {
					outputBuf = '[';
					for (i = 0; i < arg.length; ++i) {
						value = toJSON(arg[i]);
						if (value != 'function' && typeof value !== 'undefined') {
							outputBuf += (outputBuf != '[' ? ',' : '') + value;
						} else {
							outputBuf += ',';
						}
					}
					return outputBuf + ']';
				} else if (typeof arg.toString != 'undefined') {
					outputBuf = '{';
					for (i in arg) {
						value = toJSON(arg[i]);
						if (value != 'function' && typeof value !== 'undefined') {
							outputBuf += (outputBuf != '{' ? ',' : '') +
								toJSON(i) + ':' + value;
						}
					}
					return outputBuf + '}';
				} else {
					return;
				}
			}
			return 'null';
		case 'unknown':
		case 'undefined':
			return 'undefined';
		case 'string':
			var s = arg.replace(/\\/g,'\\\\');
			s = s.replace(/"/g,'\\"');
			return '"' + s + '"';
		case 'function':
			return 'function';
		default:
			return String(arg);
	}
}




/* sprintf

Tries really hard to be like the unix function by the same name.  Note, snprintf is unnesscary due to js's memory management.

Conversion specifiers currently supportd: 
 s - string ( no flags, just field width ) 
 i,d - integer/decimal (same thing) (padding and field width flags)

flags supported: 
 '0' - zero pad
 ' ' - space pad



   
*/
function sprintf() {
	var format = arguments[0];
	
	// stores position of next argument to consume.	
	var argCounter = 1;
	
	/*
	 this works by regex'ing up to the next format string,
	 contacenating the previous part to the buffer, performing the
	 conversion described in the format string, and placing the rest back into
	 the 'formatRest' string to repeat again.
	 once the formatRest string contains no match, no more format strings exist,
	 and the contents of formatRest are appeneded to the rest of the output buffer.
	*/
	
	// the rest of the format string.
	var formatRest = new String(format);
	var buffer = '';
	while(formatRest.length > 0) { 	
		if(res = formatRest.match(/^([^%]*?)%(.*?)([idpfFs%])(.*?)$/)) { 
			buffer+=res[1]; // append the before-string to the end of the buffer.
			
			// this is what we really care about to execute the format string.
			var flagStr = res[2];
			var conversion = res[3];
			formatRest = res[4];

			var flags = {width: '',radix: '',pad: ' ',leftAlign: true,alwaysSign: false};
			var arg = argCounter++;
			for(var i = 0; i < flagStr.length; i++) { 
				var singleChar = flagStr.charAt(i);
				switch(singleChar) { 
					case '0':
						flags.pad = '0'; break;
					case ' ':
						flags.pad = ' '; break;
					case '+': 
						flags.alwaysSign = true; break;
					case '-': 
						flags.leftAlign = false; break;

					case "'":
						flags.groupThousandths = true; break;

					case '.': // enter read-radix mode.
						flags.readRadix = true; break;

					case '$': //we really just read a positional flag.
						// convert width to the arg position, and reset the width.
						arg = Number(flags.width);
						flags.width = '';
						
						argCounter--; // unadavnace the pointer, this means we don't consume the argument.

						break;
						
					case '#':
					case 'I':
						//legal according to GNU, but ignored.
						break;
					default:
						if(Number(singleChar) > 0) { 
							//these are intentionally string contatenations.
							if(flags.readRadix) { 
								flags.radix+=singleChar;
							}  else { 
								flags.width+=singleChar;
							}
						}
				}
			}
			if(flags.width != '') { flags.width = Number(flags.width); } else { flags.width = false; }
			if(flags.radix != '') { flags.radix = Number(flags.radix); } else { flags.radix = false; }
			

			var asString;
			//NOTE: Any case you add here has to be added to the regex above.
			switch(conversion) {
				case '%': 
					asString = '%'; break;
				case 'p':
					var value = Number(arguments[arg]);
					if(value != 1) { 
						asString = 's';
					} else { 
						asString = '';
					}
					break;
				case 's':
					asString = String(arguments[arg]);
					if(flags.radix) { 
						asString = asString.substr(0,flags.radix);
					}
					if(flags.width && asString.length < flags.width) {
						while(asString.length < flags.width) { 
							if(flags.leftAlign) { 
								asString = flags.pad + asString
							} else { 
								asString = asString + flags.pad;
							}
						}
					}
					break;
				case 'f':
				case 'F':
					asString = String(arguments[arg]);
					break;
				case 'i':
				case 'd':
					var value = Number(arguments[arg]);
					var abs = Math.floor(Math.abs(value));
					var asString = String(abs);
					var leftIsPad = false;
					if(flags.width && asString.length < flags.width) {
						while(asString.length < flags.width) { 
							if(flags.leftAlign) { 
								asString = flags.pad + asString
							} else { 
								asString = asString + flags.pad;
							}
						}
					}
					if(value < 0 || flags.alwaysSign) { 
						var sign = (value < 0 ? '-' : '+' );
						if(asString.charAt(0) == '0') { 
							asString = sign + asString.substr(1);
						}  else if (asString.charAt(0) == ' ') { 
							var spaceIndex = -1;
							while(asString.charAt(++spaceIndex) == ' ');

							asString = asString.substr(0,spaceIndex - 1) 
								+ sign 
								+ asString.substr(spaceIndex);
						} else { 
							asString = sign + asString;
						}
					}
					break;
				default: 
					asString = String(arguments[arg]);
			};

			buffer+=asString;

		} else { 
			// happens on last match.
			buffer+= formatRest;
			formatRest = '';
		}
	}
	return buffer;
	
}




/**
  Analagous to perl's map, but note argument are reversed.
  
  */
function map(list, func) { 
	var newlist = [];
	for(var i = 0; i < list.length; i++) { 
		newlist.push(func(list[i]));
	}
	return newlist;
}

/**
	grepobj,grep	
	  
	Similar to perl's grep, but this version understands objects.

	passed 2 arguments, list, func
	
	if you use grep(): 
	you will always get a list back.  List has to be a JS array, or another object that supports
	the .length property, and bracket-indexing.

	if you use grepobj():

	if list is an object then a new object will be returned where func evaluates to true.

	if list is an array, then a new array will be returned where func evaluats to true.

	func is a function object that takes 2 arguments a value, and a key/index into the object/array.

	

  */
function grepobj(list, func) { 
	var newlist = [];
	if ( typeof list == 'object' ) {
		newlist = {};
	}  
	
	for(var i in list) {
		if(func(list[i],i)) { 
			if (newlist.push){
				newlist.push(list[i]);
			} else {
				newlist[i] = list[i];
			}
		}
	}
	return newlist;
}

function grep(list, func) { 
	var newlist = [];
	for(var i = 0; i < list.length; i++) {
		if(func(list[i],i)) { 
			newlist.push(list[i]);
		}
	}
	return newlist;
}


/* Timeout lib.  Provides a cleaner interface over setTImeout */
if(!window._timeouts) { window._timeouts = {}; }

window._handleTimeout = function (index) { 
    if(window._timeouts[index]) {
        window._timeouts[index].obj.fireEvent(index);
    } else {
        // event is lost in time!
        gridAlert("lost track of event index " + index);
    }
}

makeClass('Timeout',{
    constructor: function(timems, code) {
            this.key = 0;
            this.code = code;
            this.timems = timems;
    },
    setTimeout: function() {

            // search for a random key slot to assign this timeout into.
            while(window._timeouts[this.key])
                this.key = Math.round(Math.random() * 1000);

            window._timeouts[this.key] = { obj: this }

            //save the timeout id.
            this.timeoutSet = window.setTimeout("window._handleTimeout(" + this.key + ")", this.timems);
    },
    fireEvent:  function(index) {
            this.clearTimeout();
            // eventually i may be able to figure out to change the 'scope' of this call
            // so passing 'this' isn't redunant '
            return this.code(this);
    },
    clearTimeout:  function() { 
        if(this.timeoutSet) { 
            window.clearTimeout(this.timeoutSet);
            delete(window._timeouts[this.key]);
            this.timeoutSet = false;
        }
    },
    reset: function() { this.clearTimeout(); this.setTimeout(); }
});

/**
  yieldThenRun takes a function as an argument.  This yields time to the
  browser's UI thread to updates can be drawn to the screen, then runs 
  the given function.  This is the only way to let the browser redraw
  after you make an update to the DOM (if you'd like to keep running javascript)
  */
function yieldThenRun(func) { 
	(new Timeout(1,func,true)).setTimeout();
}

function yieldForLoop(indexStart, indexEnd, bodyCode,doneCode) { 

	(function(body, start, end, done) { 
		var index = start;
		var nextItem;
		var skip = Math.ceil((end - start) / 100);
		nextItem = function() { 
			(new Timeout(0,
			     function() { 
					var skipstart = skip;
					while( (index < end) && (skipstart > 0)) { 
						skipstart--;
						body(index++);
					}
					if(index < end) {
						// This appears recursive, but all it really does is
						// install a timer for the window thread to pickup on and invoke the code again.
						nextItem();
					} else { 
						done();
					}
				}
			     ,true)).setTimeout();
		};
		nextItem();
	})(bodyCode,indexStart,indexEnd,doneCode);
}


/** 
  applyBehavior - appplies behaviors in global var 'behaviors' to the given
  node and all children of that node. behaviors should be an object, w/ properties that name
  either a class name, or a node id.  example:

   { 
      popuplink: function(n) { }
      '#record123': function(n) { }
   }
   
   You can install events (using addEvent) or transform the nodes if you'd like.
  */


function applyBehaviors(node) { 
	domDepthDescend(node,function(n) { 
		if(n.className != '') { 
			var className = String(n.className);
			for(var i in behaviors) {
				if(className.match(i)) { 
					behaviors[i](n);
				}
			}

		} 
		// if the node has an ID, then 
		// attach that behavior
		if(n.id && behaviors['#' + n.id]) { 
			behaviors['#' + n.id](n);
		}
	});

}


function scrollOffset(theWindow) { 
	if(!theWindow) theWindow = window;

	var x,y;
	if (theWindow.self.pageYOffset) { // all except Explorer
	
		x = theWindow.self.pageXOffset;
		y = theWindow.self.pageYOffset;
	} else if (theWindow.document.documentElement && theWindow.document.documentElement.scrollTop) {
		// Explorer 6 Strict
	
		x = theWindow.document.documentElement.scrollLeft;
		y = theWindow.document.documentElement.scrollTop;
	} else if (theWindow.document.body) { // all other Explorers
	
		x = theWindow.document.body.scrollLeft;
		y = theWindow.document.body.scrollTop;
	}

	return {x: x, y: y}
}
