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

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

	/**
	 * Creates a object context.
	 */
	 ,compile(builder, node, tagName) {
		const klass = vnCustomTags[tagName];
		if (!klass) return null;
			
		const context = {
			klass,
			props: {},
			funcProps: {},
			objectProps: {},
			childs: [],
			events: {},
			custom: null
		};

		const a = node.attributes;
		for (let i = 0; i < a.length; i++) {
			const attribute = a[i].nodeName;
			const value = a[i].nodeValue;

			if (this.isEvent(attribute)) {
				const handler = this._getMethod(value)
				if (handler) context.events[attribute.substr(3)] = handler;
			} else if (!/^(id|property)$/.test(attribute)) {
				this.propCompile(context, node, attribute, value);
			}
		}
		
		const childNodes = node.childNodes;

		if (childNodes)
		for (let i = 0; i < childNodes.length; i++) {
			const child = childNodes[i];
			const isElement = child.nodeType === Node.ELEMENT_NODE;
			const childTagName = isElement ? child.tagName.toLowerCase() : null;
			let childContext;
			
			if (childTagName === 'pointer') {
				this._addLink(context, null, child.getAttribute('object'));
			} else if (childTagName === 'custom') {
				context.custom = child;
			} else if (childContext = builder._compile(child)) {
				let prop = isElement ? child.getAttribute('property') : null;
	
				if (prop) {
					prop = kebabToCamel(prop); 
					context.objectProps[prop] = childContext.id;
				} else
					context.childs.push(childContext.id);
			}
		}

		return context;
	}

	,propCompile(context, node, attribute, value) {
		const tagName = node.tagName;
		const propName = kebabToCamel(attribute);
		const propInfo = context.klass.Properties[propName];

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

		if (propName == 'vModel') {
			context.vModel = this.modelExpr(value);
			return;
		}

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

		if (this.isExpr(value)) {
			this.compileExpr(context, propName, value);
		} else {
			let isLink = false;
			let propError = false;
			let newValue = null;

			switch (propInfo.type) {
			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] = this._getMethod(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 && newValue !== undefined)
				context.props[propName] = newValue;
			else if (propError)
				this.showError('Attribute \'%s\' invalid for tag \'%s\'',
					attribute, tagName);
		}
	}
	
	,instantiate(doc, context, scope) {
		const object = new context.klass();
		object.setProperties(context.props);

		if (context.nodeId && object instanceof Component) {
			const id = context.nodeId;
			object.htmlId = scope.getHtmlId(id);
			object.className = '_'+ id +' '+ (object.className || '');
		}

		return object;
	}

	,setProperty(object, property, value) {
		object[property] = value;
	}

	,preLink(scope) {
		const objects = scope.objects;
		const links = this._links;

		for (let i = links.length - 1; i >= 0; i--) {
			const link = links[i];
			const object = objects[link.context.id];
			const 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(context, object, objects, scope) {
		const objectProps = context.objectProps;
		for (const prop in objectProps)
			object[prop] = objects[objectProps[prop]];

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

		const funcProps = context.funcProps;
		for (const prop in funcProps)
			object[prop] = this.bindMethod(funcProps[prop], scope);

		const events = context.events;
		for (const event in events) {
			const listener = this.bindMethod(events[event], scope);
			if (listener)
				object.on(event, listener, scope.thisArg);
		}

		if (context.vModel) {
			object.on('change', function(lot) {
				context.vModel.call(scope.thisArg, scope.$, lot.$);
				scope.digest();
			}, scope);
		}

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

	,_addLink(context, prop, objectId) {
		this._links.push({
			 context
			,prop
			,objectId: kebabToCamel(objectId)
		});
	}
	
	,_replaceFunc(token) {
		return token.charAt(1).toUpperCase();
	}
});