/*
	This is IBDOM, Copyright Internet Brands, Inc.

	Documentation, Download and Open-Source Licensing available at:
	http://ibdom.sourceforge.net/
	
	Developer blog located at:
	http://ibbydev.blogspot.com/
	
	We *are* hiring skilled developers on many platforms and languages
	Check "careers" at http://www.internetbrands.com/
*/

IBDOM = {
	Config: {
		debug: true
		
		/* TODO: LOOP EXECUTOR, CLASS DIRECTIVES */
	}//Config
	,
	NodeTypes: {
		ATTRIBUTE_NODE: 2,
		TEXT_NODE: 3,
		CDATA_NODE: 4
	}//Node Types
	,
	DataTypes: {
		Handlers: {
			defaultHandler: function() {
				return arguments[0];
			}//defaultHandler			
		}//Handlers
		,
		Defs: {
			NUMBER: {
				id: 2,
				handler: function() {
					return IBDOM.Utils.getFormattedNumber(arguments[0]);
				}
			}//NUMBER
			,
			NUMBER_ROUNDED_NO_ZEROES: {
				id: 3,
				handler: function() {
					return IBDOM.Utils.getFormattedNumber(arguments[0],false,true);
				}
			}//NUMBER_ROUNDED_NO_ZEROES
		}//Data Type Definitions
		,
		getType: function() {
			s = arguments[0];
			if (s) {
				return this.Defs[s.toUpperCase()];
			} else {
				return {
					id: -1,
					handler: function() {
						return IBDOM.DataTypes.Handlers.defaultHandler.apply(this,arguments);
					}
				}
			}
		}
	}//DataTypes
	,
	Utils: {
		scape: function() {
			return (
				window.encodeURIComponent
				?
				window.encodeURIComponent(arguments[0])
				:
				escape(arguments[0])
			);
		}
		,
		getElement: function() {
			return IBDOM.IBElement.getAugmentedElement(document.getElementById(arguments[0]));
		}//getElement
		,
		isCollection: function() {
			return (
				(l = arguments[0].length)
				&&
				(!isNaN(l))
			);
		}
		,
		getFormattedNumber: function() {
			/*
				todo: replace doTrailingZeroes boolean with
				decimal points specification
			*/
			p = arguments[0];
			doTrailingZeroes = arguments[1];
			useMathDotRound = arguments[2];

			if (useMathDotRound) {
				p = Math.round(p);
			}
			
			if (doTrailingZeroes) p = p + 0.001;
			p = p + "";
			decIndex=p.indexOf(".");
			
			if (decIndex != -1)
				intpart = p.substring(0,decIndex);
			else
				intpart = p;
			
			if (intpart.length > 3) {
				formattedIntPart = new IBDOM.Utils.StringBuffer();
				ccount = intpart.length - 1;
				poscount = 1;
				while((c=intpart.charAt(ccount))) {
					formattedIntPart.append(c);
					if	( 
							(ccount != 0)
							&&
							((poscount % 3) == 0)
						) {
						formattedIntPart.append(",");
					}
					ccount--;
					poscount++;
				}//loop thru int chars
				formattedIntPart.__strings__.reverse();
				formattedIntPart = formattedIntPart.toString();
			} else {
				formattedIntPart = intpart;
			}
			
			if (decIndex != -1) {
				decpart = p.substring(decIndex + 1,decIndex + 3);
				return (formattedIntPart + "." + decpart);
			} else {
				return formattedIntPart;
			}
			
		}//getFormattedNumber
		,
		getString: function() {
			arr = arguments[0];
			if (arr) {
				return (new IBDOM.Utils.StringBuffer(arr)).toString();
			} else {
				return "";
			}
		}//getString
		,
		StringBuffer: function(arr) {
			if (arr)
				this.__strings__ = arr;
			else
				this.__strings__ = new Array;
			
			this.append = function() {
				str = arguments[0];
				this.__strings__.push(str);
			}
			
			this.toString = function() {
				separator = arguments[0];
				if (this.__strings__ && this.__strings__.join ) {
					if (!separator) separator = "";
					return this.__strings__.join(separator);
				} else {
					return "";
				}
			}
		}//StringBuffer Class
		,
		getDataKey: function() {
			dataString = arguments[0];
			if (dataString && (typeof (dataString) == "string")) {
				dataString = dataString.trim();
				dataValue = dataString.split("data:")[1];
				if (dataValue) {
					dv = dataValue.split(":type:");
					return {
						propName: dv[0],
						dataType: IBDOM.DataTypes.getType(dv[1])
					}
				} else return null;
			} else return null;
		}//getDataKey
		,
		setConditionalClassDirective: function() {
			nodeRef = arguments[0];
			classDirectiveString = nodeRef.getFirstPartialClassValue("setclass");
			if (classDirectiveString) {
				nodeRef.unsetClassValue(classDirectiveString);
				nodeRef.conditionalClassDirective = {
					string: classDirectiveString
				};
			}
		}//setConditionalClassDirective
		,
		debug: function() {
			if (IBDOM.Config.debug) {
				if (oEl = IBDOM.Utils.getElement("debug")) {
					oEl.setTextData(arguments[0]);
				} else {
					alert(arguments[0])
				}
			}
		}//debug
		,
		getMapFromSplit: function() {
			str = arguments[0];
			if (map = this.StringSplitMapCache.get(str)) {
				return map;
			} else {
				separator = arguments[1];
				arr = str.split(separator);
				map = new Array();
				for (i=0;a=arr[i];i++) {
					map[a] = true;
					map.separator = separator;
				}
				return this.StringSplitMapCache.add(str,map);
			}
		}//getMapFromSplit
		,
		StringSplitMapCache: {
		/*
			WARNING/ TODO:
			the add method below should be modified to put some bounds
			on the size of collection ... to avert big ol' memory leaks.
		*/
			collection: new Array()
			,
			inspect: function() {
				b = new IBDOM.Utils.StringBuffer();
				for (p in this.collection) {
					if (typeof(this.collection[p]) != "function") {
						b.append(this.collection[p].toString());
						b.append("\n");
					}
				}
				return b.toString();
			}
			,
			add: function() {
				l = arguments[0];
				o = arguments[1];
				map = this.collection[l] = o;
				map.toString = function() {
					filter = arguments[0];
					buff = new IBDOM.Utils.StringBuffer();
					for (k in this) {
						if (typeof(this[k]) == "boolean") {
							if	(
									!filter
									||
									(filter && (k != filter))
								) {
								buff.append(k);
							}
						}
					}//loop thru fields in map
					return buff.toString(this.separator);
				}//toString
				
				map.toStringFilter = function() {
					return this.toString(arguments[0]);
				}
				return map;
			}
			,
			get: function() {
				l = arguments[0];
				return this.collection[l];
			}
		}//StringSplitMapCache
	}//Utils Functions
	,
	Templates: {
		Cache: {
			collection: new Array()
			,
			findOrStore: function() {
				nodeRef = arguments[0];
				templateType = arguments[1];
				key = IBDOM.Utils.getString([templateType,"_",(nodeRef.parentNode ? nodeRef.parentNode.nodeName: "NOPARENT"),"/",nodeRef.nodeName,"_",nodeRef.className,"_",nodeRef.id]);
				identifier = nodeRef.className;
				tpl = this.collection[key];
				if (!tpl) {
					tpl = nodeRef.gFEBCN(templateType);
					if (tpl) {
						tpl = tpl.cloneNodeAugmented(true);
					} else {
						useTemplateDirective = nodeRef.getFirstPartialClassValue(templateType);
						if (useTemplateDirective) {
							tplClass = useTemplateDirective.split("|")[1];
							if (tplClass) {
								tpl = IBDOM.Utils.getElement("templates").gFEBCN(tplClass);
								if (tpl) tpl = tpl.cloneNodeAugmented(true);
							}//tplClass was found in directive, look for it in $("templates")
						}//tpl directive for templateType was found
					}//if template isn't contained in reference element, look in $("templates")
					if (tpl) {
						this.collection[key] = tpl;
					}
				}//if template not already in cache, find it, then cache it.
				if (tpl) {
					return tpl.cloneNodeAugmented(true);
				} else {
					return null;
				}
			}//findOrStore
			,
			inspect: function() {
				buff = new IBDOM.Utils.StringBuffer();
				for (p in this.collection) {
					buff.append(p);
					buff.append(": ");
					buff.append(this.collection[p]);
					buff.append("\n");
				}
				return buff.toString();
			}
		}//cache
	}//ITBTemplate
	,
	IBElement:  {
		/* methods collection inspired from Element.methods at http://www.prototypejs.org/ */
		/* todo: would like to find a reliable way to augment a node's prototype instead of augmenting each instance */
		/*
			while i've managed to make a wrapper object inherit methods from a node's prototype, this wrapper object isn't 
			a node, so calling node-native methods causes weirdness.
		*/
		methods: {
			getFirstPartialClassValue: function() {
				v = arguments[0];
				if (v) {
					cMap = IBDOM.Utils.getMapFromSplit(this.className," ");
					matchClass = null;
					for (c in cMap) {
						if (c.indexOf(v) != -1) {
							if (!matchClass) {
								matchClass = c;
							}
						}
					}//loop thru classes in classmap
					return matchClass;
				} else {
					return null
				}
			}//getFirstPartialClassValue
			,
			hasClassValue: function() {
				theClass = arguments[0];
				if (theClass) {
					cMap = IBDOM.Utils.getMapFromSplit(this.className," ");
					return (theClass in cMap);
				} else {
					return false;
				}
			}//hasClassValue
			,
			setClassValue: function() {
				theClass = arguments[0];
				if (!this.hasClassValue(theClass)) {
					this.className = IBDOM.Utils.getString([this.className," ",theClass]);
				}//if class not already in map
			}//setClassValue
			,
			unsetClassValue: function() {
				theClass = arguments[0];
				if (theClass) {
					cMap = IBDOM.Utils.getMapFromSplit(this.className," ");
					if (theClass in cMap) {
						this.className = cMap.toStringFilter(theClass);
					}
				}//if class to UNSET was passed
			}//unsetClassValue
			,
			cloneNodeAugmented: function() {
				return IBDOM.IBElement.getAugmentedElement(this.cloneNode(arguments[0]));
			}//cloneNodeAugmented
			,
			populate: function() {
				if (IBDOM.Utils.isCollection(arguments[0])) {
					this.populateFromBeanCollection(arguments[0], arguments[1]);
				} else {
					this.populateFromDataBean(arguments[0]);
				}
			}//populate-dispatcher method
			,
			populateFromBeanCollection: function() {
				beanCollection = arguments[0];
				templateProcessor = arguments[1];
				
				beanTemplate = IBDOM.Templates.Cache.findOrStore(this,"template:repeat");
				emptyCollectionTemplate = IBDOM.Templates.Cache.findOrStore(this,"template:empty_collection");
				
				this.removeAllChildren();//after capturing the templates above
								
				if (beanCollection && beanTemplate) {
					if (beanCollection.length > 0) {
						for (bc=0;(bean=beanCollection[bc]) && (tplClone = beanTemplate.cloneNodeAugmented(true));bc++) {
							if (templateProcessor) {
								tplClone.setDataProcessor(templateProcessor);
							}
							tplClone.populateFromDataBean(bean);
							this.appendChild(tplClone);
						}
					} else if (emptyCollectionTemplate) {
						this.appendChild(emptyCollectionTemplate);
					}
				} else {
					if (emptyCollectionTemplate) {
						this.appendChild(emptyCollectionTemplate);
					}
					IBDOM.Utils.debug("empty bean collection passed for: " + this.localName);
				}
			}//populateFromBeanCollection
			,
			setDataProcessor: function() {
				this.dataProcessor = arguments[0];
			}//setDataProcessor
			,
			populateFromDataBean: function() {
				dataBean = arguments[0];
				if (dataBean) {
					dataBean.test = function() {
						return eval(arguments[0]);
					}
					;
					allKids = this.gEBTN("*");
					if (allKids.length == 0) allKids = [this];
					doRoot = true;
					for(ec=-1; (el = (doRoot ? this : IBDOM.IBElement.getAugmentedElement(allKids[ec]))); ec++) {
						if (doRoot) doRoot = false;
						if (!el.dataKey) {
							if (dk = IBDOM.Utils.getDataKey(el.getTextData())) {
								el.dataKey = dk;
							}
						}

						if ((dataKey = el.dataKey) && (strData = dataBean[dataKey.propName])) {
							el.setTextData(dataKey.dataType.handler(strData));

							if (!el.conditionalClassDirective) {
								IBDOM.Utils.setConditionalClassDirective(el);
							}
							if (ccd = el.conditionalClassDirective) {
								ccds = ccd.string.split("{if}");
								ccd_class = ccds[0].split(":")[1];
								ccd_condition = ccds[1];
								if (dataBean.test(ccd_condition)) {
									nodeRef.setClassValue(ccd_class);
								}
							}
						}//if dataKey found for node, and bean has matching property with value

						attrs = el.attributes;
						if (!el.attrDataMap) {
							el.attrDataMap = new Array();
						}
						for(ac=0;(attrs && (att=attrs[ac]));ac++) {
							if (!el.attrDataMap[att.nodeName]) {
								if (el.getAttribute(att.nodeName) && (dataKey = IBDOM.Utils.getDataKey(el.getAttribute(att.nodeName)))) {
									el.attrDataMap[att.nodeName] = dataKey;
									if (att.nodeName == "name") {
										IBDOM.Utils.debug("There currently are some weird WIN/IE issues with setting the NAME attribute in the templating mechanism. See FAQ");
									}
								}
							}
							if (el.attrDataMap[att.nodeName] && (dataKey = el.attrDataMap[att.nodeName]) && (strData = dataBean[dataKey.propName])) {
								//att.nodeValue = dataKey.dataType.handler(strData);
								el.setAttribute(att.nodeName,dataKey.dataType.handler(strData));
							}
						}//loop thru attributes of the node
					}//loop thru all descendant nodes
					if (this.dataProcessor) {
						this.dataProcessor.apply(this,arguments);
					}
				}//if passed dataBean exists/not null
				else {
					IBDOM.Utils.debug("empty data bean passed for: " + this.localName);
				}
			}//populateFromDataBean
			,
			removeAllChildren: function() {
				while(fc = this.firstChild)
					this.removeChild(fc);
			}//removeAllChildren
			,
			gEBTN: function() {
				t = arguments[0];
				results = null;
				if (this.getElementsByTagNameNS) {
					results = this.getElementsByTagNameNS("",t);
				}
				if ((results == null) || (results.length == 0))
				results = this.getElementsByTagName(t);
				return results;
			}//getElementsByTagName
			,
			gFEBTN: function() {
				t = arguments[0];
				els = this.gEBTN(t);
				if (els)
					return IBDOM.IBElement.getAugmentedElement(els[0]);
				else
					return null;
			}//getFirstElementByTagName
			,
			gEBCN: function() {
				cName = arguments[0];
				tagName = arguments[1];
				dels = tagName ? this.gEBTN(tagName) : this.gEBTN("*");
				els = new Array(); j = 0;
				for (t = 0; dels[t]; t++) {
					if (
							(c = dels[t].className)
								&&
								(
									cName in IBDOM.Utils.getMapFromSplit(c," ")
								)
						) {
						els[j] = IBDOM.IBElement.getAugmentedElement(dels[t]);
						j++;
					}
				}
				if (els.length == 0) els = null;
				return els;
			}//getElementsByClassName
			,
			gFEBCN: function() {
				if (col = this.gEBCN(arguments[0],arguments[1])) {
					return col[0];
				} else return null;
			}//getFirstElementByClassName
			,
			gEBANV: function() {
				attName = arguments[0];
				attValue = arguments[1];
				tagName = arguments[2];
				nodeResults = new Array();
				rcount = 0;
				els = tagName ? this.gEBTN(tagName) : this.gEBTN("*");
				if (attName && attValue) {
					for (i=0;(el=els[i]);i++) {
						el = IBDOM.IBElement.getAugmentedElement(el);
						if(el.getAttribute && (el.getAttribute(attName) == attValue)) {
							nodeResults[rcount] = el;
							rcount++;
						}//if element has passed attribute, add it to result set
					}//loop thru retrieved elements
				}//attribute Name and Value passed
				return nodeResults;
			}//getElementsByAttributeNameValue
			,
			gFEBANV: function() {
				if (col = this.gEBANV(arguments[0], arguments[1], arguments[2])) {
					return col[0];
				} else return null;
			}//getFirstElementByAttributeNameValue
			,
			getTextData: function() {
				s = new IBDOM.Utils.StringBuffer();
				c = this.firstChild;
				while (c) {
					if ((c.nodeType == IBDOM.NodeTypes.TEXT_NODE) || (c.nodeType == IBDOM.NodeTypes.CDATA_NODE)) {
						s.append(c.data);
					}
					c = c.nextSibling;
				}
				return s.toString();
			}//getTextData
			,
			setTextData: function() {
				this.removeAllChildren();
				this.appendChild(doc.createTextNode(arguments[0]));
			}
		}//methods
		,
		getAugmentedElement: function() {
			/* inspired by http://www.prototypejs.org/ */
			node = arguments[0];
			if (node && !node._IB_AUGMENTED) {
				methods = this.methods;
				for (property in methods) {
					value = methods[property];
					if (typeof value == 'function' && !(property in node))
					node[property] = value;
				}
				node._IB_AUGMENTED = {};
			}
			
			if (window.Element && window.Element.extend) {
				return window.Element.extend(node);
			} else {
				return node;
			}
		}//getNode
	}//IB Element object
}//IBDOM Library

function $e(i) {
	return IBDOM.IBElement.getAugmentedElement(document.getElementById(i));
}//getElementById

if (typeof($) == "undefined") {
	$ = $e;
}

doc = IBDOM.IBElement.getAugmentedElement(document);

String.prototype.trim = function() {
	return (this.replace(/^[ \r\n\t\f\s]+/,"").replace(/[ \r\n\t\f\s]+$/,""));
}
