var Filter = function(table){

	this.table = table; //hold Tabulator object

	this.filterList = []; //hold filter list
	this.headerFilters = {}; //hold column filters
	this.headerFilterColumns = []; //hold columns that use header filters

	this.prevHeaderFilterChangeCheck = "";
	this.prevHeaderFilterChangeCheck = "{}";

	this.changed = false; //has filtering changed since last render
};


//initialize column header filter
Filter.prototype.initializeColumn = function(column, value){
	var self = this,
	field = column.getField(),
	params;


	//handle successfull value change
	function success(value){
		var filterType = (column.modules.filter.tagType == "input" && column.modules.filter.attrType == "text") || column.modules.filter.tagType == "textarea" ? "partial" : "match",
		type = "",
		filterChangeCheck = "",
		filterFunc;

		if(typeof column.modules.filter.prevSuccess === "undefined" || column.modules.filter.prevSuccess !== value){

			column.modules.filter.prevSuccess = value;

			if(!column.modules.filter.emptyFunc(value)){
				column.modules.filter.value = value;

				switch(typeof column.definition.headerFilterFunc){
					case "string":
					if(self.filters[column.definition.headerFilterFunc]){
						type = column.definition.headerFilterFunc;
						filterFunc = function(data){
							var params = column.definition.headerFilterFuncParams || {};
							var fieldVal = column.getFieldValue(data);

							params = typeof params === "function" ? params(value, fieldVal, data) : params;

							return self.filters[column.definition.headerFilterFunc](value, fieldVal, data, params);
						};
					}else{
						console.warn("Header Filter Error - Matching filter function not found: ", column.definition.headerFilterFunc);
					}
					break;

					case "function":
					filterFunc = function(data){
						var params = column.definition.headerFilterFuncParams || {};
						var fieldVal = column.getFieldValue(data);

						params = typeof params === "function" ? params(value, fieldVal, data) : params;

						return column.definition.headerFilterFunc(value, fieldVal, data, params);
					};

					type = filterFunc;
					break;
				}

				if(!filterFunc){
					switch(filterType){
						case "partial":
						filterFunc = function(data){
							var colVal = column.getFieldValue(data);

							if(typeof colVal !== 'undefined' && colVal !== null){
								return String(colVal).toLowerCase().indexOf(String(value).toLowerCase()) > -1;
							}else{
								return false;
							}
						};
						type = "like";
						break;

						default:
						filterFunc = function(data){
							return column.getFieldValue(data) == value;
						};
						type = "=";
					}
				}

				self.headerFilters[field] = {value:value, func:filterFunc, type:type, params:params || {}};

			}else{
				delete self.headerFilters[field];
			}

			filterChangeCheck = JSON.stringify(self.headerFilters);

			if(self.prevHeaderFilterChangeCheck !== filterChangeCheck){
				self.prevHeaderFilterChangeCheck = filterChangeCheck;

				self.changed = true;
				self.table.rowManager.filterRefresh();
			}
		}

		return true;
	}

	column.modules.filter = {
		success:success,
		attrType:false,
		tagType:false,
		emptyFunc:false,
	};

	this.generateHeaderFilterElement(column);
};

Filter.prototype.generateHeaderFilterElement = function(column, initialValue, reinitialize){
	var self = this,
	success = column.modules.filter.success,
	field = column.getField(),
	filterElement, editor, editorElement, cellWrapper, typingTimer, searchTrigger, params;

	//handle aborted edit
	function cancel(){}

	if(column.modules.filter.headerElement && column.modules.filter.headerElement.parentNode){
		column.contentElement.removeChild(column.modules.filter.headerElement.parentNode);
	}

	if(field){

		//set empty value function
		column.modules.filter.emptyFunc = column.definition.headerFilterEmptyCheck || function(value){
			return !value && value !== "0" && value !== 0;
		};

		filterElement = document.createElement("div");
		filterElement.classList.add("tabulator-header-filter");

		//set column editor
		switch(typeof column.definition.headerFilter){
			case "string":
			if(self.table.modules.edit.editors[column.definition.headerFilter]){
				editor = self.table.modules.edit.editors[column.definition.headerFilter];

				if((column.definition.headerFilter === "tick" || column.definition.headerFilter === "tickCross") && !column.definition.headerFilterEmptyCheck){
					column.modules.filter.emptyFunc = function(value){
						return value !== true && value !== false;
					};
				}
			}else{
				console.warn("Filter Error - Cannot build header filter, No such editor found: ", column.definition.editor);
			}
			break;

			case "function":
			editor = column.definition.headerFilter;
			break;

			case "boolean":
			if(column.modules.edit && column.modules.edit.editor){
				editor = column.modules.edit.editor;
			}else{
				if(column.definition.formatter && self.table.modules.edit.editors[column.definition.formatter]){
					editor = self.table.modules.edit.editors[column.definition.formatter];

					if((column.definition.formatter === "tick" || column.definition.formatter === "tickCross") && !column.definition.headerFilterEmptyCheck){
						column.modules.filter.emptyFunc = function(value){
							return value !== true && value !== false;
						};
					}
				}else{
					editor = self.table.modules.edit.editors["input"];
				}
			}
			break;
		}

		if(editor){

			cellWrapper = {
				getValue:function(){
					return typeof initialValue !== "undefined" ? initialValue : "";
				},
				getField:function(){
					return column.definition.field;
				},
				getElement:function(){
					return filterElement;
				},
				getColumn:function(){
					return column.getComponent();
				},
				getRow:function(){
					return {
						normalizeHeight:function(){

						}
					};
				}
			};

			params = column.definition.headerFilterParams || {};

			params = typeof params === "function" ? params.call(self.table) : params;

			editorElement = editor.call(this.table.modules.edit, cellWrapper, function(){}, success, cancel, params);

			if(!editorElement){
				console.warn("Filter Error - Cannot add filter to " + field + " column, editor returned a value of false");
				return;
			}

			if(!(editorElement instanceof Node)){
				console.warn("Filter Error - Cannot add filter to " + field + " column, editor should return an instance of Node, the editor returned:", editorElement);
				return;
			}

			//set Placeholder Text
			if(field){
				self.table.modules.localize.bind("headerFilters|columns|" + column.definition.field, function(value){
					editorElement.setAttribute("placeholder", typeof value !== "undefined" && value ? value : self.table.modules.localize.getText("headerFilters|default"));
				});
			}else{
				self.table.modules.localize.bind("headerFilters|default", function(value){
					editorElement.setAttribute("placeholder", typeof self.column.definition.headerFilterPlaceholder !== "undefined" && self.column.definition.headerFilterPlaceholder ? self.column.definition.headerFilterPlaceholder : value);
				});
			}

			//focus on element on click
			editorElement.addEventListener("click", function(e){
				e.stopPropagation();
				editorElement.focus();
			});

			editorElement.addEventListener("focus", (e) => {
				var left = this.table.columnManager.element.scrollLeft;

				if(left !== this.table.rowManager.element.scrollLeft){
					this.table.rowManager.scrollHorizontal(left);
					this.table.columnManager.scrollHorizontal(left);
				}
			});

			//live update filters as user types
			typingTimer = false;

			searchTrigger = function(e){
				if(typingTimer){
					clearTimeout(typingTimer);
				}

				typingTimer = setTimeout(function(){
					success(editorElement.value);
				},self.table.options.headerFilterLiveFilterDelay);
			};

			column.modules.filter.headerElement = editorElement;
			column.modules.filter.attrType = editorElement.hasAttribute("type") ? editorElement.getAttribute("type").toLowerCase() : "" ;
			column.modules.filter.tagType = editorElement.tagName.toLowerCase();

			if(column.definition.headerFilterLiveFilter !== false){

				if (
					!(
						column.definition.headerFilter === 'autocomplete' ||
						column.definition.headerFilter === 'tickCross' ||
						((column.definition.editor === 'autocomplete' ||
							column.definition.editor === 'tickCross') &&
						column.definition.headerFilter === true)
						)
					) {
					editorElement.addEventListener("keyup", searchTrigger);
				editorElement.addEventListener("search", searchTrigger);


				//update number filtered columns on change
				if(column.modules.filter.attrType == "number"){
					editorElement.addEventListener("change", function(e){
						success(editorElement.value);
					});
				}

				//change text inputs to search inputs to allow for clearing of field
				if(column.modules.filter.attrType == "text" && this.table.browser !== "ie"){
					editorElement.setAttribute("type", "search");
					// editorElement.off("change blur"); //prevent blur from triggering filter and preventing selection click
				}

			}

				//prevent input and select elements from propegating click to column sorters etc
				if(column.modules.filter.tagType == "input" || column.modules.filter.tagType == "select" || column.modules.filter.tagType == "textarea"){
					editorElement.addEventListener("mousedown",function(e){
						e.stopPropagation();
					});
				}
			}

			filterElement.appendChild(editorElement);

			column.contentElement.appendChild(filterElement);

			if(!reinitialize){
				self.headerFilterColumns.push(column);
			}
		}
	}else{
		console.warn("Filter Error - Cannot add header filter, column has no field set:", column.definition.title);
	}
};

//hide all header filter elements (used to ensure correct column widths in "fitData" layout mode)
Filter.prototype.hideHeaderFilterElements = function(){
	this.headerFilterColumns.forEach(function(column){
		if(column.modules.filter && column.modules.filter.headerElement){
			column.modules.filter.headerElement.style.display = 'none';
		}
	});
};

//show all header filter elements (used to ensure correct column widths in "fitData" layout mode)
Filter.prototype.showHeaderFilterElements = function(){
	this.headerFilterColumns.forEach(function(column){
		if(column.modules.filter && column.modules.filter.headerElement){
			column.modules.filter.headerElement.style.display = '';
		}
	});
};


//programatically set focus of header filter
Filter.prototype.setHeaderFilterFocus = function(column){
	if(column.modules.filter && column.modules.filter.headerElement){
		column.modules.filter.headerElement.focus();
	}else{
		console.warn("Column Filter Focus Error - No header filter set on column:", column.getField());
	}
};

//programmatically get value of header filter
Filter.prototype.getHeaderFilterValue = function(column){
	if(column.modules.filter && column.modules.filter.headerElement){
		return column.modules.filter.headerElement.value;
	} else {
		console.warn("Column Filter Error - No header filter set on column:", column.getField());
	}
};

//programatically set value of header filter
Filter.prototype.setHeaderFilterValue = function(column, value){
	if (column){
		if(column.modules.filter && column.modules.filter.headerElement){
			this.generateHeaderFilterElement(column, value, true);
			column.modules.filter.success(value);
		}else{
			console.warn("Column Filter Error - No header filter set on column:", column.getField());
		}
	}
};

Filter.prototype.reloadHeaderFilter = function(column){
	if (column){
		if(column.modules.filter && column.modules.filter.headerElement){
			this.generateHeaderFilterElement(column, column.modules.filter.value, true);
		}else{
			console.warn("Column Filter Error - No header filter set on column:", column.getField());
		}
	}
};

//check if the filters has changed since last use
Filter.prototype.hasChanged = function(){
	var changed = this.changed;
	this.changed = false;
	return changed;
};

//set standard filters
Filter.prototype.setFilter = function(field, type, value, params){
	var self = this;

	self.filterList = [];

	if(!Array.isArray(field)){
		field = [{field:field, type:type, value:value, params:params}];
	}

	self.addFilter(field);

};

//add filter to array
Filter.prototype.addFilter = function(field, type, value, params){
	var self = this;

	if(!Array.isArray(field)){
		field = [{field:field, type:type, value:value, params:params}];
	}

	field.forEach(function(filter){

		filter = self.findFilter(filter);

		if(filter){
			self.filterList.push(filter);

			self.changed = true;
		}
	});

	if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
		this.table.modules.persistence.save("filter");
	}

};

Filter.prototype.findFilter = function(filter){
	var self = this,
	column;

	if(Array.isArray(filter)){
		return this.findSubFilters(filter);
	}

	var filterFunc = false;

	if(typeof filter.field == "function"){
		filterFunc = function(data){
			return filter.field(data, filter.type || {})// pass params to custom filter function
		};
	}else{

		if(self.filters[filter.type]){

			column = self.table.columnManager.getColumnByField(filter.field);

			if(column){
				filterFunc = function(data){
					return self.filters[filter.type](filter.value, column.getFieldValue(data), data, filter.params || {});
				};
			}else{
				filterFunc = function(data){
					return self.filters[filter.type](filter.value, data[filter.field], data, filter.params || {});
				};
			}


		}else{
			console.warn("Filter Error - No such filter type found, ignoring: ", filter.type);
		}
	}


	filter.func = filterFunc;

	return filter.func ? filter : false;
};

Filter.prototype.findSubFilters = function(filters){
	var self = this,
	output = [];

	filters.forEach(function(filter){
		filter = self.findFilter(filter);

		if(filter){
			output.push(filter);
		}
	});

	return output.length ? output : false;
};


//get all filters
Filter.prototype.getFilters = function(all, ajax){
	var output = [];

	if(all){
		output = this.getHeaderFilters();
	}

	if(ajax){
		output.forEach(function(item){
			if(typeof item.type == "function"){
				item.type = "function";
			}
		});
	}

	output = output.concat(this.filtersToArray(this.filterList, ajax));

	return output;
};

//filter to Object
Filter.prototype.filtersToArray = function(filterList, ajax){
	var output = [];

	filterList.forEach((filter) => {
		var item;

		if(Array.isArray(filter)){
			output.push(this.filtersToArray(filter, ajax));
		}else{
			item = {field:filter.field, type:filter.type, value:filter.value}

			if(ajax){
				if(typeof item.type == "function"){
					item.type = "function";
				}
			}

			output.push(item);
		}
	});

	return output;
};

//get all filters
Filter.prototype.getHeaderFilters = function(){
	var self = this,
	output = [];

	for(var key in this.headerFilters){
		output.push({field:key, type:this.headerFilters[key].type, value:this.headerFilters[key].value});
	}

	return output;
};

//remove filter from array
Filter.prototype.removeFilter = function(field, type, value){
	var self = this;

	if(!Array.isArray(field)){
		field = [{field:field, type:type, value:value}];
	}

	field.forEach(function(filter){
		var index = -1;

		if(typeof filter.field == "object"){
			index = self.filterList.findIndex(function(element){
				return filter === element;
			});
		}else{
			index = self.filterList.findIndex(function(element){
				return filter.field === element.field && filter.type === element.type  && filter.value === element.value;
			});
		}

		if(index > -1){
			self.filterList.splice(index, 1);
			self.changed = true;
		}else{
			console.warn("Filter Error - No matching filter type found, ignoring: ", filter.type);
		}

	});

	if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
		this.table.modules.persistence.save("filter");
	}

};

//clear filters
Filter.prototype.clearFilter = function(all){
	this.filterList = [];

	if(all){
		this.clearHeaderFilter();
	}

	this.changed = true;

	if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.filter){
		this.table.modules.persistence.save("filter");
	}
};

//clear header filters
Filter.prototype.clearHeaderFilter = function(){
	var self = this;

	this.headerFilters = {};
	self.prevHeaderFilterChangeCheck = "{}";

	this.headerFilterColumns.forEach(function(column){
		if(typeof column.modules.filter.value !== "undefined"){
			delete column.modules.filter.value;
		}
		column.modules.filter.prevSuccess = undefined;
		self.reloadHeaderFilter(column);
	});

	this.changed = true;
};

//search data and return matching rows
Filter.prototype.search = function (searchType, field, type, value){
	var self = this,
	activeRows = [],
	filterList = [];

	if(!Array.isArray(field)){
		field = [{field:field, type:type, value:value}];
	}

	field.forEach(function(filter){
		filter = self.findFilter(filter);

		if(filter){
			filterList.push(filter);
		}
	});

	this.table.rowManager.rows.forEach(function(row){
		var match = true;

		filterList.forEach(function(filter){
			if(!self.filterRecurse(filter, row.getData())){
				match = false;
			}
		});

		if(match){
			activeRows.push(searchType === "data" ? row.getData("data") : row.getComponent());
		}

	});

	return activeRows;
};

//filter row array
Filter.prototype.filter = function(rowList, filters){
	var self = this,
	activeRows = [],
	activeRowComponents = [];

	if(self.table.options.dataFiltering){
		self.table.options.dataFiltering.call(self.table, self.getFilters());
	}

	if(!self.table.options.ajaxFiltering && (self.filterList.length || Object.keys(self.headerFilters).length)){

		rowList.forEach(function(row){
			if(self.filterRow(row)){
				activeRows.push(row);
			}
		});

	}else{
		activeRows = rowList.slice(0);
	}

	if(self.table.options.dataFiltered){

		activeRows.forEach(function(row){
			activeRowComponents.push(row.getComponent());
		});

		self.table.options.dataFiltered.call(self.table, self.getFilters(), activeRowComponents);
	}

	return activeRows;

};

//filter individual row
Filter.prototype.filterRow = function(row, filters){
	var self = this,
	match = true,
	data = row.getData();

	self.filterList.forEach(function(filter){
		if(!self.filterRecurse(filter, data)){
			match = false;
		}
	});


	for(var field in self.headerFilters){
		if(!self.headerFilters[field].func(data)){
			match = false;
		}
	}

	return match;
};

Filter.prototype.filterRecurse = function(filter, data){
	var self = this,
	match = false;

	if(Array.isArray(filter)){
		filter.forEach(function(subFilter){
			if(self.filterRecurse(subFilter, data)){
				match = true;
			}
		});
	}else{
		match = filter.func(data);
	}

	return match;
};



//list of available filters
Filter.prototype.filters ={

	//equal to
	"=":function(filterVal, rowVal, rowData, filterParams){
		return rowVal == filterVal ? true : false;
	},

	//less than
	"<":function(filterVal, rowVal, rowData, filterParams){
		return rowVal < filterVal ? true : false;
	},

	//less than or equal to
	"<=":function(filterVal, rowVal, rowData, filterParams){
		return rowVal <= filterVal ? true : false;
	},

	//greater than
	">":function(filterVal, rowVal, rowData, filterParams){
		return rowVal > filterVal ? true : false;
	},

	//greater than or equal to
	">=":function(filterVal, rowVal, rowData, filterParams){
		return rowVal >= filterVal ? true : false;
	},

	//not equal to
	"!=":function(filterVal, rowVal, rowData, filterParams){
		return rowVal != filterVal ? true : false;
	},

	"regex":function(filterVal, rowVal, rowData, filterParams){

		if(typeof filterVal == "string"){
			filterVal = new RegExp(filterVal);
		}

		return filterVal.test(rowVal);
	},

	//contains the string
	"like":function(filterVal, rowVal, rowData, filterParams){
		if(filterVal === null || typeof filterVal === "undefined"){
			return rowVal === filterVal ? true : false;
		}else{
			if(typeof rowVal !== 'undefined' && rowVal !== null){
				return String(rowVal).toLowerCase().indexOf(filterVal.toLowerCase()) > -1;
			}
			else{
				return false;
			}
		}
	},

	//contains the keywords
	"keywords":function(filterVal, rowVal, rowData, filterParams){
		var keywords = filterVal.toLowerCase().split(typeof filterParams.separator === "undefined" ? " " : filterParams.separator),
		value = String(rowVal === null || typeof rowVal === "undefined" ? "" : rowVal).toLowerCase(),
		matches = [];

		keywords.forEach((keyword) =>{
			if(value.includes(keyword)){
				matches.push(true);
			}
		});

		return filterParams.matchAll ? matches.length === keywords.length : !!matches.length;
	},

	//starts with the string
	"starts":function(filterVal, rowVal, rowData, filterParams){
		if(filterVal === null || typeof filterVal === "undefined"){
			return rowVal === filterVal ? true : false;
		}else{
			if(typeof rowVal !== 'undefined' && rowVal !== null){
				return String(rowVal).toLowerCase().startsWith(filterVal.toLowerCase());
			}
			else{
				return false;
			}
		}
	},

	//ends with the string
	"ends":function(filterVal, rowVal, rowData, filterParams){
		if(filterVal === null || typeof filterVal === "undefined"){
			return rowVal === filterVal ? true : false;
		}else{
			if(typeof rowVal !== 'undefined' && rowVal !== null){
				return String(rowVal).toLowerCase().endsWith(filterVal.toLowerCase());
			}
			else{
				return false;
			}
		}
	},

	//in array
	"in":function(filterVal, rowVal, rowData, filterParams){
		if(Array.isArray(filterVal)){
			return filterVal.length ? filterVal.indexOf(rowVal) > -1 : true;
		}else{
			console.warn("Filter Error - filter value is not an array:", filterVal);
			return false;
		}
	},
};

Tabulator.prototype.registerModule("filter", Filter);
