'use strict';

var domify = require('domify');
var _template = require('lodash.template');
var matcher = require('template-matcher');
var _uniq = require('lodash.uniq');


module.exports = function bindling(template, model) {

	var dom = (typeof template === 'object') ? template : domify(template);

	var watchers = {};

	if (!model)
	{
		return dom;
	}

	var elements = dom.querySelectorAll('*');

	Array.prototype.forEach.call(elements, function(element) {
		
		Array.prototype.forEach.call(element.childNodes, function(node) {
			if (node && node.nodeType === 3)
			{
				parse(watchers, element, null, node, node.nodeValue, model);
			}
		});

		Array.prototype.forEach.call(element.attributes, function(attr) {
			if (attr)
			{
				parse(watchers, element, attr.name, null, attr.value, model);
			}
		});
	});

	Object.observe(model, function(changes) {
		onChanges(watchers, changes);
	}, ['update']);

	return dom;
};



/* PRIVATE */

function parse(watchers, element, attrName, textNode, value, model)
{
	if (attrName && attrName.substr(0,3) === 'on-') 
	{
		return addListener(element, attrName, value, model);
	}

	if (value.indexOf('{') === -1 ) 
	{
		return;
	}

	var watch = {
		element: element,
		attrName: attrName,
		textNode: textNode,
		renderer: isOnlyOneParam(params, value) ? bindFactory(params(value)) : _template(value)
	};

	render(watch, model);

	params(value).forEach(function(param) {
		var watcher = watchers[param];

		if (!watcher)
		{
			watcher = watchers[param] = [];
		}

		watcher.push(watch);
	});
}


function onChanges(watchers, changes)
{
	changes.forEach(function(change) {
		var watcher = watchers[change.name];

		if (!watcher) 
		{
			return;
		}

		watcher.forEach(function(watch) {
			render(watch, change.object);
		});
	});
}


function render(watch, model)
{
	var content = watch.renderer(model);
	
	if (typeof content === 'string' && watch.attrName && watch.element.getAttribute(watch.attrName) !== content)
	{
		watch.element.setAttribute(watch.attrName, content);
	}
	else if (watch.attrName && watch.element[watch.attrName] !== content)
	{
		watch.element[watch.attrName] = content;
	}
	else if (watch.textNode && watch.textNode.nodeValue !== content)
	{
		watch.textNode.nodeValue = content;
	}
}


function params(value)
{
	return _uniq(matcher(value));
}


function addListener(element, attrName, value, model)
{
	var event = attrName.substring(3).toLowerCase();
	
	var open = value.indexOf('(');
	var functionName = value.substring(0, open);

	var argStr = value.substring(open+1, value.indexOf(')'));
	var args = argStr.split(',');

	element.removeAttribute(attrName);

	element.addEventListener(event, function onEvent(event) {
		
		var argValues = [];

		args.forEach(function(arg){
			if (arg)
			{
				if (arg.substr(0, 5) === 'this.')
				{
					argValues.push( element[arg.substring(5)] );
				}
				else if (arg.substr(0, 6) === 'event.')
				{
					argValues.push( event[arg.substring(6)] );
				}
				else
				{
					argValues.push( eval(arg) );
				}
			}
		});
		argValues.push(event);
		
		model[functionName].apply(model, argValues);
	});
}


function bindFactory(value)
{
	return function (model) {
		if (model[value] !== undefined)
		{
			return model[value];
		}
		else
		{
			return null;
		}
	};
}


function isOnlyOneParam(params, value)
{
	var parameters = params(value);
	var paramValue = parameters.length === 1 ? '${' + parameters[0] + '}' : '';

	return parameters.length === 1 && paramValue === value;
}
@@ -1854,35 +1854,29 @@
 if (
-typeof content === 'string'
+isSimpleType(content)
  && 
@@ -2076,32 +2076,81 @@
 !== content)%0A%09%7B%0A
+%09%09watch.element.removeAttribute(watch.attrName);%0A
 %09%09watch.element%5B
@@ -3526,8 +3526,137 @@
 value;%0A%7D
+%0A%0A%0Afunction isSimpleType(value)%0A%7B%0A%09return typeof value === 'string' %7C%7C typeof value === 'number' %7C%7C typeof value === 'boolean';%0A%7D

Clean up bound attributes with complex data