/**
 * easyajax bootstrap script
 */

if(window.ajax == undefined){
	window.ajax = {};
}

var ajax = window.ajax;

if(ajax.baseURI == undefined) {
	ajax.baseURI = function(){
		var scripts = document.getElementsByTagName("SCRIPT");
		var re = /\/?js\/easyajax\.js$/;		// replace js/dombean.js to empty
		for(var i=0; i<scripts.length; i++){
			var src = scripts[i].src;
			if(src != null && re.test(src)){
				src = src.replace(re, "");
				return src;
			}
		}
		return "";
	}.call(null);
}

// ajax.lang.System
ajax.lang = {};
ajax.lang.Modules = {
	
	_modules:	{
	},
	
	_modulePaths: [],
	
	
	addModulePath:	function(path){
		this._modulePaths.push(path);
	},
	
	// require a module preloaded
	require: function(name){
		if(this.isLoaded(name)) return;
		this.load(name);
		this.loaded(name);
	},
	
	//
	loaded: function(name){
		this._modules[name] = true;
	},
	
	isLoaded: function(name){
		return this._modules[name] != null;
	},
	
	/**
	 * load from "a/B" means load a/B.js
	 */
	load: function(module){
		for(var i = 0; i<this._modulePaths.length; i++){
			try {
				ajax.lang.System.loadScriptFromURL( this.modulePaths[i] + "/" + module + ".js" );
				return;
			}
			catch(ex){
			}
		}
		throw "Cant load " + module + " from modulePath";
	}
};

ajax.lang.Modules.addModulePath( ajax.baseURI + "/js");


ajax.lang.System = {

	loadResourceFromURL: function(url){
		var request = new Ajax.Request(url, {method: "get", asynchronous: false});
		if( request.transport.readyState == 4 && request.responseIsSuccess() ){
			return request.transport.responseText;
		}
		else {
			throw "loadResource " + url + " failure status = " + request.transport.status;
		}
	},
	
	loadScriptFromURL: function(url){
		var script = this.loadResourceFromURL(url);
		window.eval(script);
	},
	
	loadStyleFromURL: function(url){
		var text = this.loadResourceFromURL(url);
		var style = document.createElement("style");
		style.appendChild(document.createTextNode(text));
		document.getElementsByTagName("HEAD")[0].appendChild(style);
	},
	
	loadXmlFromURL: function(url){
		var request = new Ajax.Request(url, {method: "get", asynchronous: false});
		if( request.transport.readyState == 4 && request.responseIsSuccess() ){
			return request.transport.responseXML;
		}
		else {
			throw "loadResource " + url + " failure status = " + request.transport.status;
		}
	},
	
	// obj.name, obj.__watchers, obj.getName(), obj.setName(value), obj.watchName(func) 
	addWatchableProperty: function(obj, name){
		
		var geter = ("get-" + name).camelize();
		var seter = ("set-" + name).camelize();
		var watcher = ("watch-" + name).camelize();
		
		obj[geter] = function(){
			return this[name];
		};
		
		obj[seter] = function(value){
			var old = this[name];
			if( old == value ) return;
			this[name] = value;
			this.__fireChanged(name, old, value);
		};			
		obj[watcher] = function(func, flag){
			if(arguments.length == 1) flag = true;
			if(this.__watchers == null) this.__watchers = {};
			if(this.__watchers[name] == null) this.__watchers[name] = [];
			
			for(var i=0; i<this.__watchers[name].length; i++){
				if(this.__watchers[name][i] == func) {
					this.__watchers[name].splice(i, 1);
				}
			}
			if(flag == true)
				this.__watchers[name].push(func);
			
		};
		if(obj.__fireChanged == null){
			obj.__fireChanged = function(name, _old, _new){
				if(this.__watchers && this.__watchers[name]){
					var listeners = this.__watchers[name];
					for(var i=0; i<listeners.length; i++)
						listeners[i].call(null, name, _old, _new);
				}
			};
		};
	},
	
	watch: function(obj, name, listener){
		var watcher = ("watch-" + name).camelize();
		if(obj[watcher]){
			obj[watcher].call(null, listener, true);
		}
	},
	
	unwatch: function(obj, name, listener){
		var watcher = ("watch-" + name).camelize();
		if(obj[watcher]){
			obj[watcher].call(null, listener, false);
		}
	}
	
};

ajax.lang.Modules.loaded("ajax.lang.Modules");
ajax.lang.Modules.loaded("ajax.lang.System");

ajax.lang.XmlUtils = {
	
	_newActiveXDomDoc: function(){
		return Try.these(
			function() { return new ActiveXObject('MSXML2.DomDocument')   },
			function() { return new ActiveXObject('Microsoft.DomDocument')},
			function() { return new ActiveXObject('MSXML.DomDocument')    },
			function() { return new ActiveXObject('MSXML3.DomDocument')   }
		) || null;
	},
		
	parseXml: function(text){
		if(window.DOMParser){
			var parser = new DOMParser();
			return parser.parseFromString(text, "text/xml");
		}

		if (window.ActiveXObject){
			var doc = this._newActiveXDomDoc();
			doc.async = false;
			doc.loadXML(text);
			if(doc.parseError.errorCode != 0) doc = null;
			return doc;
		}
		return null;
	},

	createXmlDocument: function(){
		if (document.implementation && document.implementation.createDocument) {
			var doc = document.implementation.createDocument("", "", null);

			if (doc.readyState == null) {
				doc.readyState = 1;
				doc.addEventListener("load", function () {
				   doc.readyState = 4;
				   if (typeof doc.onreadystatechange == "function")
					  doc.onreadystatechange();
				}, false);
			}
			return doc;
		}
		if (window.ActiveXObject)
			return this._newActiveXDomDoc();

		return null;
	},
	
	// return an xml document
	object2xml: function(obj, tag){
		var doc;
		var elem;
		if(typeof tag == "string"){
			doc = this.createXmlDocument();
			tag = doc.createElement(tag);
			doc.appendChild(tag);
		} else {
			doc = tag.ownerDocument;
		}

		switch (typeof obj) {
		case 'object':
			for(var name in obj){
				if(typeof obj[name] == "function") // dont serialize functions
					continue;
				if(name.charAt(0) == '$'){
					tag.setAttribute(name.substring(1), obj[name]);
					continue;
				}
				if(obj[name] instanceof Array){
					for(var i = 0; i<obj[name].length; i++) {
						elem = doc.createElement(name);
						tag.appendChild(elem);
						this.object2xml(obj[name][i], elem);
					}
				}
				else{
					elem = doc.createElement(name);
					tag.appendChild(elem);
					this.object2xml(obj[name], elem);
				}
			}
			break;
		case 'number':
		case 'string':
		case 'boolean':
			tag.appendChild( doc.createTextNode(String(obj)));
		default:
		}
		return tag.ownerDocument;
	},

	/*
	 * <root name="n"><child>some</child></root>
	 * { root: { $name: "n", child : "some" } }
	 */
	xml2object: function(xml){

		function xmlContent2object(element){
			var result = {};
			var attributes = element.attributes;
			var childNodes = element.childNodes;
			for(var i=0; i<attributes.length; i++){
				var attrName = attributes[i].name;
				var attrValue = attributes[i].value;
				result["$" + attrName] = attrValue;
			}
			var hasElement = false;
			for(var i=0; i<childNodes.length; i++){
				var node = childNodes[i];
				if(node.nodeType == 1){ // element
					hasElement = true;
					var arr = result[node.tagName];
					if(!arr)
						arr = result[node.tagName] = [];
					arr[arr.length] = xmlContent2object(node);
				}
				if(node.nodeType == 3){ // text
					if(!hasElement)
						result["@text"] = node.data;	//dont process mixed
				}
			}
			if(hasElement){
				delete result["@text"];
			}
			else {
				result = result["@text"];
			}
			// normalize result
			for(var item in result){
				var value = result[item];
				if(value instanceof Array && value.length==1){
					value = result[item] = value[0];
				}
			}

			return result;
		}

		var result = {};
		if(xml.nodeType == 9) {// document
			xml = xml.documentElement;
		}
		result[xml.tagName] = xmlContent2object(xml);
		return result;
	},
	
	DOM_ITERATE_DOWN:	0,
	DOM_ITERATE_NEXT:	1,
	DOM_ITERATE_UP:	2,
	DOM_ITERATE_STOP:	3,
	
	/**
	 * the func is like:
	 * function func(element){ return flag; }
	 */
	topDownIterateElements: function(element, func, post){

		var stack = new Array(32);
		var count = 0;
		
		if(element.nodeType == 9){
			element = element.documentElement;
		}

		var node = element;

		while(node != null){
			var flag = this.DOM_ITERATE_DOWN;
			if(node.nodeType == 1){
				flag = func.call(null, node) || this.DOM_ITERATE_DOWN;
				if(flag == this.DOM_ITERATE_STOP) return;
			}

			stack[count++] = node;

			// visit node's child
			var child = null;
			if(flag == this.DOM_ITERATE_DOWN)
				child = node.firstChild;
			if(child != null){	//continue for DOWN
				node = child;
				continue;
			} else {	// no child to process
				for(;;){					
					if(post && node.nodeType == 1){
						post.call(null, node);
					}

					// delete stack[--count]; // remove from stack
					count--;
					if(count == 0) {	// no remain
						node = null;
						break;
					}
					var parent = stack[count-1]; // uplevel
					var next = null;
					if(flag == this.DOM_ITERATE_NEXT || flag == this.DOM_ITERATE_DOWN)
						next = node.nextSibling;
					if(next != null){	// continue for next
						node = next;
						break;
					}
					else { // this is the last child of parent, return parent's sibling
						node = parent;
						continue;
					}
				}
			}
		}
	},
	
	// find the given element, since the normal DOM dont support getElementById
	getElementById: function(doc, id){
		var element = null;
		function func(e){
			if(e.getAttribute("id") == id){
				element = e;
				return ajax.lang.XmlUtils.DOM_ITERATE_STOP;
			}
		}
		this.topDownIterateElements(doc, func);
		return element;
	}
};
// hack firefox to support the .xml property for Document and Element
if(window.Document && window.Document.prototype.__defineGetter__ ){
	
	function _toxml(){
		return (new XMLSerializer()).serializeToString(this);
	}
	Document.prototype.__defineGetter__( "xml", _toxml);
	Element.prototype.__defineGetter__( "xml", _toxml);
}

ajax.lang.Modules.loaded("ajax.lang.XmlUtils");

ajax.RemoteCall = Class.create();

ajax.RemoteCall.prototype = {
	
	initialize: function(url){
		this.url = url;
		this.transport = Ajax.getTransport();
		this.asynchronous = false;
		this.callback = null;	// function(result, ex){}
		this.requestHeaders=['X-Requested-With', 'XMLHttpRequest',
    	   'X-Prototype-Version', Prototype.Version];
	},
	
	_onComplete: function(xmlhttp){
		if( xmlhttp.readyState == 4 ){
			var result;
			if (this.requestHeaders.include('Return-type')){
				var resptext=xmlhttp.responseText;
				var respObject=JSON.parse(resptext);
				if (respObject.response.exception){
					alert(respObject.response.exception);
					throw respObject.response.exception;
				}
				result=respObject.response.result;
			}else{
				var respXML = xmlhttp.responseXML;
				respObj = ajax.lang.XmlUtils.xml2object(respXML);

				if (respObj.response.exception)	{
					alert(respObj.response.exception);
					throw respObj.response.exception;
		//				this.callback.call(null, null, respObj.response.exception);
		//				return;
				}
				result= respObj.response.result;
			}

				
			
			
			var result2 = null;
			for(var idx in result){ // return the content in <result/>
				var value = result[idx];
				if(typeof value == "function") 
					continue;
				result2 = value;
				break;
			}
			if(this.callback)
				this.callback.call(null, result2, null);
		} else {
			if(this.callback)
				this.callback.call(null, null, "No Response");
		}		
	},
	setHeader:function(type){
		type='text/'+type;
		this.requestHeaders.push('Return-type',type);
	},
	call: function(method){
		var request = {
			"id": "not-used",
			"method": method,
			param : []
		};
		for(var i=1; i<arguments.length; i++){
			request.param[i-1] = arguments[i];
		}
		
		var xml = ajax.lang.XmlUtils.object2xml(request, "request");

    	   
		var options = {
			"method": "post",
			"asynchronous" : this.asynchronous,
			"requestHeaders":this.requestHeaders,
			postBody: xml
		};
		if(this.asynchronous && this.callback)
			options.onComplete = this._onComplete.bind(this);
			
		var httpReq = new Ajax.Request(this.url, options);
		
		if(!this.asynchronous){
			if( httpReq.transport.readyState == 4 && httpReq.responseIsSuccess() ){		
			var result;
			if (this.requestHeaders.include('Return-type')){
				var resptext=httpReq.transport.responseText;
				var respObject=JSON.parse(resptext);
				if (respObject.response.exception){
					alert(respObject.response.exception);
					throw respObject.response.exception;
				}
				result=respObject.response.result;
			}else{
				var respXML = httpReq.transport.responseXML;
				respObj = ajax.lang.XmlUtils.xml2object(respXML);

				if (respObj.response.exception)	{
					alert(respObj.response.exception);
					throw respObj.response.exception;
		//				this.callback.call(null, null, respObj.response.exception);
		//				return;
				}
				result= respObj.response.result;
			}
				
				// TODO make maps more clearly
				for(var idx in result){ // return the content in <result/>
					var value = result[idx];
					if(typeof value == "function") 
						continue;
					return value;
				}
				return null;
			} else if(httpReq.transport.readyState == 4 && httpReq.responseIsFailure() ){
				throw "Request Failure: status:" + httpReq.transport.status;
			} else {
				throw "Request Failure, Can't connect to service";
			}
		} else {
			return;
		}
	}		
}

ajax.lang.Modules.loaded("ajax.RemoteCall");


var debug = true;

// html2js support
ajax.Html2js = {
	
	/* return { tag:, attributes:, children } with method buildDomElement/getElementById
	 */
	CreateJsElement: function(tag, attrs, children){
		var e = {};
		e.tag = tag;
		e.attributes = [];
		e.children = [];
		e.buildDomElement = ajax.Html2js._buildDomElement;
		e.getElementById = ajax.Html2js._getElementById;
		for(var i=0; i<attrs.length; i=i+2){
			var attr = {};
			attr.name = attrs[i];
			attr.value = attrs[i+1];
			e.attributes[ e.attributes.length ] = attr;
		}
		for(var i=0; i<children.length; i++){
			e.children[ e.children.length ] = children[i];
		}
		return e;
	},
	
	_getElementById: function(id){
		for(var i=0; i<this.attributes.length; i++){
			if(this.attributes[i].name == "id" &&
				this.attributes[i].value == id) return this;
		}
		for(var i=0; i<this.children.length; i++){
			if(typeof this.children[i] == "string") continue;
			var find = this.children[i].getElementById(id);
			if(find != null) return find;
		}
		return null;
	},
			
	_buildDomElement: function(doc){
		var element = doc.createElement(this.tag);
		for(var i=0; i<this.attributes.length; i++){
			element.setAttribute( this.attributes[i].name, this.attributes[i].value );
		}
		for(var i=0; i<this.children.length; i++){
			var child = this.children[i];
			if(typeof child == "string"){
				var txt = doc.createTextNode(child);
				element.appendChild(txt);
			}
			else {
				var e = child.buildDomElement(doc);
				element.appendChild(e);
			}
		}
		return element;
	}
};

ajax.DomBean = Class.create();

function makeDebugVersion(obj, func, id){
	if(typeof obj[func] == "function"){
		var save = obj[func];
		if(id == null) id = func;
		obj[func] = function(){
			var start = new Date();
			var result = save.apply(this, arguments);
			var end = new Date();
			ajax.DomBean._LogTime(id, (end-start));
			return result;
		};
	}
};

// Debug method for log runtime profile
Object.extend(ajax.DomBean, {

	profileData: {},

	_LogTime: function(id, time){
		var item = this.profileData[id];
		if(item == null) item = this.profileData[id] = {time:0, count:0};
		item.time += time;
		item.count += 1;
	},

	_ResetLog: function(){
		this.profileData = {};
	},

	_PrintLog: function(){
		var message = "";
		for(var i in this.profileData){
			var item = this.profileData[i];
			if(item.count && item.count > 0){
				message = message + "\n" + i + ":" + item.time + "ms/" + item.count;
			}
		}
		return message;
	}

});

Object.extend(ajax.DomBean, {

	regex: /#\{.*\}/,
	classmap: {}, // classname -> constructor
	
	InitPage: function(){
//		Event.observe(window, "error", this._onError.bindAsEventListener(this) );  
		this.PreprocessTextNode(document.body);
		this.InitElement(document.documentElement);
		this.RefreshPage();

	},
	_onError:function(){
		alert('´íÎó,Çë¿´×´Ì¬À¸');
	},
	ClearPage: function(){
		ajax.DomBean.ClearElement(document.documentElement);
	},
	
	ClearElement: function(element){
		ajax.lang.XmlUtils.topDownIterateElements(
			element,
			function(e){
				if(e.domBean != null)
					e.domBean = null;
			});
	},

	// preprocess all text node which matches #{expr}
	PreprocessTextNode: function(element){
		ajax.lang.XmlUtils.topDownIterateElements(element,ajax.DomBean._PreprocessTextNode);
	},

	RefreshPage: function(){
		if(ajax.DomBean._refreshPageTimer){
			window.clearTimeout(ajax.DomBean._refreshPageTimer);
			delete ajax.DomBean._refreshPageTimer;
		}
		ajax.DomBean.RefreshElement(document.documentElement);
	},
	
	// delay the RefreshPage operation
	RefreshPageLater: function(){
		if(this._refreshPageTimer){
			return;
		}
		this._refreshPageTimer = window.setTimeout(ajax.DomBean.RefreshPage, 100);
	},

	// create domBean for element
	InitElement: function(element){
		ajax.lang.XmlUtils.topDownIterateElements(element,
			this._InitElement,
			function(e){
				if(e.domBean && e.domBean.postInitialize)
					e.domBean.postInitialize();
			});
	},

	// optimized for FF
	_InitElement: function(e){
		// avoid reinit an element
		if(e.domBean == null || e.domBean.element != e){

			var jsclass = e.getAttribute("jsclass");

			if( jsclass == "true" || jsclass == "ajax.DomBean") {
				new ajax.DomBean(e);
				return;
			} else if(jsclass == "ajax.ExprText"){	// frequece case
				new ajax.ExprText(e);
				return;
			}
			else if(jsclass != null && jsclass.length > 0){
				var cls = ajax.DomBean.classmap[jsclass];	// avoid eval much more
				if(cls == null){
					cls = eval(jsclass);
					if(typeof cls != "function"){
						throw "invalid DomBean class:" + jsclass;
					}
					else
						ajax.DomBean.classmap[jsclass] = cls;
				}
				new cls(e);
			}
		}
	},
	
	RefreshElement: function(element){
		ajax.lang.XmlUtils.topDownIterateElements(
			element, function(e){
				if(e.domBean)
					return e.domBean.applyExpression();
			}
		);

		ajax.lang.XmlUtils.topDownIterateElements(
			element,
			function(e){
				if(e.domBean && e.domBean.refresh)
					return e.domBean.refresh();
			}
		);

	},


	ApplyExpression: function(element){

		ajax.lang.XmlUtils.topDownIterateElements(
			element,
			function(e){
				if(e.domBean)
					return e.domBean.applyExpression();
			}
		);
	},

	_HasExpression: function(text){

		if(text == null || text.indexOf("#{") == -1)
			return false;
		else
			return this.regex.test(text);
	},

	// this method process text nodes of the element
	_PreprocessTextNode: function(element){

		var jsclass = null;

		// replace #{expr} text node to ajax.ExprText except for script
		var tagName = element.tagName.toUpperCase();
		switch(tagName){
			case "SCRIPT":
			case "TITLE":
			case "TEXTAREA":
			case "H1":
			case "H2":
			case "H3":
			case "H4":
			case "H5":
			case "H6":
				return;
		}

		for(var i=0; i< element.childNodes.length; i++){
			var node = element.childNodes[i];

			if(node.nodeType == 3){ // Text
				if( ajax.DomBean._HasExpression(node.data)){

					var span = document.createElement("span");
					span.setAttribute("jsclass", "ajax.ExprText");
					span.setAttribute("expr", node.data);
					node.parentNode.replaceChild(span, node);
					new ajax.ExprText(span);
				}
			}
		}
	},
	
	// return a subclass of ajax.DomBean
	SubClass: function(){
		var cls = Class.create();
		Object.extend(cls.prototype, ajax.DomBean.prototype);
		return cls;
	}

});

Event.observe(window, 'unload', ajax.DomBean.ClearPage, false);

if(debug){

//	makeDebugVersion( ajax.DomBean, "ApplyExpression", "ajax.DomBean.ApplyExpression");
//	makeDebugVersion( ajax.DomBean, "InitElement", "ajax.DomBean.InitElement");
//	makeDebugVersion( ajax.DomBean, "RefreshElement", "ajax.DomBean.RefreshElement");

	ajax.DomBean.$$RefreshPage = ajax.DomBean.RefreshPage;
	ajax.DomBean.RefreshPage = function(){
		var start = new Date();
		var result = this.$$RefreshPage.apply(this, arguments);
		var end = new Date();
		this._LogTime("RefreshPage", (end-start));
		window.status = this._PrintLog();
		return result;
	};
}

ajax.DomBean.prototype = {

	element:	null,
	
	/*
	 * each binding has format: 
	 * {
	 *		compiled: {source: string, getter: 'return expr;', setter: 'expr = value;'}, 
	 *		static: string or other, // a static value
	 *		setter: func, 	// setName(value){...}
	 *		field: fieldName //	name = value
	 * }
	 */
	bindings:	null,
	localEnv:	null,	// local saved environment
	computedEnv:	null,	// computed environment, merged with parent
	watches:	null,	// { a: { target: obj, property: name, compiled:.., static:.. } } like
	
	// Initialize an DomBean which is mapped to the dom %element%
	initialize: function(element){

		this.element = element;
		this.bindings = {};
		this.watches = {};
		this.localEnv = null;	// used when evaluate expression

		// for a cloned element, we can reuse its data
		if(element.domBean != null){
			this.bindings = element.domBean.bindings;			
		} else {
			// check each attribute which contains #{}

			var attributes = element.attributes;
			var count = attributes.length;

			for(var i=0; i<count; i++){

				// speedup for IE
				if(attributes[i].specified === false) continue;
				var attrName = attributes[i].name;
				
				var attrValue = element.getAttribute(attrName);
				var isExpr = (typeof attrValue == "string") && ajax.DomBean._HasExpression(attrValue);
				
				// name.expr just an alternative to name, synchronized to DomBean
				// name.dom	synchronized with DOM attribute
				var isDom = false, isWatch = false;
				if(attrName.length > 5 && attrName.substr(attrName.length-5) == ".expr")
					attrName = attrName.substr(0, attrName.length-5);
				else if(attrName.length > 4 && attrName.substr(attrName.length-4) == ".dom"){
					isDom = true;
					attrName = attrName.substr(0, attrName.length - 4);
				}
				
				if(attrName.substr(0,6) == "watch_"){
					isWatch = true;
					attrName = attrName.substr(6);
				}

				attrName = this._convertAttributeName(attrName);
				attrValue = this._convertValue(attrValue);

				var setter = null;
				setter = "set" + attrName.charAt(0).toUpperCase() + attrName.substr(1);

				var binding = null;	// 
				if(isExpr){
					var compiled = this._compileExpression(attrValue); 
					binding = { "compiled": compiled }; // 
				} else {	// static binding only if there is a setter or instance field
					binding = { "static": attrValue };
				}
				
				if(isWatch){
					this.watches[attrName] = binding;	//
				}
				else {
					if(isDom){	// synchronize value with element.attribute
						binding["isDom"] = true;
					}
					else if(this[setter] instanceof Function){	// synchronize value with domBean.setter
						binding["setter"] = this[setter];
					} else if(this[attrName] !== undefined){	// synchronize value with domBean.field
						binding["field"] = attrName;
					}
					this.bindings[attrName] = binding;
				}
			}
		}
		
		this._updateStaticWatches();
		
		// reset static binding
		for(var name in this.bindings){
			var binding = this.bindings[name];
			var value = binding["static"];
			if(value == null) continue;

			// static dom binding is done by browser
			if(binding.setter != null){
				binding.setter.call(this, value);
			} else if(binding.field != null){
				this[binding.field] = value;
			} 
		}

		element.domBean = this;		
	},

	// environment can be accessed in children node's expression via using env
	//
	saveEnv: function(name, value){
		if(this.localEnv == null){
			this.localEnv = {};
		}
		this.localEnv[name] = value;
		if(value == null)
			delete this.localEnv[name];

		var anyitem = false;
		for(var p in this.localEnv){
			if(typeof this.localEnv[p] != "function"){ // prototype add "extend" to object
				anyitem = true;
				break;
			}
		}
		if(anyitem == false)	// optimize, so dont iterate this node
			this.localEnv = null;

		this.computedEnv = null;	// need recalculate
	},

	getEnv: function(name){
		if(this.computedEnv == null)
			this._prepareEnv();
		return this.computedEnv[name];
	},

	getVisible: function(){
		return this.visible;
	},

	// all DomBean support visible property which controls the visibility of the element
	setVisible: function(flag){
		this.visible = flag;
		if(flag)Element.show(this.element);
		else	Element.hide(this.element);
	},
	
	applyExpression: function(){

		// clear it, need to recompute
		this.computedEnv = null;
		
		// update watches first
		this._updateWatches();

		for(var name in this.bindings){
			var binding = this.bindings[name];
			
			var value = null;
			if( binding.compiled != null ){
				value = this._evaluateCompiled(binding["compiled"]);
			} else 
				continue;	// ignore static binding
				
			if(binding.setter != null){
				binding.setter.call(this, value);
			} else if(binding.field != null){
				this[binding.field] = value;
			} else if(binding.isDom){
				if(this.element[name] !== undefined){
					this.element[name] = value;
				}
				else
					this.element.setAttribute(name, value);
			}
		}
	},
	
	_watch: function(wvar, target, property){
		var method = ("watch-" + property).camelize();
		if(target[method]){
			target[method].call(target, this._onWatch.bind(this));
		}
	},
	
	_onWatch: function(){
		
		ajax.DomBean.RefreshElement(this.element);
		
	},
	
	_updateStaticWatches: function(){		
		
		for(var name in this.watches){
			var binding = this.watches[name];			
			if( binding["static"] != null) {	// static watches a.b
				if(binding.target == null) {
					var arr = binding["static"].split(".");
					if(arr.length != 2){
						delete this.watches[name];
						continue;
					}
					var target = $(arr[0]);
					var m_watch = ("watch-" + arr[1]).camelize();
					var m_getter = ("get-" + arr[1]).camelize();
					if(target && target.domBean && target.domBean[m_watch] && target.domBean[m_getter]) {
						binding.target = target;
						binding.property = arr[1];
						//target, this._onWatch.bind(this)
//						target.domBean[m_watch].call(target.domBean, this.applyExpression.bind(this));
						target.domBean[m_watch].call(target.domBean, this._onWatch.bind(this));
						binding.getter = target.domBean[m_getter].bind(target.domBean);
					}
					else {
						delete this.watches[name];
						continue;
					}
				}
			}
		}
	},
	
	_updateWatches: function(){

		// process static watches
		this._watches = {};
		for(var name in this.watches){
			var getter = this.watches[name].getter;
			if(getter instanceof Function){
				this._watches[name] = getter.call(null);
			}
		}
		
	},

	_compileExpression: function(source){

		var status = 0;	// 0: normal 1: $_
		var items = [];
		var text = "";

		var inText = true;

		for(var i=0; i<source.length; i++){
			var ch = source.charAt(i);
			if(inText){
				switch(status){
					case 0:
						if(ch=='#') status = 1;
						else text = text.concat(ch);
						break;
					case 1:
						if(ch=='{') {
							inText= false;
							if(text.length>0)
								items[items.length] = { "text": text };
							status = 0;
							text = "";
						}
						else if(ch == '#') {
							text = text.concat('#'); // continue lookup {
						}
						else {
							text = text.concat('#').concat(ch);
							status = 0;
						}
				}
			}
			else {
				if(ch=='}'){
					inText = true;
					if(text.length>0)
						items[items.length] = { "script": text };
					text = "";
					status = 0;
				}
				else {
					text = text.concat(ch);
				}
			}
		}
		if(text.length>0)
			items[items.length] = { "text": text };

		var setEnv = this._needSetEnv(items);

		var result = { "source": source, "setEnv": setEnv};

		var body = "";
		for(var i=0; i<items.length; i++){
			if(i>0)
				body = body + "+";
			if(items[i].text != null) {
				body = body + JSON.stringify(items[i].text);
				continue;
			}
			if(items[i].script != null){
				body = body + "(" + items[i].script + ")";
			}
		}

		try {
			result.getter = new Function("env, watch", "return " + body + ";");
		}
		catch(ex){
		}

		try {
			result.setter = new Function("env, watch, value", "(" + body +") = value;");
		}
		catch(ex){
		}

		return result;
	},

	// convert put_at to putAt
	_convertAttributeName: function(name){
		name = name.toLowerCase();
		var result = "";
		var upper = false;
		for(var j=0; j<name.length; j++){
			if(name.charAt(j) == '_') {
				upper = !upper;
				continue;
			}
			if(upper){
				result = result + name.charAt(j).toUpperCase();
				upper = false;
				continue;
			} else {
				result = result + name.charAt(j);
			}
		}
		return result;
	},
	
	_convertValue: function(value){
		switch(value){
			case "true":	return true;
			case "false":	return false;
			case "null":	return null;
			case "undefined":	return undefined;
			default:{
				if(/^\s*(\d*(\.\d*)?)\s*$/.test(value)){
					return new Number(RegExp.$1);
				}
				return value;
			}
		}
	},

	_needSetEnv: function(items){
		for(var i=0; i<items.length; i++){
			var item = items[i];
			if(item.script && /(\$|_|\W)*env(\$|_\W)*/.test(item.script)){
				return true;
			}
		}
		return false;
	},

	// TODO optimize speed
	// 'this is a ' + test + ' ok?'
	_evaluateCompiled: function(compiled){

		if(compiled.setEnv)
			this._prepareEnv();

		if(compiled.getter){
			try {
				return compiled.getter.call(this.element, this.computedEnv, this._watches);
			}
			catch(ex){
				return "eval err:" + ex.toString();
			}
		}
		else {
			return "not evaluatable " + compiled.source;
		}
	},
	
	evaluateSetter: function(attr, value){
		
		if(this.bindings[attr] == null)	return;
		var compiled = this.bindings[attr].compiled;
		if(compiled == null) return;
	
		if(compiled.setEnv)
			this._prepareEnv();
			
		if(compiled.setter){
			try {
				compiled.setter.call(this.element, this.computedEnv, this._watches, value);
				return;
			}
			catch(ex){
				// TODO
			}
		}
	},

	// optimized
	_prepareEnv: function(){

		if(this.computedEnv != null)
			return this.computedEnv;

		var result = {};

		if(this.localEnv != null)
			Object.extend(result, this.localEnv);

		// search for parent
		var element = this.element.parentNode;
		while(element != null){
			if(element.domBean != null){
				Object.extend(result,  element.domBean._prepareEnv() );
				break;
			}
			element = element.parentNode;
		}

		this.computedEnv = result;
		return this.computedEnv;
	}
		
}

ajax.ExprText = Class.create();
Object.extend(ajax.ExprText.prototype, ajax.DomBean.prototype);
Object.extend(ajax.ExprText.prototype, {
	text:	null,
	// renamed from text to expr
	// it looks opera process strange for span.setAttribute("text")
	getExpr: function(){
		return this.text;
	},

	setExpr: function(text){
		if(text == null) text = "";
		this.text = text;
		var e = this.element;
		var oldChild = e.firstChild;
		if(oldChild != null && oldChild.data != text){
			e.replaceChild( document.createTextNode(text), oldChild);
		} else if(oldChild == null){
			e.insertBefore( document.createTextNode(text), null );
		}

	}

});

//if(debug){
//	makeDebugVersion( ajax.ExprText.prototype, "setText");
//}

ajax.TextInput = Class.create();
Object.extend(ajax.TextInput.prototype, ajax.DomBean.prototype);
Object.extend(ajax.TextInput.prototype, {

	initialize: function(element){
		ajax.DomBean.prototype.initialize.call(this, element);
		Event.observe(element, "change", this._changed.bind(this) );
	},

	setValue: function(text){
		if(text == null) text = "";
		this.element.value = text;
	},

	_changed: function(){
		var value = this.element.value;
		this.evaluateSetter("value", value);
	}
});

ajax.DataIteration = Class.create();
Object.extend(ajax.DataIteration.prototype, ajax.DomBean.prototype);
Object.extend(ajax.DataIteration.prototype, {
	_datasource: null,
	_variable: null,
	_insertBefore: null,
	_rows: null,	// save created rows

	initialize: function(element){
		ajax.DomBean.prototype.initialize.call(this, element);
		this._rows = [];
		this._insertBefore = element.nextSibling;
	},

	setDatasource: function(ds){
		if(ds instanceof Array) {
			this._datasource = ds;
		}
	},

	setVariable: function(name){
		this._variable = name;
	},
	
	applyExpression: function(){
		ajax.DomBean.prototype.applyExpression.call(this);
		return ajax.lang.XmlUtils.DOM_ITERATE_NEXT;	// dont apply children
	},

	refresh: function(){

		if(this._datasource == null || this._datasource instanceof Array == false)
			this._datasource = [];
			
		this.setVisible(false);

		for(var i=0; i<this._datasource.length; i++){
			var val = this._datasource[i];

			var cloneArea = null;
			if(this._rows[i] != null)
				cloneArea = this._rows[i];
			else {
				cloneArea = this.element.cloneNode(true);
				if(cloneArea.domBean == null){	// speedup for firefox
					this._copyDomBeans(this.element, cloneArea);
				}
				cloneArea.domBean = null;	// force reinit the node itself
				cloneArea.style.display = "";
				cloneArea.setAttribute("jsclass", "ajax.DomBean");
				cloneArea.setAttribute("datasource", "");
				//cloneArea.removeAttribute("id");	// avoid dupicate
				if (cloneArea.getAttribute("id")!=null)
					cloneArea.setAttribute("id",cloneArea.getAttribute("id")+i);
				ajax.DomBean.InitElement(cloneArea);
				this.element.parentNode.insertBefore(cloneArea, this._insertBefore);
				this._rows[i] = cloneArea;
			}
			
			cloneArea.domBean.saveEnv(this._variable, val);
			cloneArea.domBean.saveEnv("index", i);			
			ajax.DomBean.ApplyExpression(cloneArea); // apply after insert to access env
		}
		
		// remove unused item which is previous create
		var deleted = this._rows.splice(i, this._rows.length - i);
		for(i=0; i<deleted.length; i++){
			if(deleted[i] != null){
				ajax.DomBean.ClearElement(deleted[i]);
				deleted[i].parentNode.removeChild(deleted[i]);
			}
		}
			
		return ajax.lang.XmlUtils.DOM_ITERATE_NEXT;	// dont refresh children
	},

	// dest must be a clone of src
	_copyDomBeans: function(src, dest){
		if(src.domBean != null)
			dest.domBean = src.domBean;
		var s = src.childNodes;
		var d = dest.childNodes;
		var count = s.length;
		for(var i=0; i<count; i++){
			var si = s[i];
			var di = d[i];
			if(si.nodeType == 1)
				this._copyDomBeans(si, di);
		}
	}
});

ajax.Include = Class.create();
Object.extend(ajax.Include.prototype, ajax.DomBean.prototype);
Object.extend(ajax.Include.prototype, {
	
	url:	null,	
		
	initialize:	function(el){
		ajax.DomBean.prototype.initialize.call(this, el);
		
		var doc = null;
		try {
			doc = ajax.lang.System.loadXmlFromURL(this.url);
		}
		catch(ex){
			return this.showPrompt(ex);
		}

		var content = null;
		try {
			content = ajax.lang.XmlUtils.getElementById(doc, "TemplateBody");
			if(content == null)
				content = doc.getElementsByTagName("body")[0];
		}
		catch(ex){
		}
		
		if(content == null){
			this.showPrompt("Not a valid template: either TemplateBody or body should be defined");
			return;
		}
			
		this._savedChildren = new Array();
		while(el.firstChild != null){
			this._savedChildren[this._savedChildren.length] = el.firstChild;
			el.removeChild(el.firstChild);
		}
		
		this.element.innerHTML = content.xml;

		var insertDecorated = ajax.lang.XmlUtils.getElementById(this.element, "InsertDecorated");
		
		if(insertDecorated != null){
			while(insertDecorated.firstChild != null)
				insertDecorated.removeChild(insertDecorated.firstChild);
				
			for(var i=0; i<this._savedChildren.length; i++){
				insertDecorated.insertBefore(this._savedChildren[i], null);	
			}
		}
		
		this.element.domBean = null;
		this.element.removeAttribute("jsclass");
		
		ajax.DomBean.InitElement(this.element);
		ajax.DomBean.RefreshElement(this.element);
		
		return ajax.lang.XmlUtils.DOM_ITERATE_NEXT; // not process children
	},
	
	showPrompt: function(message){
		var prompt = document.createElement("div");
		prompt.appendChild( document.createTextNode(message) );
		this.element.appendChild(prompt);
	}
	
});

ajax.lang.Modules.loaded("ajax.DomBean");

