/*
Copyright (c) 2007 Rizqi Ahmad

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var foo = function(){
	if(!foo.noLoad && !foo.isLoaded){
		foo.isLoaded = true;
		foo.reversion = foo.Utils.reverseTest();
		foo.unique = 0;
		foo.idx = 0;
		foo.getNamespace();
		
		foo.fireEvent('initialize');
		
		document.body.id = 'root';
		new foo.Section(document.body, null);
	}
};

// Extend
foo.extend = function(target, source){
	source = source || target || {};
	target = source == target ? this : target;
	
	for(var i in source)
		target[i] = source[i];
	
	return target;
};

// Properties
foo.extend({
	version: '0.1',
	
	Browser: {
		IE: /*@cc_on!@*/false,
		Opera: !!window.opera,
		Gecko: (navigator.product == 'Gecko') && !window.opera && !/*@cc_on!@*/false,
		Webkit: (navigator.userAgent.indexOf('AppleWebKit/')> -1)
	},
	
	ns: 'foo',
	nsURI: 'http://foo.riiv.net/namespace',
	escapeChars: [['\\', '\\\\'], ['\'', '\\\''], ['"', '\\"'], ['\r', '\\r'], ['\n','\\n'], ['\t','\\t']],
	entityChars: [['&amp;', '&'], ['&gt;', '>'], ['&lt;', '<'], ['&and;', '\^'], ['&frasl;', '\/']],
	closure: [
		[	'var $text = [];',
			'$text.print = function(text) {',
					'this.push((typeof text == "number")?text:(text||""));',
			'};'
		].join('\n'),
		
		'return $text.join("");'
	],
	
	fuidPrefix: 'fuid', // foo unique identifier
	sectionClassName: 'foo-section',
	reversion: false,
	idx: 0,
	unique: 0,
	isLoaded: false,
	noLoad: false
});

// Prototype extension
Element.addMethods({
	getSection: function(element){
		if(element._parentSection)
			return element._parentSection;
		var parentSection, el = element;
		while(el.parentNode){
			if(el.section){
				parentSection = el.section;
				break;
			}
			el = el.parentNode;
		}
		element._parentSection = parentSection;
		return parentSection || false;
	},
	
	getData: function(element, name){
		var section = $(element).getSection();
		if(section)
			return section.getElementData(element.id, name);
	}
})

// Essential functions
foo.extend({	
	namespace: function(){
		var arg, o;
		for(var i=0; i<arguments.length; i++){
			args = arguments[i].split('.');
			window[args[0]] = window[args[0]] || {};
			o = window[args[0]];
			for(var j=1; j<args.length; j++){
				o[args[j]] = o[args[j]] || {};
				o = o[args[j]];
			}
		}
	},
	
	getNamespace: function(){
		var html = document.body;
		$A(html.attributes).each(function(a){
			if(a.nodeName && a.nodeValue && (a.nodeValue == foo.nsURI || a.nodeValue == foo.nsURI+'/')){
				foo.ns = a.nodeName.split(':').pop();
				throw $break;
			}
		});
	}
});

foo.extend({
	events: {
		initialize: [],
		sectionInit: [],
		sectionUpdate: [],
		sectionRefresh: []
	},
	
	addListener: function(name, listener){
		foo.events[name].push(listener);
	},
	
	removeListener: function(name, fn){
		foo.events[name] = foo.events[name].without(fn);
	},
	
	fireEvent: function(){
		var args = $A(arguments), name = args.shift();
		foo.events[name] = foo.events[name].reject(function(fn){return fn.apply(foo, args);});
	}
});

foo.Section = Class.create({
	initialize: function(node, content){
		this.element = $(node);
		this.element.addClassName(foo.sectionClassName);
		this.id = this.element.id;
		window[this.id] = this;
		
		
		this.parent = this.element.getSection();
		this.children = [];
		if(this.parent)
			this.parent.children.push(this);
		
		this.element.section = this;
		
		if(content)
			this.element.innerHTML = content;
		
		this.events = {
			initialize: [],
			loading: [],
			loaded: [],
			update: [],
			refresh: [],
			destroy: []
		};
		
		this.clients = [];
		this.servers = [];
		this.queue = [];
		
		this.storage = {};
		this.elementData = {};
		this.sectionContents = [];
		
		this.modules = [];
		this.config = {};
		this.statements = {};
		this.tokens = {};
		
		this.unique = 0;
		
		foo.baseModules.each(this.implement.bind(this));
		
		this.fireEvent('initialize');
		foo.fireEvent('sectionInit', this);
		
		this.element.hide();
		this.compile();
		this.element.show();
		
		this.refresh();
	},
	
	loading: function(){
		this.fireEvent('loading');
	},
	
	loaded: function(){
		this.fireEvent('loaded');
	},
	
	update: function(){
		this.fireEvent('update');
		foo.fireEvent('sectionUpdate', this);
		this.clients.invoke('refresh', this);
	},
	
	refresh: function(){
		this.fireEvent('update');
		foo.fireEvent('sectionUpdate', this);
		this.render();
		
		this.children.invoke('destroy');
		this.children.clear();
		
		this.queue.invoke('apply', this);
		this.queue.clear();
		
		this.fireEvent('refresh');
		foo.fireEvent('sectionRefresh', this);
		this.clients.invoke('refresh', this);
	},
	
	implement: function(module){
		if(typeof module == 'string'){
			module = foo.modules[module];
			if(!module)
				return;
		}
		if(module.requires)
			module.requires.each(this.implement.bind(this));
		
		Object.extend(this.tokens, module.tokens);
		Object.extend(this.statements, module.statements);
		
		if(module.events)
			module.events.each(function(e){
				if(!this.events[e])
					this.events[e] = [];
			}, this);
		
		for(var i in this.events)
			if(module.listeners[i])
				this.events[i].push(module.listeners[i]);
		
		for(var i in module.globals){
			var descendant = module.globals[i], ancestor = this[i];
			if(ancestor && Object.isFunction(ancestor) && descendant.argumentNames().first() == '$super'){
				var method = descendant, descendant = ancestor.wrap(method);
				Object.extend(descendant, {
				valueOf:  function() { return method },
				toString: function() { return method.toString() }
			  });
			};
			this[i] = descendant;
		}
		
		module.initialize.apply(this);
		this.modules.push(module);
		this.parseTokens();
	},
	
	render: function(){
		if(this.datasource && !this.datasource.ready)
			return;
		if(this.bindings && this.bindings.length != this.bindingNames.length)
			return;
			
		delete this.storage;
		delete this.elementData;
		this.storage = {};
		this.elementData = {};
		this.unique = 0;
		
		this.element.innerHTML = this.template();
	},
	
	compile: function(){
		this.templateSource = this.element.innerHTML;
		this.templateText = this.parse();
		try {
			this.template = new Function('$data', foo.closure[0] + this.templateText + foo.closure[1]);
			this.element.innerHTML = '';
		} catch(e){
			this.error = e;
			this.template = function(){foo.Console.log('Error', 'Cannot execute emplate "'+this.id+'"')};
			this.element.innerHTML = "Error!";
		}
	},
	
	parse: function(node, options){
		var options = options || {};
		var statement, value, attr;
		var result = [];
		
		
		if(!node){
			node = this.element;
			options.excludeNode = true;
		}
		
		var nodeName = node.nodeName.toLowerCase();
		var statements = {};
		Object.extend(statements, this.statements);
		Object.extend(statements, this.tokens);
		
		
		if(nodeName == '#text' && (/[^ \t\n]+/).test(node.nodeValue)){
			result.push(foo.parseText(node.nodeValue));
		}else if(node.nodeName.toLowerCase() != 'script' && node.nodeType == 1){
			node = $(node);
			
			attr = $A(node.attributes);
			if(foo.reversion)
				attr = attr.reverse();
				
			for(var i=0; i<attr.length; i++){
				for(var state in statements){
					if(attr[i].nodeName.toLowerCase() == foo.ns+':'+state){
						statement = state;
						value = attr[i].nodeValue;
						break;
					}
				}
				if(statement)
					break;
			}
			
			// If it is a section
			if(node != this.element && (node.hasClassName(foo.sectionClassName) || this.tokens[statement])){
				var contentID = this.sectionContents.length;
				this.sectionContents.push(node.innerHTML);
				this.addFuid(node);
				var id = node.id;
				
				var variable = '_sectionID' + foo.idx++;
				var res = [
					foo.parseText(foo.Utils.serializeTag(node)),
					foo.parseText(foo.Utils.serializeTag(node, true)),
					"var "+variable+" = "+(node.hasClassName('fuid') ? "this.getLastFuid()" : foo.parseInline(node.id))+";",
					"this.addQueue(new Function('new foo.Section('+"+variable+".toJSON()+', this.sectionContents["+contentID+"])'));"
				].join('\n');
				result.push(res);
				
				
			}else if(statement){
				node.removeAttribute(foo.ns+':'+statement);
				result.push(this.statements[statement].apply(this, [node, value, options]));
			}else{
				if(!options.excludeNode)
					result.push(foo.parseText(foo.Utils.serializeTag(node)));
				
				if(node.nodeName.toLowerCase() == 'textarea')
					result.push(foo.parseText(node.value));
				else if(!options.noChild)
					for(var i=0; i<node.childNodes.length; i++)
						result.push(this.parse(node.childNodes[i]));
				
				if(!options.excludeNode)
					result.push(foo.textPrint(foo.Utils.serializeTag(node, true)));
				
			}
			
		}
		return result.join('\n');
	},
	
	parseTokens: function(){
		var attr = $A(this.element.attributes);
		if(foo.reversion)
			attr = attr.reverse();
		
		for(var i=0; i<attr.length; i++){
			for(var token in this.tokens){
				if(attr[i].nodeName == foo.ns+':'+token){
					var value = attr[i].nodeValue;
					this.element.removeAttribute(attr[i].nodeName);
					this.tokens[token].apply(this, [value]);
				}
			}
		}
	},
	
	addListener: function(name, fn){
		this.events[name].push(fn);
	},
	
	removeListener: function(name, fn){
		this.events[name] = this.events[name].without(fn);
	},
	
	fireEvent: function(){
		var args = $A(arguments), name = args.shift();
		this.events[name] = this.events[name].reject(function(fn){return fn.apply(this, args);}, this);
	},
	
	putStorage: function(name, obj){
		if(!this.storage[name])
			this.storage[name] = [];
		
		if(this.storage[name].include(obj))
			return this.storage[name].indexOf(obj);
		
		var id = this.storage[name].length;
		this.storage[name].push(obj);
		return id;
	},
	
	getStorage: function(name, id){
		if(this.storage[name])
			return this.storage[name][id];
		else
			return null;
	},
	
	setElementData: function(id, name, data){
		if(!this.elementData[id])
			this.elementData[id] = {};
		this.elementData[id][name] = data;
	},
	
	removeElementData: function(id, name){
		if(!this.elementData[id])
			delete this.elementData[id][name];
	},
	
	getElementData: function(id, name){
		if(this.elementData[id])
			return this.elementData[id][name] || null;
	},
	
	addQueue: function(fn){
		this.queue.push(fn);
	},
	
	removeEvent: function(event, fn){
		this.events[event] = this.events[event].without(fn);
	},
	
	register: function(client){
		if(!this.clients.include(client)){
			this.clients.push(client);
			client.servers.push(this);
		}
	},
	
	unregister: function(client){
		this.clients = this.clients.without(client);
		client.servers = client.servers.without(this);
	},
	
	getAllChildren: function(){
		return this.children
		.clone()
		.concat(this.children
			.map(function(e){return e.getAllChildren();})
			.flatten()
		);
	},
	
	rebuildChain: function(){
		var children = this.getAllChildren();
		this.addQueue(function(){
			children.each(function(child){
				child.clients.each(function(client){
					$(child.id).section.register(client);
				});
			});
		});
	},
	
	destroy: function(){ // Preventing memory leaks
		this.servers.invoke('unregister', this);
		this.clients.each(this.unregister.bind(this));
		this.parent = false;
		this.children.invoke('destroy');
		this.children.clear();
		this.fireEvent('destroy');
	},
	
	toElement: function(){
		return this.element;
	},
	
	newFuid: function(){
		return this.id + '_' + foo.fuidPrefix + this.unique++;
	},
	
	getLastFuid: function(){
		return this.id + '_' + foo.fuidPrefix + (this.unique-1);
	},
	
	getNextFuid: function(){
		return this.id + '_' + foo.fuidPrefix + this.unique;
	},
	
	addFuid: function(node){
		if(!node.id){
			node.id = foo.printTag[0]+'this.newFuid()'+foo.printTag[1];
			node.addClassName('fuid');
		}
	}
});

// Foo Console
foo.Console = {
	protocol: [],
	
	log: function(type, message){
		this.protocol.push({type: type, message: message, date: new Date});
	},
	
	clear: function(){
		this.protocol = [];
	}
};

// Foo Print
foo.extend({
	printTag: ['${', '}'],
	
	parseText: function(text){
		var opener, closer, result = [];
		text = unescape(text);
		while(text){
			opener = text.indexOf(foo.printTag[0]);
			closer = opener + text.substring(opener).indexOf(foo.printTag[1]) + foo.printTag[1].length;
			
			if(opener != -1) {
				if(text[opener-1] == '\\'){
					result.push(foo.textPrint(text.substring(0,opener-foo.printTag[0].length)));
					result.push(foo.textPrint(text.substring(opener,closer)));
				}else{
					if(text.substring(0,opener))
						result.push(foo.textPrint(text.substring(0,opener)));
					if(text.substring(opener,closer))
						result.push(foo.scriptPrint(text.substring(opener+foo.printTag[0].length,closer-foo.printTag[1].length)));
				}
				text = text.substring(closer);
			} else {
				if(text)
					result.push(foo.textPrint(text));
				text = '';
			}
		}
		
		return result.join('\n');
	},
	
	parseInline: function(text){
		var opener, closer, result = [];
		text = unescape(text);
		while(text){
			opener = text.indexOf(foo.printTag[0]);
			closer = text.indexOf(foo.printTag[1]) + foo.printTag[1].length;
			
			if(opener != -1) {
				if(text[opener-1] == '\\'){
					result.push(text.substring(0,opener-foo.printTag[0].length).toJSON()+text.substring(opener,closer).toJSON());
				}else{
					if(text.substring(0,opener))
						result.push(text.substring(0,opener).toJSON());
					if(text.substring(opener,closer))
						result.push('('+text.substring(opener+foo.printTag[0].length,closer-foo.printTag[1].length)+')');
					text = text.substring(closer);
				}
			} else {
				if(text)
					result.push(text.toJSON());
				text = '';
			}
		}
		return result.join('+');
	},
	
	textPrint: function(text){
		return "$text.print("+text.toJSON()+");";
	},
	
	scriptPrint: function(script){
		return "$text.print("+script+");";
	}
});

// Utilities
foo.Utils = {
	replace: function(text, reps){
		var result = '', nr, ioNr, ioI;
		while(text){
			for(var i=0; i<reps.length; i++){
				if(typeof nr != 'number' || text.indexOf(reps[nr][0]) == -1)
					nr = i;
					
				ioNr = text.indexOf(reps[nr][0]);
				ioI = text.indexOf(reps[i][0]);
				
				if(ioI != -1 && ioNr > ioI){
					nr = i;
					ioNr = ioI;
				}
			}
			if(ioNr != -1){
				result += text.substring(0, ioNr)+reps[nr][1];
				text = text.substring(ioNr+reps[nr][0].length, text.length);
			}else{
				result += text;
				text = '';
			}
		}
		return result;
	},
	
	serializeTag: function(node, closer){
		var tag, attr, attributes = [];
		var autoclose = ['img', 'br'].include(node.nodeName.toLowerCase());
		if(foo.Browser.IE){
			tag = node.outerHTML.substring(node.outerHTML.indexOf('<'+node.nodeName)+1, node.outerHTML.indexOf('>'));
			tag = foo.Utils.replace(tag, foo.entityChars);
		}else{
			var attrs = $A(node.attributes);
			if(foo.reversion)
				attrs = attrs.reverse();
			attrs.each(function(attr){
				if(attr.nodeValue != null && attr.nodeValue != '')
					attributes.push(' ' + attr.nodeName + '=' + (typeof attr.nodeValue == 'string' && attr.nodeValue.indexOf('"') > -1 ? "'"+attr.nodeValue+"'" : '"'+attr.nodeValue+'"'));
			})
			attributes = foo.Utils.replace(attributes.join(''), foo.entityChars);
		}
		if(closer)
			return !autoclose ? '</'+(tag ? node.nodeName : node.nodeName.toLowerCase())+'>' : '';
		else
			return '<'+ (tag || node.nodeName.toLowerCase()+attributes) + (autoclose ? ' /' : '')+'>';
	},
	
	reverseTest: function(){
		var div, container = document.createElement('div');
		container.innerHTML = '<div att1="1" att2="2"></div>';
		
		for(var i=0; i<container.childNodes.length; i++){
			if(container.childNodes[i].nodeName.toLowerCase() == 'div')
				div = container.childNodes[i];
			break;
		}
		
		for(var i=0; i<div.attributes.length; i++)
			if(div.attributes[i].nodeName.toLowerCase() == 'att1')
				return false;
			else if(div.attributes[i].nodeName.toLowerCase() == 'att2')
				return true;
	},
	
	notateAttributes: function(node){
		var attr = $A(node.attributes).select(function(e){return !!e.nodeValue;});
		var obj = {};
		attr.each(function(e,i,s){
			var value = {};
			value.toJSON = function(){return foo.parseInline(e.nodeValue);};
			obj[e.nodeName] = value;
		});
		return obj;
	},
	
	serialize: function(obj){
		if(obj.toJSON){
			return obj.toJSON();
		}else if(obj instanceof Array){
			return '['+obj.map(foo.Utils.serialize).join(', ')+']';
		}else if(typeof obj == 'object'){
			var res = $H(obj);
			res = res.map(function(e){
				return foo.Utils.serialize(e.key)+': '+foo.Utils.serialize(e.value);
			});
			return '{'+res.join(', ')+'}';
		}else if(typeof obj == 'string'){
			return '"'+foo.Utils.replace(obj, foo.escapeChars)+'"';
		}else if(obj == null){
			return 'null';
		}else if(obj.toString){
			return obj.toString();
		}
	},
	
	convertXMLToJSON: function(xml) {
		var result;
		if(xml.childNodes && xml.childNodes.length == 0){
			result = null;
		}
		else if(xml.childNodes && xml.childNodes.length == 1 && xml.childNodes[0].nodeName == "#text") {
			result = xml.childNodes[0].nodeValue;
		}else if(xml.documentElement){
			result = {};
			result[xml.documentElement.nodeName] = foo.Utils.convertXMLToJSON(xml.documentElement);
		}else{
			result = {};
			for(var i=0; i<xml.childNodes.length; i++) {
				if(result[xml.childNodes[i].nodeName]) {
					if(!(result[xml.childNodes[i].nodeName] instanceof Array))
						result[xml.childNodes[i].nodeName] = [result[xml.childNodes[i].nodeName]];
					result[xml.childNodes[i].nodeName].push(foo.Utils.convertXMLToJSON(xml.childNodes[i]));
				}else if(xml.childNodes[i].nodeName.indexOf('#') == -1)
					result[xml.childNodes[i].nodeName] = foo.Utils.convertXMLToJSON(xml.childNodes[i]);
			}
		}
		
		if(xml.attributes)
			for(var i=0; i<xml.attributes.length; i++)
				result['@'+xml.attributes[i].nodeName] = xml.attributes[i].nodeValue;
		
		return result;
	},
	
	alterTree: function(tree, path, fn) {
		var obj = tree;
		path = path.split('.');
		for(var i=0; i<path.length-1; i++) {
			if(obj[path[i]])
				obj = obj[path[i]];
			else
				return;
		}
		obj[path[path.length-1]] = fn(obj[path[path.length-1]]);
	},

	followPath: function(obj, path){
		path = path.split('.');
		for(var i=0; i<path.length; i++) {
			if(obj[path[i]])
				obj = obj[path[i]];
			else
				return false;
		}
		return obj;
	}
};

// Foo Module
foo.extend({
	modules: {},
	baseModules: [],
	addModule: function(name, mod){
		mod.name = name;
		if(mod.standard)
			foo.baseModules.push(mod);
		foo.modules[name] = mod;
	}
})

// Dataset
foo.DataSet = Class.create({
	initialize: function(){
		this.data = {};
		this.clients = [];
		this.servers = [];
		this.queue = [];
		this.ready = false;
	},
	
	get: function(){
		if(this.ready)
			return (this.data instanceof Array) ? this.data.clone() : Object.clone(this.data);
		else
			return false;
	},
	
	addDependency: function(ds) {
		if(typeof ds == 'string')
			ds = window[ds];
		
		if(ds.register)
			ds.register(this);
	},
	
	removeDependencies: function(ds) {
		if(typeof ds == 'string')
			ds = window[ds];
		
		if(ds.unregister)
			ds.unregister(this);
	},
	
	// Queue
	addQueue: function(queue) {
		if(typeof queue != 'function')
			return false;
		this.queue.push(queue);
		return true;
	},
	
	removeQueue: function(queue) {
		this.queue = this.queue.without(queue);
	},
	
	register: function(client){
		if(!this.clients.include(client)){
			this.clients.push(client);
			client.servers.push(this);
		}
	},
	
	unregister: function(client){
		this.clients = this.clients.without(client);
		client.servers = client.servers.without(this);
	},
	
	set: function(obj){
		Object.extend(this, obj);
		this.refresh();
	},
	
	update: function(){
		this.queue.invoke('apply', this);
		this.clients.invoke('refresh');
	},
	
	refresh: function(){
		this.update();
	}
});

foo.XHRDataSet = Class.create(foo.DataSet, {
	initialize: function($super, url, set){
		$super();
		
		this.url = url;
		this.path = '';
		this.period = false;
		this.method = 'get',
		this.parameters = '';
		
		if(set){
			var options = set.options;
			delete set.options;
			Object.extend(this, set);
		}
		
		this.options = {
			method: this.method,
			onSuccess: this.successHandler.bind(this),
			onFailure: this.failHandler.bind(this),
			onComplete: this.completeHandler.bind(this)
		};
		
		Object.extend(this.options, options || {});
		
		if(this.url)
			this.refresh();
	},
	
	getURL: function(){
		var url;
		try{
			url = eval(foo.parseInline(this.url));
		}catch(e){
			url = false;
		}
		return url;
	},
	
	getPath: function(){
		return eval(foo.parseInline(this.path));
	},
	
	successHandler: function(res){
		this.ready = true;
		var data = res.responseText;
		
		this.data = data;
		
		this.update();
	},
	
	failHandler: function(res) {
		this.ready = false;
		alert('Cannot load data');
	},
	
	completeHandler: function(res) {
		this.clients.invoke('loaded');
	},
	
	request: function(opt){
		Object.extend(this.options, opt);
		this.refresh();
	},
	
	update: function($super){
		$super();
		if(this.period != false){
			setTimeout(this.refresh.bind(this), this.period*1000);
		}
	},
	
	refresh: function(){
		var url = this.getURL();
		if(this.nocache)
			Object.extend(this.parameters, {_nocache: new Date().getTime()});
		if(url){
			new Ajax.Request(url, Object.extend({method: this.method,parameters: this.paremeters}, this.options));
			this.clients.invoke('update');
		}
	}
});

foo.JSONDataSet = Class.create(foo.XHRDataSet, {
	successHandler: function(res){
		this.ready = true;
		var data = res.responseText.evalJSON();
		
		this.data = data;
		if(this.path){
			try{
				this.data = eval('data.'+this.getPath());
			}catch(e){
				this.data = false;
			}
		}
		
		this.update();
	}
});

foo.XMLDataSet = Class.create(foo.XHRDataSet, {
	successHandler: function(res){
		this.ready = true;
		var data = foo.Utils.convertXMLToJSON(res.responseXML);
		
		this.data = data;
		if(this.path){
			try{
				this.data = eval('data.'+this.getPath());
			}catch(e){
				this.data = false;
			}
		}
		
		this.update();
	}
});

foo.TableDataSet = Class.create(foo.DataSet, {
	initialize: function($super){
		$super();
		this.data = [];
		this.ready = true;
		this.refresh();
	},
	
	add: function(data){
		this.data.push(data);
		this.refresh();
	},
	
	remove: function(data){
		var mark = false, res = [];
		for(var i=this.data.length-1; i>=0; i--){
			if(!mark && this.data[i] == data){
				mark = true;
				continue;
			}
			res.push(this.data[i]);
		}
		
		this.data = res.reverse();
		this.refresh();
	},
	
	removeAll: function(data){
		this.data = this.data.without(data);
	},
	
	include: function(data){
		this.data = this.data.concat(data);
		this.refresh();
	},
	
	clear: function(){
		this.data.clear();
		this.refresh();
	},
	
	get: function(fn){
		if(fn)
			return this.data.select(fn);
		else
			return this.data;
	}
});

///////Modules
foo.addModule('Base', {standard: true,
	tokens: {
		'implement': function(value){
			value.split(',').each(function(e){
				this.implement(e.strip());
			}, this);
		},
		
		'bind': function(value){
			var self = this;
			this.bindingNames = value.split(',');
			this.bindings = [];
			this.bindingNames.each(function(e, i){
				var id = e.strip();
				if(window[id] && window[id].register){
					window[id].register(self);
					if(i == 0)
						self.binding = window[id];
					else
						self['binding'+i] = window[id];
					
					self.bindings.push(window[id]);
				}
				else{
					foo.addListener('sectionInit', function(section){
						if(window[id] == section && section.register){
							section.register(self);
							if(i == 0)
								self.binding = section;
							else
								self['binding'+i] = section;
							
							self.bindings.push(section);
							self.refresh();
							return true;
						}else
							return false;
					});
				}
			});
		},
		
		'datasource': function(value){
			var args = value.split('#');
			var datasource = window[args.shift()];
			var dspath = args.join('#');
			
			if(datasource){
				this.datasource = datasource;
				this.config.dspath = dspath;
				datasource.register(this);
			}
		},
		
		'config': function(value){
			this.addListener('initialize', function(){
				this.config = Object.extend(this.config, value.evalJSON());
				return true;
			});
		}
	},
	
	statements: {
		'if': function(node, value, options){
			var nextnode = $(node).next();
			if(nextnode && (nextnode.hasAttribute(foo.ns+':else') || nextnode.hasAttribute(foo.ns+':elseif'))){
				while(node.nextSibling != nextnode)
					node.parentNode.removeChild(node.nextSibling);
			}
			return [
				'if('+value+'){',
					this.parse(node),
				'}'
			].join('\n');
		},
		
		'elseif': function(node, value, options){
			var nextnode = $(node).next();
			if(nextnode && (nextnode.hasAttribute(foo.ns+':else') || nextnode.hasAttribute(foo.ns+':elseif'))){
				if(node.nextSibling != nextnode)
					node.parentNode.removeChild(node.nextSibling);
			}
			return [
				'else if('+value+'){',
					this.parse(node),
				'}'
			].join('\n');
		},
		
		'else': function(node, value, options){
			return [
				'else{',
					this.parse(node),
				'}'
			].join('\n');
		},
		
		'each': function(node, source, options){
			var nextnode = $(node).next();
			if(nextnode && (nextnode.hasAttribute(foo.ns+':else') || nextnode.hasAttribute(foo.ns+':elseif'))){
				while(node.nextSibling != nextnode)
					node.parentNode.removeChild(node.nextSibling);
			}
			
			var useIn, useAs, temp, key, value, src;
			key = 'key'+foo.idx;
			src = 'src'+foo.idx;
			value = 'val'+foo.idx++;
			useIn = source.include(' in ');
			useAs = source.include(' as ');
			
			if(useIn){
				temp = source.split(' in ');
				key = temp.first().strip();
				source = temp.last().strip();
			}
			if(useAs){
				temp = source.split(' as ');
				value = temp.last().strip();
				source = temp.first().strip();
			}
			
			var content = this.parse(node)
			
			return [
				'var '+value+', '+key+', '+src+' = '+source+';',
				'if('+src+' && '+src+'.length > 0){',
					'for('+key+'=0;'+key+'<'+src+'.length; '+key+'++){',
						value+' = '+src+'['+key+'];',
						!useIn&&!useAs?'with('+value+'){':'',
							content,
						!useIn&&!useAs?'}':'',
					'}',
				'}else if('+src+' && typeof '+src+'.length != "number"){',
					'for('+key+' in '+src+'){',
						value+' = '+src+'['+key+'];',
						!useIn&&!useAs?'with('+value+'){':'',
							content,
						!useIn&&!useAs?'}':'',
					'}',
				'}',
			].join('\n');
		},
		
		'for': function(node, source, options){
			var nextnode = $(node).next();
			if(nextnode && (nextnode.hasAttribute(foo.ns+':else') || nextnode.hasAttribute(foo.ns+':elseif'))){
				while(node.nextSibling != nextnode)
					node.parentNode.removeChild(node.nextSibling);
			}
			
			var useIn, useAs, temp, key, value, src;
			key = 'key'+foo.idx;
			src = 'src'+foo.idx;
			value = 'val'+foo.idx++;
			useIn = source.include(' in ');
			useAs = source.include(' as ');
			
			if(useIn){
				temp = source.split(' in ');
				key = temp.first().strip();
				source = temp.last().strip();
			}
			if(useAs){
				temp = source.split(' as ');
				value = temp.last().strip();
				source = temp.first().strip();
			}
			
			var strip = node.hasAttribute(foo.ns+':strip');
			node.removeAttribute(foo.ns+':strip');
			
			var content = this.parse(node, {excludeNode:true});
			
			return [
				strip ? '' : foo.textPrint(foo.Utils.serializeTag(node)),
				'var '+value+', '+key+', '+src+' = '+source+';',
				'if('+src+' && '+src+'.length > 0){',
					'for('+key+'=0;'+key+'<'+src+'.length; '+key+'++){',
						value+' = '+src+'['+key+'];',
						!useIn&&!useAs?'with('+value+'){':'',
							content,
						!useIn&&!useAs?'}':'',
					'}',
				'}else if('+src+' && typeof '+src+' != "number"){',
					'for('+key+' in '+src+'){',
						value+' = '+src+'['+key+'];',
						!useIn&&!useAs?'with('+value+'){':'',
							content,
						!useIn&&!useAs?'}':'',
					'}',
				'}',
				strip ? '' : foo.textPrint(foo.Utils.serializeTag(node, true)),
			].join('\n');
		},
		
		'content': function(node, value, options){
			node.innerHTML = 'typeof '+foo.printTag[0]+value+' != "undefined" && '+value+' ? '+value+' : '+node.innerHTML.toJSON()+foo.printTag[1];
			return this.parse(node);
		},
		
		'assign': function(node, value, options){
			this.addFuid(node);
			var id = node.hasClassName('fuid') ? 'this.getNextFuid()' : foo.parseInline(node.id);
			return [
				'this.setElementData('+id+', '+value+');',
				this.parse(node)
			].join('\n');
		},
		
		'with': function(node, value, options){
			return [
				'with('+value+'){',
					'try{',
						this.parse(node),
					'}catch(e){}',
				'}'
			].join('\n');
		},
		
		'fuid': function(node, value, options){
			this.addFuid(node);
			var name = eval(foo.parseInline(value));
			var def = !(/\W+/.test(value)) ? 'var ' : '';
			var id = node.hasClassName('fuid') ? 'this.getNextFuid()' : foo.parseInline(node.id);
			return [
				def+name+' = '+id+';',
				this.parse(node)
			].join('\n');
		},
		
		'section-loader': function(node, value, options){
			this.addFuid(node);
			value = '['+eval(foo.parseInline(value))+']';
			var content = node.innerHTML;
			return [
				foo.parseText(foo.Utils.serializeTag(node)),
				foo.textPrint(foo.Utils.serializeTag(node, true)),
				'this.addQueue(function(){',
					'var values = '+value+';',
					'$(values[0]).observe(values[1], function(){',
						'new foo.Section('+(node.hasClassName('fuid') ? 'this.getLastFuid()' : foo.parseInline(node.id))+', '+content.toJSON()+');',
						'$(values[0]).stopObserving(values[1], arguments.callee);',
					'})',
				'});'
			].join('\n')
		},
		
		'script': function(node, value, options){
			var content = node.value || node.innerHTML;
			var scriptText = foo.parseInline(content);
			var script = eval(scriptText);
			if(value == 'instant'){
				eval(script);
			}else if(value == 'inline'){
				return script;
			}else if(value == 'queue'){
				return 'this.addQueue(new Function('+scriptText+'));';
			}else{
				var values = value.split(/\s+/);
				var first = values.shift();
				var second = values.shift();
				if(this['parseAs'+first.substring(0,1).toUpperCase()+first.substring(1)])
					this['parseAs'+first.substring(0,1).toUpperCase()+first.substring(1)](second, content);
			}
		},
		
		'strip': function(node, value, options){
			return this.parse(node, {excludeNode: true});
		}
		
	},
	
	globals: {
		getData: function(){
			if(this.config.dspath.startsWith('xpath:')){
				return []; // not implemented yet
			}else{
				this.data = this.datasource.get();
				if(this.config.dspath){
					try{
						this.data = eval('this.data.'+this.config.dspath);
					}catch(e){
						this.data = false;
					}
				}
			}
		},
		
		parseAsEvent: function(event, content){
			this.addListener(event, new Function('event', content));
		}
	},
	
	listeners: {
		update: function(){
			if(this.datasource)
				this.getData();
		}
	},
	
	initialize: function(){
		this.dataset = false;
		this.bindings = false;
	}
	
	
});

$(document).observe('contentloaded', foo);
window.onload = foo;
