const VnObject = require('./object');
const kebabToCamel = require('./string-util').kebabToCamel;

let scopeUid = 0;
Vn.nWatchers = 0;

module.exports = new Class({
	Extends: VnObject

	,initialize(builder, doc, objects, exprValues, thisArg, parent) {
		this.builder = builder;
		this.objects = objects;
		this.exprValues = exprValues;
		this.thisArg = thisArg;
		this.parent = parent;
		this.uid = ++scopeUid;
		this.$ = parent ? Object.create(parent.$) : {};
		Vn.nWatchers += exprValues.length;

		if (parent) {
			parent.ref();
			// XXX: Keep commented until optimized
			//parent.on('change', this.onChange, this);
			if (!thisArg) this.thisArg = parent.thisArg;
		}

		const contexts = builder._contexts;
		for (let i = 0; i < contexts.length; i++) {
			const context = contexts[i];
			objects[i] = context.compiler.instantiate(doc, context, this);
		}
	}
	
	,link(extraObjects) {
		var contextMap = this.builder._contextMap;

		for (var id in extraObjects)
			this.$[id] = extraObjects[id];
		for (var id in contextMap)
			this.$[id] = this.objects[contextMap[id]];

		const builder = this.builder;
		const contexts = builder._contexts;
		const objects = this.objects;

		for (const compiler of builder._compilers)
			compiler.preLink(this);

		for (let i = 0; i < contexts.length; i++) {
			const context = contexts[i];
			context.compiler.link(context, objects[i], objects, this);
		}

		for (let i = 0; i < contexts.length; i++) {
			const context = contexts[i];
			context.compiler.connect(context, objects[i], objects, this);
		}

		for (const compiler of builder._compilers)
			compiler.postLink(this);

		this.digest();

		for (const object of this.objects)
		if (object.assignLot)
			object.on('change', this.onChange, this);
	}

	,digest() {
		const exprContexts = this.builder._exprContexts;
		const exprValues = this.exprValues;
		const objects = this.objects;

		for (let i = 0; i < exprContexts.length; i++) {
			const exprContext = exprContexts[i];
			let newValue;

			if (exprContext.template) {
				const values = [];
				let isEmpty = false;

				for (expr of exprContext.exprs) {
					const value = this.execExpr(expr);
					if (value == null) {
						isEmpty = true;
						break;
					}
					values.push(value);
				}

				if (!isEmpty) {
					let k = 0;
					newValue = exprContext.template.replace(/{{\d+}}/g, function() {
						return values[k++];
					});
				} else
					newValue = '';
			} else 
				newValue = this.execExpr(exprContext.expr);

			if (newValue !== exprValues[i]) {
				const context = exprContext.context;
				context.compiler.setProperty(objects[context.id],
					exprContext.property, newValue);
				exprValues[i] = newValue;
			}
		}
	}

	,execExpr(expr) {
		try {
			return expr.call(this.thisArg, this.$);
		// eslint-disable-next-line no-empty
		} catch (e) {}
	}

	,onChange() {
		this.emit('change');
		this.digest();
	}

	,getMain() {
		return this.objects[this.builder._mainContext];
	}
	
	,getById(objectId) {
		if (!objectId) return null;
		return this.$[kebabToCamel(objectId)];
	}
	
	,getByTagName(tagName) {
		const tags = this.builder._tags[tagName];
	
		if (tags) {
			const arr = new Array(tags.length);
			
			for (let i = 0; i < tags.length; i++)
				arr[i] = this.objects[tags[i]];

			return arr;
		}
		
		return [];
	}

	,getHtmlId(nodeId) {
		return 'vn-'+ this.uid +'-'+ nodeId;
	}
	
	,_destroy() {
		Vn.nWatchers -= this.exprValues.length;

		for (const object of this.objects)
		if (object instanceof VnObject) {
			object.disconnectByInstance(this);
			object._destroy();
		}
		if (this.parent)
			this.parent.disconnectByInstance(this);

		VnObject.prototype._destroy.call(this);
	}
});