/**
 * Creates a object from a XML specification.
 **/
Vn.Builder = new Class
({
	 Extends: Vn.Object
	,objectMap: {}
	,tags: {}

	,loadXml: function (xmlDoc)
	{
		if (!xmlDoc)
			return false;

		var docElement = xmlDoc.documentElement;
		
		if (docElement.tagName !== 'vn')
			return false;

		this.contexts = [];
		var childs = docElement.childNodes;

		if (childs)
		for (var i = 0; i < childs.length; i++)
			this.loadNode (childs[i], null);

		this.resolveProperties ();
		delete this.contexts;
		return true;
	}
	
	,loadXmlFromNode: function (node)
	{
		this.contexts = [];
		var mainNode = this.loadNode (node);
		this.resolveProperties ();
		delete this.contexts;
		return mainNode;
	}
	
	,add: function (id, object)
	{
		this.objectMap[id] = object;
	}

	,loadNode: function (node)
	{
		var customNode;
		var htmlNode = null;
		var tagName = null;

		if (node.tagName)
			tagName = node.tagName.toLowerCase ();
		
		if (tagName === 't')
		{
			htmlNode = document.createTextNode (_(node.firstChild.textContent));
		}
		else if (!tagName)
		{
			htmlNode = document.importNode (node, false);
		}
		else if ((customNode = this.loadCustomNode (node, null)))
		{
			if (customNode instanceof Htk.Widget)
				htmlNode = customNode.getNode ();
		}
		else
		{
			htmlNode = document.createElement (tagName);

			var a = node.attributes;

			for (var i = 0; i < a.length; i++)
			{
				var nodeName = a[i].nodeName;
				var nodeValue = a[i].nodeValue;
				
				if (/^on-\w+/.test (nodeName))
				{
					var method = this.getMethod (nodeValue);
					htmlNode.addEventListener (
						nodeName.substr (3), method.bind (this.signalData));
				}			
				else if (nodeName === 'id')
				{
					this.objectMap[nodeValue] = htmlNode;
				}
				else
					htmlNode.setAttribute (nodeName,
						this.translateValue (nodeValue));
			}

			var childs = node.childNodes;
	
			if (childs)
			for (var i = 0; i < childs.length; i++)
			{
				var htmlChild = this.loadNode (childs[i]);
				
				if (htmlChild)
					htmlNode.appendChild (htmlChild);
			}
		}

		return htmlNode;
	}
	
	,loadCustomNode: function (node, parentContext)
	{
		if (!node.tagName)
			return null;
	
		var tagName = node.tagName.toLowerCase ();
		var klass = Vn.customTags[tagName];
		
		if (!klass)
			return null;

		var customNode = new klass ();
		
		if (!this.tags[tagName])
			this.tags[tagName] = [];
		
		this.tags[tagName].push (customNode);

		var context = {
			 node: node
			,parent: parentContext
			,object: customNode
			,klass: klass
		};
		this.contexts.push (context);
		
		var nodeId = node.getAttribute ('id');
		
		if (nodeId)
			this.objectMap[nodeId] = customNode;
		
		var childs = node.childNodes;
	
		if (childs)
		for (var i = 0; i < childs.length; i++)
			this.loadCustomNode (childs[i], context);
			
		return customNode;
	}

	,translateValue: function (value)
	{
		var chr = value.charAt (0);

		if (chr == '_')
			return _(value.substr (1));
		else if (chr == '\\' && value.charAt (1) == '_')
			return value.substr (1);
			
		return value;		
	}
	
	,getMethod: function (value)
	{	
		if (this.signalData)
			var methodName = 'this.signalData.'+ value;
		else
			var methodName = value;

		var method;

		try {
			method = eval (methodName);
		}
		catch (e)
		{
			method = null;
		}
		
		if (method == null)
			console.warn ('Vn.Builder: Function \'%s\' not found',
				value);

		return method;
	}
	
	,replaceFunc: function (token)
	{
		return token.charAt(1).toUpperCase ();
	}
	
	,resolveProperties: function ()
	{
		for (var i = 0; i < this.contexts.length; i++)
		{
			var c = this.contexts[i];
			var a = c.node.attributes;

			for (var j = 0; j < a.length; j++)
				this.setAttribute (c, a[j].nodeName, a[j].nodeValue);

			if (c.parent)
			{
				var parentProperty = c.node.getAttribute ('property');
			
				if (!parentProperty)
					parentProperty = c.parent.klass.Child;
	
				if (parentProperty)
					this.setProperty (c.parent, parentProperty, c.object);
				
				if (c.klass.Parent)
					this.setProperty (c, c.klass.Parent, c.parent.object);
			}

			c.object.loadXml (this, c.node);
		}
	}
	
	,setAttribute: function (c, attribute, value)
	{
		if (/^on-\w+/.test (attribute))
		{
			var method = this.getMethod (value);
			
			if (method)
				c.object.on (attribute.substr (3), method, this.signalData);
		}
		else if (!/^(id|property)$/.test (attribute))
		{
			this.setProperty (c, attribute, value)
		}
	}

	,setProperty: function (c, attribute, value)
	{
		var propName = attribute.replace (/-./g, this.replaceFunc);
		var prop = c.klass.Properties[propName];

		if (!prop)
		{
			console.warn ('Vn.Builder: Attribute \'%s\' not valid for tag \'%s\'',
				attribute, c.node.tagName);
			return;
		}
		if (!value)
			return;

		switch (prop.type)
		{
			case Boolean:
				value = (/^(true|1)$/i).test (value);
				break;
			case Number:
				value = 0 + new Number (value);
				break;
			case String:
				value = this.translateValue (value);
				break;
			case Function:
			{
				var method = this.getMethod (value);		
				value = method ? method.bind (this.signalData) : null;
				break;
			}
			default:
			if (prop.type instanceof Function)
			{
				if (typeof value == 'string')
					value = this.get (value);
				if (!(value instanceof prop.type))
					return;
			}
			else if (prop.enumType)
				value = prop.enumType[value];
		}

		if (value !== undefined)
			c.object[propName] = value;
		else
			console.warn ('Vn.Builder: Empty attribute \'%s\' on tag \'%s\'',
				attribute, c.node.tagName);
	}
	
	,setParent: function (parentBuilder)
	{
		this.parentBuilder = parentBuilder;
		
		if (parentBuilder)
			this.signalData = parentBuilder.signalData;
	}
	
	,$: function (objectId)
	{
		return this.get (objectId);
	}

	,get: function (objectId)
	{
		var object = this.objectMap[objectId];
		
		if (object)
			return object;
		if (this.parentBuilder)
			return this.parentBuilder.get (objectId);
			
		return null;
	}
	
	,getObjects: function (tagName)
	{
		if (this.tags[tagName])
			return this.tags[tagName];
			
		return [];
	}
	
	,_destroy: function ()
	{
		for (var tag in this.tags)
		{
			var objects = this.tags[tag];

			for (var i = 0; i < objects.length; i++)
				objects[i].unref ();
		}
		
		this.parent ();
	}
});