var zCore = {
	version: '0.1',
	free: function() { }
};

var Class = {
	create: function() {
		return function() { 
			this.initialize.apply(this, arguments);
		}
	}
};

Object.extend = function(destination, source) {
  for (property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Object.prototype.extend = function(object) {
  return Object.extend.apply(this, [this, object]);
}

function $() {
	if (arguments.length == 1) return document.getElementById(arguments[0]);
	else {
		var elements = new Array();
		
		for (var ai = 0; ai < arguments.length; ai++) {
			elements.push(document.getElementById(arguments[ai]));
		}
		
		return elements;
	}
}

function concat() {
    var result = [];

    for (var i = 0; i < arguments.length; i++)
        for (var j = 0; j < arguments[i].length; j++)
            result.push(arguments[i][j]);

    return result;
}

function withoutFirst(sequence) {
    result = [];

    for (var i = 1; i < sequence.length; i++)
        result.push(sequence[i]);

    return result;
}

function cons(element, sequence) {
    return concat([element], sequence);
}

Function.prototype.bind = function(object) {
	var method = this;
    var preappliedArguments = withoutFirst(arguments);
	return function() {
		return method.apply(object, concat(preappliedArguments, arguments));
	};
};
Function.prototype.bindEventListener = function (object) {
    var method = this;
    var preappliedArguments = withoutFirst(arguments);
    return function (event) {
        return method.apply(object, cons(event || window.event, preappliedArguments));
    };
};

document.getElementsWithClassName = function(className) {
	var elements = new Array();
					
	var tagFilter = arguments[1] ? arguments[1] : '*';
	var source = document.getElementsByTagName(tagFilter) || document.all;

	if (source) {
		for (var si = 0; si < source.length; si++) {
			if (source[si].className.indexOf(className) != -1) {
				elements.push(source[si]);
			}
		}
	}
	
	return elements;
};

zCore.Events = {
	addEventListener: function (element, event, handler, capture) {
		if (element.addEventListener) {
			element.addEventListener(event, handler, capture);
		} else if (element.attachEvent) {
			element.attachEvent('on'+event, handler);
		}
	},
	
	removeEventListener: function (element, event, handler, capture) {
		if (element.removeEventListener) {
			element.removeEventListener(event, handler, capture);
		} else if (element.detachEvent) {
			element.detachEvent('on'+event, handler);
		}
	},

	getEventTarget: function (evt) {
		if (!evt) var evt = window.event;
		var t;
		
		if (evt.target) t = evt.target;
		else if (evt.srcElement) t = evt.srcElement;
	
		if (t.nodeType == 3) // Bystep Safari bug
			t = t.parentNode;
	
		return t;
	},

	getMouseCoordinates: function (evt) {
		if (!evt) var evt = window.event;
		var posx = 0;
		var posy = 0;
		
		if (evt.pageX || evt.pageY) {
			posx = evt.pageX;
			posy = evt.pageY;
		} else if (evt.clientX || evt.clientY) {
			posx = evt.clientX + document.body.scrollLeft;
			posy = evt.clientY + document.body.scrollTop;
		}
		
		return { x: posx, y: posy };
	},

	abort : function (evt) {
		if (evt.preventDefault) evt.preventDefault();
        else evt.returnValue = false;
	}	
};


/*
   getAllChildren and getElementsBySelector is Copyright (C) Simon Willison 2004.

   document.getElementsBySelector(selector)
   - returns an array of element objects from the current document
     matching the CSS selector. Selectors can contain element names, 
     class names and ids and can be nested. For example:
     
       elements = document.getElementsBySelect('div#main p a.external')
     
     Will return an array of all 'a' elements with 'external' in their 
     class attribute that are contained inside 'p' elements that are 
     contained inside the 'div' element which has id="main"

   New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
   See http://www.w3.org/TR/css3-selectors/#attribute-selectors

   Version 0.4 - Simon Willison, March 25th 2003
   -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
   -- Opera 7 fails 
*/
zCore.DOM = {
	getAllChildren: function (e) {
		// Returns all children of element. Workaround required for IE5/Windows. Ugh.
		return e.all ? e.all : e.getElementsByTagName('*');
	},

	getParentWithTagName: function (child, tagName) {
		if (!child)
			return null;
	
		var to_match = new RegExp('^'+tagName+'$', 'i');
		var parent = child.parentNode;
		
		while (!parent.nodeName.match(to_match) && parent.parentNode) {
			parent = parent.parentNode;
		}
		
		if (!parent.nodeName.match(to_match)) {
			return null;
		}
		
		return parent;
	},
	
	getElementsWithClassName: function (className, tagName, inRoot) {
		var elements = new Array();
		
		if (!inRoot) inRoot = document;
		if (!tagName) tagName = '*';
		
		var source = inRoot.getElementsByTagName(tagName);
	
		if (source) {
			for (var si = 0; si < source.length; si++) {
				if (source[si].className.indexOf(className) != -1) {
					elements.push(source[si]);
				}
			}
		}
		
		return elements;
	},

	getElementsBySelector: function (selector) {
		// Attempt to fail gracefully in lesser browsers
		if (!document.getElementsByTagName) {
			return new Array();
		}
		
		// Split selector in to tokens
		var tokens = selector.split(' ');
		var currentContext = new Array(document);

		for (var i = 0; i < tokens.length; i++) {
			token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');

			if (token.indexOf('#') > -1) { // Token is an ID selector
				var bits = token.split('#');
				var tagName = bits[0];
				var id = bits[1];
				var element = document.getElementById(id);
				
				if (tagName && element.nodeName.toLowerCase() != tagName) {
					// Tag with that ID not found, return false
					return new Array();
				}
				// Set currentContext to contain just this element
				currentContext = new Array(element);
				continue; // Skip to next token
			}
			
			if (token.indexOf('.') > -1) { // Token contains a class selector
				var bits = token.split('.');
				var tagName = bits[0];
				var className = bits[1];
				
				if (!tagName) {
					tagName = '*';
				}
				// Get elements matching tag, filter them for class selector
				var found = new Array;
				var foundCount = 0;
				for (var h = 0; h < currentContext.length; h++) {
					var elements;
					if (tagName == '*') {
						elements = getAllChildren(currentContext[h]);
					} else {
						elements = currentContext[h].getElementsByTagName(tagName);
					}
					for (var j = 0; j < elements.length; j++) {
						found[foundCount++] = elements[j];
					}
				}
				currentContext = new Array;
				var currentContextIndex = 0;
				for (var k = 0; k < found.length; k++) {
					if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
						currentContext[currentContextIndex++] = found[k];
					}
				}
				continue; // Skip to next token
			}
			
			if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { // Code to deal with attribute selectors
				var tagName = RegExp.$1;
				var attrName = RegExp.$2;
				var attrOperator = RegExp.$3;
				var attrValue = RegExp.$4;
				if (!tagName) {
					tagName = '*';
				}
				
				// Grab all of the tagName elements within current context
				var found = new Array;
				var foundCount = 0;
				for (var h = 0; h < currentContext.length; h++) {
					var elements;
					if (tagName == '*') {
						elements = getAllChildren(currentContext[h]);
					} else {
						elements = currentContext[h].getElementsByTagName(tagName);
					}
					for (var j = 0; j < elements.length; j++) {
						found[foundCount++] = elements[j];
					}
				}
				/* Redundant, right?? See below switch
				currentContext = new Array;
				var currentContextIndex = 0;
				*/
				var checkFunction; // This function will be used to filter the elements
				switch (attrOperator) {
					case '=': // Equality
						checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
						break;
					case '~': // Match one of space seperated words 
						checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
						break;
					case '|': // Match start with value followed by optional hyphen
						checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
						break;
					case '^': // Match starts with value
						checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
						break;
					case '$': // Match ends with value - fails with "Warning" in Opera 7
						checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
						break;
					case '*': // Match ends with value
						checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
						break;
					default: // Just test for existence of attribute
						checkFunction = function(e) { return e.getAttribute(attrName); };
				}
				currentContext = new Array;
				var currentContextIndex = 0;
				for (var k = 0; k < found.length; k++) {
					if (found[k].getAttribute(attrName) && checkFunction(found[k])) {
						currentContext[currentContextIndex++] = found[k];
					}
				}
				continue; // Skip to next token
			}
			
			if (!currentContext[0]) {
				return;
			}
    		
    		// If we get here, token is JUST an element (not a class or ID selector)
    		tagName = token;
    		var found = new Array;
    		var foundCount = 0;
    		for (var h = 0; h < currentContext.length; h++) {
    			var elements = currentContext[h].getElementsByTagName(tagName);
    			for (var j = 0; j < elements.length; j++) {
    				found[foundCount++] = elements[j];
    			}
    		}
    		currentContext = found;
    	}
    	return currentContext;
	},
	
	getElementCoordinates: function (elm) {
		var coords = new Coordinates(0, 0);
	
		if (elm.offsetParent) {
			while (elm.offsetParent) {
				coords.x += elm.offsetLeft;
				coords.y += elm.offsetTop;
				elm = elm.offsetParent;
			}
		} else if (elm.x && elm.y) {
			coords.x += elm.x;
			coords.y += elm.y;
		}
		
		return coords;
	}
};
document.getElementsBySelector = zCore.DOM.getElementsBySelector;
