
var VnObject = require('./object');

/**
 * Base class for compilers.
 */
module.exports = new Class({
	Extends: VnObject

	,compile() {}
	,postCompile() {}
	,instantiate() {}
	,preLink() {}
	,link() {}
	,connect() {}
	,postLink() {}
	,setProperty() {}
	,free() {}

	,initialize(builder) {
		this._builder = builder;
		this._interpoler = builder._interpoler;
		this.parent();
	}

	/**
	 * Checks if the passed attribute name it's an event.
	 * 
	 * @param {String} attribute The attribute name
	 * @return {Boolean} %true if it's an event, otherwise %false
	 */
	,isEvent(attribute) {
		return /^on-\w+/.test(attribute);
	}

	,isIdentifier(value) {
		return /^[a-zA-Z_$][\w$]*$/.test(value);
	}

	/**
	 * Logs an error parsing the node.
	 * 
	 * @param {String} error The error message template
	 * @param {...} varArgs The message template arguments
	 */
	,showError() {
		this._builder.showError.apply(this._builder, arguments);
	}

	,_getMethod(value) {
		// XXX: Compatibility with old methods
		return this.isIdentifier(value)
			? value
			: this.fnExpr(value);
	}

	,bindMethod(handler, scope, isEvent) {
		// XXX: Compatibility with old methods
		if (typeof handler === 'string') {
			const method = scope.thisArg[handler];
			if (!method) {
				this.showError(`Function '${handler}' not found`);
				return undefined;
			}

			return method.bind(scope.thisArg);
		}

		return function($event) {
			let handlerScope;
			if (isEvent) {
				handlerScope = Object.create(scope.$);
				Object.assign(handlerScope, {$event});
			} else
				handlerScope = scope.$;

			handler.call(this, handlerScope);
		}.bind(scope.thisArg);
	}

	,matchExpr(value) {
		const match = /^{{(.*)}}$/.exec(value);
		if (!match) return null;
		return this.fnExpr(match[1]);
	}

	,modelExpr(expr) {
		try {
			return new Function('$scope', '$value',
				`"use strict"; $scope.${expr} = $value;`
			);
		} catch (err) {
			this.showError(`${err.message}:`, expr);
		}
	}

	,exprRegex: /^{{((?:(?!}}).)*)}}$/
	,exprRegexMulti: /{{((?:(?!}}).)*)}}/g

	,isExpr(expr, isMulti) {
		return isMulti
			? this.exprRegexMulti.test(expr)
			: this.exprRegex.test(expr);
	}

	,compileExpr(context, property, value, isMulti) {
		const exprContext = {
			context,
			property,
			value
		};

		if (isMulti) {
			let i = 0;
			const self = this;
			exprContext.exprs = [];
			exprContext.template = value.replace(this.exprRegexMulti,
				function(match, capture) {
					exprContext.exprs.push(self.fnExpr(capture));
					return `{{${i++}}}`;
				});
		} else {
			const match = this.exprRegex.exec(value);
			exprContext.expr = this.fnExpr(match[1]);
		}

		this._builder._exprContexts.push(exprContext);
	}

	,fnExpr(expr) {
		try {
			return new Function('$scope',
				`with($scope) { return ${expr}; }`
			);
		} catch (err) {
			this.showError(`${err.message}:`, expr);
		}
	}

	,_translateValue(value) {
		var chr = value.charAt(0);

		if (chr === '_')
			return _(value.substr(1));
		else if (chr === '\\' && value.charAt(1) === '_')
			return value.substr(1);
			
		return value;
	}
});
