
var Compiler = require ('./compiler');
var Component = require ('./component');
var Type = require ('./type');
var kebabToCamel = require ('./string-util').kebabToCamel;

var specialAttrs = {
	id       : 1,
	property : 1
};

/**
 * Compiles a @Vn.Object from element tag.
 */
module.exports = new Class
({
	Extends: Compiler

	,_links: []

	,compile: function (builder, node, tagName)
	{
		var klass = vnCustomTags[tagName];
		
		if (!klass)
			return null;

		var props = {};
		var objectProps = {};
		var funcProps = {};
		var childs = [];
		var events = {};
			
		var context = {
			klass: klass,
			props: props,
			objectProps: objectProps,
			funcProps: funcProps,
			childs: childs,
			events: events,
			custom: null
		};

		var a = node.attributes;

		for (var i = 0; i < a.length; i++)
		{
			var attribute = a[i].nodeName;
			var value = a[i].nodeValue;

			if (this.isEvent (attribute))
				events[attribute.substr (3)] = value;
			else if (!specialAttrs[attribute])
				this.propCompile (context, klass, node, attribute, value);
		}
		
		var childNodes = node.childNodes;

		if (childNodes)
		for (var i = 0; i < childNodes.length; i++)
		{
			var child = childNodes[i];
			var childContext = null;
			var childTagName = null;

			if (child.nodeType === Node.ELEMENT_NODE)
				childTagName = child.tagName.toLowerCase ();
			
			if (childTagName === 'pointer')
			{
				this.addLink (context, null, child.getAttribute ('object'));
			}
			else if (childTagName === 'custom')
			{
				context.custom = child;
			}
			else if (childContext = builder._compile (child))
			{
				var prop = null;

				if (childTagName)
					prop = child.getAttribute ('property');

				if (prop)
				{
					prop = kebabToCamel (prop); 
					objectProps[prop] = childContext.id;
				}
				else
					childs.push (childContext.id);
			}
		}

		return context;
	}

	,propCompile: function (context, klass, node, attribute, value)
	{
		var isLink = false;
		var propError = false;
		var newValue = null;
		var propName = kebabToCamel (attribute);
		var propInfo = klass.Properties[propName];

		if (!propInfo)
		{
			this.showError ('Attribute \'%s\' not valid for tag \'%s\'',
				attribute, node.tagName);
			return;
		}
		if (!value)
		{
			this.showError ('Attribute \'%s\' empty on tag \'%s\'',
				attribute, node.tagName);
			return;
		}

		switch (propInfo.type)
		{
			case null:
				newValue = value;
				break;
			case Boolean:
				newValue = (/^(true|1)$/i).test (value);
				break;
			case Number:
				newValue = 0 + new Number (value);
				break;
			case String:
				newValue = this.translateValue (value);
				break;
			case Function:
				context.funcProps[propName] = value;
				break;
			case Type:
				newValue = window[value];
				break;
			default:
			if (propInfo.enumType)
				newValue = propInfo.enumType[value];
			else if (propInfo.type instanceof Function)
				isLink = true;
			else
				propError = true;
		}
		
		if (isLink)
			this.addLink (context, propName, value);
		else if (newValue != null)
			context.props[propName] = newValue;
		else if (propError)
			this.showError ('Attribute \'%s\' invalid for tag \'%s\'',
				attribute, node.tagName);
	}

	,addLink: function (context, prop, objectId)
	{
		this._links.push ({
			 context: context
			,prop: prop
			,objectId: kebabToCamel (objectId)
		});
	}

	,instantiate: function (doc, context, scope)
	{
		var object = new context.klass ();
		object.setProperties (context.props);

		if (context.nodeId && object instanceof Component)
			object.htmlId = scope.getHtmlId (context.nodeId);

		return object;
	}

	,preLink: function (scope)
	{
		var objects = scope.objects;
		var links = this._links;

		for (var i = links.length - 1; i >= 0; i--)
		{
			var link = links[i];
			var object = objects[link.context.id];
			var objectRef = scope.$[link.objectId];

			if (objectRef === undefined)
			{
				this.showError ('Referenced unexistent object with id \'%s\'',
					link.objectId);
				continue;
			}

			if (link.prop)
				object[link.prop] = objectRef;
			else
				object.appendChild (objectRef);
		}   
	}

	,link: function (context, object, objects, scope)
	{
		var objectProps = context.objectProps;
		for (var prop in objectProps)
			object[prop] = objects[objectProps[prop]];

		var childs = context.childs;
		for (var i = 0; i < childs.length; i++)
			object.appendChild (objects[childs[i]]);

		if (context.custom)
			object.loadXml (scope, context.custom);
	}

	,connect: function (context, object, objects, scope)
	{
		var funcProps = context.funcProps;
		for (var prop in funcProps)
		{
			var method = scope.getMethod (funcProps[prop], true);

			if (method)
				object[prop] = method;
		}
		
		var events = context.events;
		for (var event in events)
		{
			var method = scope.getMethod (events[event]);
			
			if (method)
				object.on (event, method, scope.signalData);
		}
	}

	,_replaceFunc: function (token)
	{
		return token.charAt(1).toUpperCase ();
	}
});
