/* atomic javascript
 *
 * -Ultra lightweight. ~5kb! Can be compressed further
 * -Totally extensible and modular, extend any part of the functionality.
 * -Doesn't attempt to do everything in one file. 
 * 
 *                                                               
 *  This file contains:
 *  -CSS selectors
 *  -OO tools for inheritance
 *  -Methods on elements, and groups of elements:
 *  	apply: Apply a function to an element or group of elements
 *  
 */
(function() {
String.prototype.trim = function() { return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); };
Array.prototype.has = function(needle) { 
	var haystack = this;
	for (var i = 0; i < haystack.length; i++) {
        if (haystack[i] == needle) {
            return true;
        }
    }
    return false;
}


//Cant use "extends" its reserved word in IE.
Function.prototype.inherit = function(parent) {
	var parentObject = new parent;
	for (var x in parentObject) this.prototype[x] = parentObject[x];

	this.___parent = null;
	this.prototype.__parent = function() {
		if (!this.___parent) {
			var self = this;
			this.___parent = new Object;
			this.___parent.construct = function() {
				parent.apply(self, arguments); 	
			}
			for (var x in parentObject) {
				var item = parentObject[x];
		    		 if (typeof(item) == 'function') {
		    			 this.___parent[x] = function() {
		    			 item.apply(self, arguments);
		    		 }
		    	 }
			}
		}
		return this.___parent;
	}
	return this;
}

window.$e = function(tagName) {return $.extend($().createElement(tagName));}
window.$t = function (text) {return $().createTextNode(text);}


window.cloneObject = function(obj) {
	var newObj = {};
	for (var x in obj) newObj[x] = obj[x];
	return newObj;
}


window.$ = function(selector, parent) {	
	if (typeof(selector) == 'object') return $.extend(selector);
	if (!selector || selector == '') return document;
	
	//deal with commas
	var parts = selector.split(',');


	var found = new Array();	
	if (!parent) parent = $.doc;
	else if (parent.tagName) parent = new atomElementGroup(new Array(parent));
	
	for (var i = 0; i < parts.length; i++) {
		var trail = parts[i].replace(/([^a-z^A-Z])\s+/g, '$1').match(window.$.re);
		//__dbg = trail;
		for (var j = 0; j < trail.length; j++) parent = $_(trail[j].trim(), parent);
		found = found.concat(parent._elements);
	}
	
	for (var i = 0; i < found.length; i++) $.extend(found[i]);

	if (found.length === 1) return found[0];
	else return new atomElementGroup(found);
}

window.$_ = function(selector, parent) {

	for (var i = 0; i < $.cssRules.length; i++) {
		var rule = new $.cssRules[i](selector, parent);
		if (rule.check()) {			
			return new atomElementGroup(rule.getElements());
		}
	}
}

window.$.selectorChars = new Array('.', '#', '>');
window.$.ruleChars = new Array('~', '^', '"', '=');

window.$.re = new RegExp('(([\\ ' + window.$.selectorChars.join('\\') + ']|)[a-zA-Z0-9\\_\\' + window.$.ruleChars.join('\\') +'\\"]+|\\[.+\\])', 'g');

function cssRuleBase(rule, parent) {
	this.rule = rule;
	this.parent = parent;
}

var cssRuleBackwards = function(rule, parent) {
	this.__parent().construct(rule, parent);
	
	this.check = function() {
		return (this.rule.trim().charAt(0) == '[');
	}
	
	this.getElements = function() {
		
	}
	
}.inherit(cssRuleBase);

var cssRuleTagName = function(rule, parent) {

	this.check = function() {
		return /^[a-zA-Z]/.test(rule);
	}
	
	this.getElements = function() {
		if (parent.tagName) this.parent = new atomElementGroup(new Array(parent));
		var uid = Math.random();		
		var elements = new Array();
		for (var i = 0; i < parent._elements.length; i++) {
			var found = parent._elements[i].getElementsByTagName(rule);
			for (var j = 0; j < found.length; j++) {
				if (!found[j].uid || found[j].uid != uid) {
					found[j].uid = uid; //Tag the element so it's not picked up more than once when elements are nested.
					elements.push(found[j]);
				}
			}
			
		}		
		return elements;
	}
}.inherit(cssRuleBase);


var cssRuleCss3 = function(rule, parent) {
	this.__parent().construct(rule, parent);
	
	this.check = function() {
		return (this.rule.charAt(0) == '[');
	}
	
	this.getElements = function() {
		var matches = /([a-zA-Z]+)([\~]*=?)\"?([^"]+)?\"?/.exec(this.rule.substring(1));
		return this.findElements(matches[1], matches[3], matches[2]);
	}
	
	this.findElements = function(attribute, value, operator) {
		if (attribute == 'class') attribute = 'className';
		var foundElements = new Array();
		
	
		for (var i = 0; i < this.parent._elements.length; i++) {
			if (!this.parent._elements[i][attribute]) continue;

			if (operator == '') {
				if (this.parent._elements[i][attribute]) foundElements.push(this.parent._elements[i]);
			}
			else if (operator == '=') {
				if (this.parent._elements[i][attribute] == value) foundElements.push(this.parent._elements[i]);
			}
			else if (operator == '~=') {
				var parts = this.parent._elements[i][attribute].split(' ');
				for (var j = 0; j < parts.length; j++) {
					if (parts[j] == value) {
						foundElements.push(this.parent._elements[j]);
						continue;
					}
				}
			}
		}
		return foundElements;
	}
}.inherit(cssRuleBase);


var cssRuleStyle = function(rule, parent) {
	this.__parent().construct(rule, parent);
	
	this.check = function() {
		return (this.rule.trim().charAt(0) == '{');
	}
	
	this.getElements = function() {
		var matches = /([a-zA-Z]+)(|[[\~\|]*=)\"(.+)\"/.exec(this.rule.trim().substring(1));
		return this.findElements(matches[1], matches[3], matches[2]);
	}
	
	this.findElements = function(attribute, value, operator) {
		var found = new Array();
		for (var i = 0; i < this.parent._elements.length; i++) {
			if (this.parent._elements[i].computedStyle()[attribute] == value) {
				$.extend(this.parent._elements[i]);
				found.push(this.parent._elements[i]);
			}
		}		
		return found;
	}
}.inherit(cssRuleBase);


var cssRuleClassName = function(rule, parent) {
	this.rule = rule;
	this.parent = parent;
	
	this.check = function() {
		return (this.rule.charAt(0) === '.');
	}
	
	this.getElements = function() {
		var foundElements = new Array();
		var className = this.rule.substring(1);
		for (i = 0; i < parent._elements.length; i++) {
			if (parent._elements[i].getElementsByClassName) {
				var els = parent._elements[i].getElementsByClassName(className);
				for (var j = 0; j < els.length; j++) foundElements.push(els[j]);
			}
			else {
				var elements = parent._elements[i].all ? parent._elements[i].all : parent._elements[i].getElementsByTagName('*');
				for (var j = 0; j < elements.length; j++) {
					if (elements[j].className && (' ' + elements[j].className + ' ').indexOf(' ' + className + ' ') >= 0) {
						foundElements.push(elements[j]);
					}
				}
			}
		}	
		
		return foundElements;	
	}
}.inherit(cssRuleBase);


var cssRuleId = function(rule, parent) {
	this.rule = rule;
	this.parent = parent;
	
	this.check = function() {
		return (this.rule.charAt(0) == '#');
	}
	
	this.getElements = function() {
		return new Array(document.getElementById(this.rule.substring(1)));
	}
	
}.inherit(cssRuleBase);

var cssRuleArrow = function(rule, parent) {
	this.check = function() {
		return (rule.charAt(0) == '>');
	}
	
	this.getElements = function() {
		var elements = new Array();
		for (var i = 0; i < parent._elements.length; i++) {
			for (var j = 0; j < parent._elements[i].childNodes.length; j++) {
				if (parent._elements[i].childNodes[j].tagName && parent._elements[i].childNodes[j].tagName.toLowerCase() == rule.substring(1).toLowerCase()) elements.push(parent._elements[i].childNodes[j]);
			}
		}
		
		return elements;
		
	}
}.inherit(cssRuleBase);

$.cssRules = new Array(cssRuleClassName, cssRuleId, cssRuleTagName, cssRuleCss3, cssRuleStyle, cssRuleArrow);

function atomElementGroup(elements) {
	this._elements = elements;
}

atomElementGroup.prototype.count = function() {
	return this._elements.length;
}

atomElementGroup.prototype.item = function(index) {
	return this._elements[index];
}

atomElementGroup.prototype.$ = function(selector) {
	var elements = new Array();
	for (var i = 0; i < this._elements.length; i++) {
		var c = this._elements[i].$(selector);
		for (var j = 0; j < c.length; j++) elements.push(c[j]);
	}
	return elements;
}


var atom = {		
	init: function() {	
		for (x in this) $[x] = this[x];
		
		$.elementPrototype = new Object;
		
		$.ready = function(func) {
			if (!this.loadEvents) {
				this.loadEvents = new Array();
				this.loadEvents.push(func);
				
				var self = this;				
				var loadFunc = function() {
					for (var i = 0; i < self.loadEvents.length; i++) self.loadEvents[i]();
				}
				
				if($().addEventListener) $().addEventListener('DOMContentLoaded', loadFunc, false);
				else if (navigator.appName.toLowerCase().indexOf('explorer') > 0) {
					$().write('<script id="__onload" defer src="//:"><\/script>');
					$('#__onload').onreadystatechange = function() {
						if (this.readyState == 'complete') loadFunc();
					}
				}
			}
			else this.loadEvents.push(func);			
		}
		
		$.addElementPrototype = function(name, f) {
			$.elementPrototype[name] = f;
			if (!atomElementGroup.prototype[name]) {
				atomElementGroup.prototype[name] = function(f) {
					return function() {
						for (var i = 0; i < self._elements.length; i++)	f.apply(self._elements[i], arguments);
					}
				}(func);
			}
		}
		
		$.elementPrototype.$ = function(selector) {
			return $(selector, this);
		}
		
		//Finds first text node and sets the value.
		$.elementPrototype.setText = function(text) {
			this.firstChild.nodeValue = text;
		}
		
		$.elementPrototype.getText = function() {
			return this.firstChild.nodeValue;
		}
		
		$.elementPrototype.parent = function() {
			$.extend(this.parentNode);
			return this.parentNode;
		}
		
		$.elementPrototype.addClass = function(className) {
			if (this.className.indexOf(className) == -1) this.className = this.className + ' ' + className; 
		}
		
		$.elementPrototype.removeClass = function(className) {
			this.className = this.className.replace(className, '');
		}
		
		$.elementPrototype.item = function(index) {
			if (index > 0 ) return null
			else return this;
		}
		
		$.elementPrototype.getSibling = function(offset) {
			if (offset < 0) {
				offset = Math.abs(offset);
				dir = 'previous';
			}
			else if (offset = 0) {
				offset = 1;
				dir = 'next';
			}
			else dir = 'next';
			
			el = this;
			while (offset > 0) el = el[dir + 'Sibling'];
						
			return $.extend(el);		
		}
		
		$.elementPrototype.count = function() {
			return 1;
		}
			
		$.elementPrototype.empty = function() {
			while (this.hasChildNodes()) this.removeChild(this.firstChild);
		}
		
		$.elementPrototype.remove = function() {
			this.parentNode.removeChild(this);
		}
		
		$.elementPrototype.getChildElement = function(index) {
			var elementCount = 0;
			for (var i = 0; i < this.childNodes.length; i++) {
				if (this.childNodes[i].nodeType == 1) {
					if (index == elementCount) {
						$.extend(this.childNodes[i]);
						return this.childNodes[i];
					}
					elementCount++;
				}
			}
			return this.childNodes[index];
		}
		
		$.elementPrototype.apply = function(func) {
			this._tmp = func;
			this._tmp();
		}		
		
		$.elementPrototype.add = function(child) {
			if (child.tagName) this.appendChild(child);
			else this.appendChild(new $t(child));
		}
		
		$.elementPrototype.position = function() {
			var element = this;
			var selectedPosX = 0;
			var selectedPosY = 0;
			var width = element.offsetWidth;
			var height = element.offsetHeight;
			do {
				selectedPosX += element.offsetLeft;
			    selectedPosY += element.offsetTop;
			}
			while (element = element.offsetParent);

			
			return {left : selectedPosX ,top : selectedPosY, width: width, right: selectedPosX + width, bottom: selectedPosY + height, height: height}
		}
		
		$.elementPrototype.addEvent = function(type, func, stopBubble) {
			
			if (!this.events) this.events = new Array();
			if (!this.events[type]) this.events[type] = new Array();
			var self = this;
			
			var f = stopBubble ? function(e) { $.killEvent(e); return func.apply(self, arguments); } : function(e) { return func.apply(self, arguments); }
			
			if (this.attachEvent) this.attachEvent('on' + type, f );
			else if (this.addEventListener) this.addEventListener(type, f, stopBubble);

			return func;
		}
		
		$.elementPrototype.computedStyle = function() {
			if (this.currentStyle) return this.currentStyle;
			else return $().defaultView.getComputedStyle(this, null);
		}
				
	/*	for (var name in $.elementPrototype) {
			var func = $.elementPrototype[name];
			if (!atomElementGroup.prototype[name]) {
				atomElementGroup.prototype[name] = function(f) {
					return function() {
						for (var i = 0; i < this._elements.length; i++)	f.apply(this._elements[i], arguments);
					}
				}(func);
			}
		}*/
		
		
		//var self = ;
		for (var name in $.elementPrototype) {
			var func = $.elementPrototype[name];
			if (!atomElementGroup.prototype[name]) {
				atomElementGroup.prototype[name] = function(f) {
					return function() {
						for (var i = 0; i < this._elements.length; i++)	f.apply(this._elements[i], arguments);
					}
				}(func);
			}
		}
		
		
		$.doc = new atomElementGroup(new Array(document));
	},
	
	include: function(name) {
		var script = new $e('script');
		script.setAttribute('type', 'text/javascript'),
		script.setAttribute('src', name);
		$('head').appendChild(script);
	},
	
	killEvent: function(eventObject) {
		if (window.event) {
			window.event.cancelBubble = true;
			window.event.returnValue = false;
		}

		if (eventObject) {
			if (eventObject.stopPropagation) eventObject.stopPropagation();
			if (eventObject.preventDefault) eventObject.preventDefault();
			if (navigator.appName.indexOf('Firefox') == 0) {
				if (eventObject.preventCapture) eventObject.preventCapture();
	    		if (eventObject.preventBubble) eventObject.preventBubble();
			}
			eventObject.cancelBubble = true;
			eventObject.returnValue = false;
		}
		return false;
	},
	
	extend: function(element) {
		if (!element || element._elements) return element;
		
		for (name in $.elementPrototype) element[name] = $.elementPrototype[name];
		
		element._elements = new Array();
		element._elements[0] = this;
		return element;
	}
}
atom.init();
})();
