var ReactiveData = function(table){
	this.table = table; //hold Tabulator object
	this.data = false;
	this.blocked = false; //block reactivity while performing update
	this.origFuncs = {}; // hold original data array functions to allow replacement after data is done with
	this.currentVersion = 0;
};

ReactiveData.prototype.watchData = function(data){
	var self = this,
	pushFunc, version;

	this.currentVersion ++;

	version = this.currentVersion;

	self.unwatchData();

	self.data = data;

	//override array push function
	self.origFuncs.push = data.push;

	Object.defineProperty(self.data, "push", {
		enumerable: false,
		configurable: true,
		value: function () {
			var args = Array.from(arguments);

			if(!self.blocked && version === self.currentVersion){
				args.forEach(function (arg){
					self.table.rowManager.addRowActual(arg, false);
				});
			}

			return self.origFuncs.push.apply(data, arguments);
		}
	});

	//override array unshift function
	self.origFuncs.unshift = data.unshift;

	Object.defineProperty(self.data, "unshift", {
		enumerable: false,
		configurable: true,
		value: function () {
			var args = Array.from(arguments);

			if(!self.blocked && version === self.currentVersion){
				args.forEach(function (arg){
					self.table.rowManager.addRowActual(arg, true);
				});
			}

			return self.origFuncs.unshift.apply(data, arguments);
		}
	});


	//override array shift function
	self.origFuncs.shift = data.shift;

	Object.defineProperty(self.data, "shift", {
		enumerable: false,
		configurable: true,
		value: function () {
			var row;

			if(!self.blocked && version === self.currentVersion){
				if(self.data.length){
					row = self.table.rowManager.getRowFromDataObject(self.data[0]);

					if(row){
						row.deleteActual();
					}
				}
			}

			return self.origFuncs.shift.call(data);
		}
	});

	//override array pop function
	self.origFuncs.pop = data.pop;

	Object.defineProperty(self.data, "pop", {
		enumerable: false,
		configurable: true,
		value: function () {
			var row;
			if(!self.blocked && version === self.currentVersion){
				if(self.data.length){
					row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]);

					if(row){
						row.deleteActual();
					}
				}
			}
			return self.origFuncs.pop.call(data);
		}
	});


	//override array splice function
	self.origFuncs.splice = data.splice;

	Object.defineProperty(self.data, "splice", {
		enumerable: false,
		configurable: true,
		value: function () {
			var args = Array.from(arguments),
			start = args[0] < 0 ? data.length + args[0] : args[0],
			end = args[1],
			newRows = args[2] ? args.slice(2) : false,
			startRow;

			if(!self.blocked && version === self.currentVersion){

				//add new rows
				if(newRows){
					startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false;

					if(startRow){
						newRows.forEach(function(rowData){
							self.table.rowManager.addRowActual(rowData, true, startRow, true);
						});
					}else{
						newRows = newRows.slice().reverse();

						newRows.forEach(function(rowData){
							self.table.rowManager.addRowActual(rowData, true, false, true);
						});
					}
				}

				//delete removed rows
				if(end !== 0){
					var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end);

					oldRows.forEach(function(rowData, i){
						var row = self.table.rowManager.getRowFromDataObject(rowData);

						if(row){
							row.deleteActual(i !== oldRows.length - 1);
						}
					});
				}

				if(newRows || end !== 0){
					self.table.rowManager.reRenderInPosition();
				}
			}

			return self.origFuncs.splice.apply(data, arguments);
		}
	});
};

ReactiveData.prototype.unwatchData = function(){
	if(this.data !== false){
		for(var key in this.origFuncs){
			Object.defineProperty(this.data, key, {
				enumerable: true,
				configurable:true,
				writable:true,
				value: this.origFuncs.key,
			});
		}
	}
};

ReactiveData.prototype.watchRow = function(row){
	var data = row.getData();

	this.blocked = true;

	for(var key in data){
		this.watchKey(row, data, key);
	}

	if(this.table.options.dataTree){
		this.watchTreeChildren(row);
	}

	this.blocked = false;
};

ReactiveData.prototype.watchTreeChildren = function (row){
	var self = this,
	childField = row.getData()[this.table.options.dataTreeChildField],
	origFuncs = {};

	function rebuildTree(){
		self.table.modules.dataTree.initializeRow(row);
		self.table.modules.dataTree.layoutRow(row);
		self.table.rowManager.refreshActiveData("tree", false, true);
	}

	if(childField){

		origFuncs.push = childField.push;

		Object.defineProperty(childField, "push", {
			enumerable: false,
			configurable: true,
			value: function value() {
				var result = origFuncs.push.apply(childField, arguments);

				rebuildTree();

				return result;
			}
		});

		origFuncs.unshift = childField.unshift;

		Object.defineProperty(childField, "unshift", {
			enumerable: false,
			configurable: true,
			value: function value() {
				var result =  origFuncs.unshift.apply(childField, arguments);

				rebuildTree();

				return result;
			}
		});

		origFuncs.shift = childField.shift;

		Object.defineProperty(childField, "shift", {
			enumerable: false,
			configurable: true,
			value: function value() {
				var result =  origFuncs.shift.call(childField);

				rebuildTree();

				return result;
			}
		});

		origFuncs.pop = childField.pop;

		Object.defineProperty(childField, "pop", {
			enumerable: false,
			configurable: true,
			value: function value() {
				var result =  origFuncs.pop.call(childField);

				rebuildTree();

				return result;
			}
		});

		origFuncs.splice = childField.splice;

		Object.defineProperty(childField, "splice", {
			enumerable: false,
			configurable: true,
			value: function value() {
				var result =  origFuncs.splice.apply(childField, arguments);

				rebuildTree();

				return result;
			}
		});
	}
};

ReactiveData.prototype.watchKey = function(row, data, key){
	var self = this,
	props = Object.getOwnPropertyDescriptor(data, key),
	value = data[key],
	version = this.currentVersion;

	Object.defineProperty(data, key, {
		set: function(newValue){
			value = newValue;
			if(!self.blocked && version === self.currentVersion){
				var update = {};
				update[key] = newValue;
				row.updateData(update);
			}

			if(props.set){
				props.set(newValue);
			}
		},
		get:function(){

			if(props.get){
				props.get();
			}

			return value;
		}
	});
};

ReactiveData.prototype.unwatchRow = function(row){
	var data = row.getData();

	for(var key in data){
		Object.defineProperty(data, key, {
			value:data[key],
		});
	}
};

ReactiveData.prototype.block = function(){
	this.blocked = true;
};

ReactiveData.prototype.unblock = function(){
	this.blocked = false;
};

Tabulator.prototype.registerModule("reactiveData", ReactiveData);



