/*
DynamicInputList takes in a parent element, a row template (in html NOT dom), a callback func and options object.

- The returned value is an array of objects, where each object's key is the name (or id) of the corresponding input/textarea/select element.

-It takes complete control of all input, textarea and select elements in the template,
 if there's a select tag, it converts it to a DropDown object.

- To pass previous options, pass {values: [{inputName: value}]}
*/
var DynamicInputList = (function(){
	var buttonsTmpl = '<div class="buttons"><icon class="add mist" title="Add"></icon><icon class="remove hot" title="Remove"></icon></div>',
		forEach = Array.prototype.forEach,
		map = Array.prototype.map;

	function Row(events, id, values, tmpl, cb, req, nums, filterFn) {
		var row = document.createElement('row'),
			self = this,
			init = false;

		row.innerHTML = tmpl + buttonsTmpl;
		this.ele = row;

		var eles = row.querySelectorAll('input, textarea, select'),
			otherEles = [],
			addBtn = row.querySelector('.add'),
			removeBtn = row.querySelector('.remove');

		forEach.call(eles, function(e) {

			if(!e.name.length && e.id.length) { e.name = e.id; }
			if(!e.name.length) return console.error("element doesn't have a name.", tmpl);
			if(e.getAttribute('required') != null) req[e.name] = true;
			if(e.getAttribute('number') != null) nums[e.name] = true;
			values[e.name] = values[e.name]; // this enforces the object to have the element name and sets older values.
			if(e.tagName === 'SELECT') {
				setupDropDown(e);
			} else if(e.tagName === 'INPUT') {
				switch(e.type.toLowerCase()) {
				case 'checkbox':
					setupCheckbox(e); break;
				case 'hidden':
					values[e.name] = getEleValue(e); break;
				default:
					events.add(e, 'change', updateValue);
				}
			} else {
				events.add(e, 'change', updateValue);
			}
		});

		function setupCheckbox(e) {
			var p = e.parentNode,
				chk = new Checkbox(p, function(v) {
					return updateValue({target:{name: e.name, value: v ? getEleValue(e) : null}});
				}, true);
			if(values[e.name] != null) chk.set();
			p.removeChild(e);
			otherEles.push(chk);
		}

		function setupDropDown(e) {
			var dd = convertSelect(e, values[e.name], function(v) {
					return updateValue({target:{name: e.name, value: v}});
				});
			otherEles.push(dd);
		}

		events.add(addBtn, 'click', function(e) {
			if(!isValidRow(values, req) || (!!filterFn && !filterFn(values))) return;
			cb(e, 'add', values);
		});

		events.add(removeBtn, 'click', function(e) {
			cb(e, 'remove', values);
		});

		if(Object.keys(values).length) setValue(values);

		function updateValue(e) {
			values[e.target.name] = getEleValue(e.target);
			if(init) cb(e, "change", values, e.target.name, values[e.target.name]);
		}

		function setValue(newValues) {
			forEach.call(eles, function(e) {
				var k = e.name, v = newValues[k];
				if(v != null) { e.value = v; values[k] = v; }
			});
		}

		this.exit = function rowExit() {
			forEach.call(otherEles, function(e) { e.exit(); });
			forEach.call(eles, function(e) { events.removeAll(e); });
			events.removeAll(addBtn);
			events.removeAll(removeBtn);
			this.ele = row = addBtn = removeBtn = self = eles = otherEles = null;
		};

		this.value = function() {
			return values;
		};

		this.setValue = setValue;

		init = true;

		function getEleValue(e) {
			if(nums[e.name]) return parseFloat(e.value);
			return e.value;
		}
	}

	function DynamicInputList(ele, tmpl, cb, opts) {
		opts = opts || {};
		var events = new EventManager(),
			allValues = [],
			req = {},
			nums = {},
			rows = [],
			active = 0,
			filterFn = typeof opts.filter === 'function' ? opts.filter : defFilter;

		this.value = value;
		this.setValue = setValue;
		this.exit = exit;

		ele.classList.add("dynamicInputList");
		if(isArray(opts.values)) setValue(opts.values);
		addRow();

		function addRow(values) {
			var id = rows.length,
				values = values || {},
				row;
			active++;
			row = new Row(events, id, values, tmpl, function(e, action, values, name, val) {
				if(action === 'remove') {
					removeRow(id);
					if(typeof opts.onRemove === 'function') opts.onRemove(e, value(), values);
				} else if(action === 'add') {
					addRow();
				} else if(action === 'change') {
					cb(e, value(), values, name, val);
				}
			}, req, nums, filterFn);

			allValues.push(values);
			rows.push(row);
			ele.appendChild(row.ele)
		}

		function removeRow(id) {
			active--;
			var row = rows[id];
			ele.removeChild(row.ele);
			row.exit(); rows[id] = allValues[id] = null;
			if(active === 0) addRow();
		}

		function setValue(vals) {
			if(!isArray(vals)) return console.error('setValue accepts an array.');
			vals.map(addRow);
		}

		function value() {
			return allValues.filter(filterFn);
		}

		function exit() {
			if(!!events) events.reset();
			if(!!rows) loop(rows, remRow);
			function remRow(k, v){ if(v && v.ele) removeChild(ele, v.ele); }
			ele = events = rows = allValues = null;
		}

		function defFilter(obj) { return isValidRow(obj, req) ? obj : null; }
	}

	function isValidRow(obj, req) {
		if(!isObject(obj)) return true;
		var keys = Object.keys(obj),
			len = keys.length,
			missing = 0;
		for(var k in obj) {
			var v = obj[k];
			if(v == null || typeof v === 'string' && v.length === 0) {
				if(req[k]) missing++;
			};
		}
		return missing === 0;
	}

	function convertSelect(e, val, cb) {
		var div = getElementFromString('<div class="select"></div>'),
			options = map.call(e.querySelectorAll('option'), function(o) { return {value: o.value || o.innerText, title: o.innerHTML}; }),
			dd = new Dropdown(div, { 'data' : options, 'initialValue': (val || options[0].value) + '', 'options' : { 'dropdownClass' : 'cool', 'menuItemClass' : 'cool' } }, cb);
		e.parentNode.replaceChild(div, e);
		return dd;
	}

	return DynamicInputList;
})();