var Prototype =
{
	Browser:
	{
		IE: !!(window.attachEvent && !window.opera),
		Opera: !!window.opera,
		WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
		Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
		MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
	},
			
	BrowserFeatures:
	{
		XPath: !!document.evaluate,
		ElementExtensions: !!window.HTMLElement,
		
		SpecificElementExtensions:
		
		document.createElement('div').__proto__ &&
		document.createElement('div').__proto__ !==
        document.createElement('form').__proto__
	},
			
	ScriptFragment: 
	
	'<script[^>]*>([\\S\\s]*?)<\/script>', 
	JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, 
	emptyFunction: function() { },
	K: function(x) { return x }
};

var Class =
{
	create: function()
	{
		var parent = null, properties = $A(arguments);
		
		if(Object.isFunction(properties[0]))
			parent = properties.shift();
		
		function klass()
		{
			this.initialize.apply(this, arguments);
		}
		
		Object.extend(klass, Class.Methods);
		
		klass.superclass = parent;
		klass.subclasses = [];
		
		if(parent)
		{
			var subclass = function() { };
			subclass.prototype = parent.prototype;
			klass.prototype = new subclass;
			parent.subclasses.push(klass);
		}
		
		for(var i = 0; i < properties.length; i++)
			klass.addMethods(properties[i]);
		
		if(!klass.prototype.initialize)
			klass.prototype.initialize = Prototype.emptyFunction;
		
		klass.prototype.constructor = klass;
		
		return klass;
	}
};

Class.Methods =
{
	addMethods: function(source)
	{
		var ancestor   = this.superclass && this.superclass.prototype;
		var properties = Object.keys(source);
		
		if(!Object.keys({ toString: true }).length)
			properties.push('toString', 'valueOf');
		
		for(var i = 0, length = properties.length; i < length; i++)
		{
			var property = properties[i], value = source[property];
			
			if(ancestor && Object.isFunction(value) && value.argumentNames().first() == '$super')
			{
				var method = value, value = Object.extend((function(m)
				{
					return function() { return ancestor[m].apply(this, arguments) };
				})(property).wrap(method), { valueOf:  function() { return method }, toString: function() { return method.toString() }});
			}
	
			this.prototype[property] = value;
		}
	
	return this;
	
	}
};

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

Object.extend(Object,
{
	keys: function(object)
	{
		var keys = [];
		
		for(var property in object)
			keys.push(property);
		
		return keys;
	},
		
	clone: function(object)
	{
		return Object.extend({ }, object);
	},
		
	isArray: function(object)
	{
		return object && object.constructor === Array;
	},
	
	isHash: function(object)
	{
		return object instanceof Hash;
	},
	
	isFunction: function(object)
	{
		return typeof object == 'function';
	},
		
	isString: function(object)
	{
		return typeof object == 'string';
	},
	
	isUndefined: function(object)
	{
		return typeof object == 'undefined';
	}
});

Object.extend(Function.prototype,
{
	argumentNames: function()
	{
		var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(',').invoke('strip');
		return names.length == 1 && !names[0] ? [] : names;
	},
	
	bind: function()
	{
		if(arguments.length < 2 && Object.isUndefined(arguments[0]))
			return this;
		
		var __method = this, args = $A(arguments), object = args.shift();
		
		return function()
		{
			return __method.apply(object, args.concat($A(arguments)));
		}
	},
		
	methodize: function()
	{
		if(this._methodized) return this._methodized;
			var __method = this;
			
		return this._methodized = function() { return __method.apply(null, [this].concat($A(arguments)));};
	}
});

Object.extend(String.prototype,
{
	strip: function()
	{
		return this.replace(/^\s+/, '').replace(/\s+$/, '');
	},
		
	capitalize: function()
	{
		return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
	}
});

var Enumerable =
{
	each: function(iterator, context)
	{
		var index = 0;
		iterator = iterator.bind(context);
		try
		{
			this._each(function(value)
			{
				iterator(value, index++);
			});
		}
		catch (e)
		{
			if(e != $break)
				throw e;
		}
		
		return this;
	},
		
	collect: function(iterator, context)
	{
		iterator = iterator ? iterator.bind(context) : Prototype.K;
		
		var results = [];
		
		this.each(function(value, index)
		{
			results.push(iterator(value, index));
		});
		
		return results;
	},
	
	findAll: function(iterator, context)
	{
		iterator = iterator.bind(context);
		
		var results = [];
		
		this.each(function(value, index)
		{
			if(iterator(value, index))
				results.push(value);
		});
		
		return results;
	},
		
	invoke: function(method)
	{
		var args = $A(arguments).slice(1);

		return this.map(function(value)
		{
			return value[method].apply(value, args);
		});
	},
	
	reject: function(iterator, context)
	{
		iterator = iterator.bind(context);
		
		var results = [];
		
		this.each(function(value, index)
		{
			if(!iterator(value, index))
				results.push(value);
		});
		
		return results;
	}
};

Object.extend(Enumerable,
{
	map: Enumerable.collect,
	find: Enumerable.detect,
	select: Enumerable.findAll,
	filter: Enumerable.findAll,
	member: Enumerable.include,
	entries: Enumerable.toArray,
	every: Enumerable.all,
	some: Enumerable.any
});

function $A(iterable)
{
	if(!iterable)
		return [];
	if(iterable.toArray)
		return iterable.toArray();
	
	var length = iterable.length || 0, results = new Array(length);
	
	while (length--) results[length] = iterable[length];
	
	return results;
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

Object.extend(Array.prototype,
{
	_each: function(iterator)
	{
		for(var i = 0, length = this.length; i < length; i++)
			iterator(this[i]);
	},
		
	first: function()
	{
		return this[0];
	},
		
	last: function()
	{
		return this[this.length - 1];
	}
});

function $w(string)
{
	if(!Object.isString(string))
		return [];
	
	string = string.strip();
	
	return string ? string.split(/\s+/) : [];
}

$w('abs round ceil floor').each(function(method)
{
	Number.prototype[method] = Math[method].methodize();
});

function $H(object)
{
	return new Hash(object);
};

var Hash = Class.create(Enumerable, (function()
{
	function toQueryPair(key, value)
	{
		if(Object.isUndefined(value))
			return key;
		
		return key + '=' + encodeURIComponent(String.interpret(value));
	}
	
	return {
		initialize: function(object)
		{
			this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
		},
			
		set: function(key, value)
		{
			return this._object[key] = value;
		},
			
		get: function(key)
		{
			return this._object[key];
		}
	}
})());

function $(element)
{
	if(arguments.length > 1)
	{
		for(var i = 0, elements = [], length = arguments.length; i < length; i++)
			elements.push($(arguments[i]));
		
		return elements;
	}
	
	if(Object.isString(element))
		element = document.getElementById(element);
	
	return Element.extend(element);
}

(function()
{
	var element = this.Element;
	
	this.Element = function(tagName, attributes)
	{
		attributes = attributes || { };
		tagName = tagName.toLowerCase();
		
		var cache = Element.cache;
		
		if(Prototype.Browser.IE && attributes.name)
		{
			tagName = '<' + tagName + ' name="' + attributes.name + '">';
			
			delete attributes.name;
			
			return Element.writeAttribute(document.createElement(tagName), attributes);
		}
		
		if(!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
			return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
	};
	
	Object.extend(this.Element, element || { });
}).call(window);

Element.Methods =
{
	cumulativeOffset: function(element)
	{
		var valueT = 0, valueL = 0;
		
		do
		{
			valueT += element.offsetTop  || 0;
			valueL += element.offsetLeft || 0;
			element = element.offsetParent;
		}
		
		while (element);
			return Element._returnOffset(valueL, valueT);
	}
};

Element._returnOffset = function(l, t)
{
	var result = [l, t];
	result.left = l;
	result.top = t;
	return result;
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if(!Prototype.BrowserFeatures.ElementExtensions && document.createElement('div').__proto__)
{
	window.HTMLElement = { };
	window.HTMLElement.prototype = document.createElement('div').__proto__;
	Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function()
{
	if(Prototype.BrowserFeatures.SpecificElementExtensions)
		return Prototype.K;
	
	var Methods = { }, ByTag = Element.Methods.ByTag;
	
	var extend = Object.extend(function(element)
	{
		if(!element || element._extendedByPrototype || element.nodeType != 1 || element == window)
			return element;
		
		var methods = Object.clone(Methods),
				
		tagName = element.tagName, property, value;
		
		if(ByTag[tagName])
			Object.extend(methods, ByTag[tagName]);
		
		for(property in methods)
		{
			value = methods[property];
			
			if(Object.isFunction(value) && !(property in element))
				element[property] = value.methodize();
		}
		
		element._extendedByPrototype = Prototype.emptyFunction;
		
		return element;
	},

	{
		refresh: function()
		{
			if(!Prototype.BrowserFeatures.ElementExtensions)
			{
				Object.extend(Methods, Element.Methods);
				Object.extend(Methods, Element.Methods.Simulated);
			}
		}
	});

	extend.refresh();
	
	return extend;
})();

Element.addMethods = function(methods)
{
	var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
	
	if(arguments.length == 2)
	{
		var tagName = methods;
		methods = arguments[1];
	}
	
	function copy(methods, destination, onlyIfAbsent)
	{
		onlyIfAbsent = onlyIfAbsent || false;
		
		for(var property in methods)
		{
			var value = methods[property];

			if(!Object.isFunction(value))
				continue;
			
			if(!onlyIfAbsent || !(property in destination))
				destination[property] = value.methodize();
		}
	}
	
	if(F.ElementExtensions)
	{
		copy(Element.Methods, HTMLElement.prototype);
		copy(Element.Methods.Simulated, HTMLElement.prototype, true);
	}
};

document.viewport =
{
	getDimensions: function()
	{
		var dimensions = { };
		
		var B = Prototype.Browser;
		
		$w('width height').each(function(d)
		{
			var D = d.capitalize();
			
			dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
			(B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
		});
		
		return dimensions;
	},
		
	getHeight: function()
	{
		return this.getDimensions().height;
	},
		
	getScrollOffsets: function()
	{
		return Element._returnOffset(window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, 
			window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
	}
};

Element.addMethods(
{
	fire: Event.fire,
	observe: Event.observe,
	stopObserving: Event.stopObserving
});
