var Format = function(table){
	this.table = table; //hold Tabulator object
};

//initialize column formatter
Format.prototype.initializeColumn = function(column){
	column.modules.format = this.lookupFormatter(column, "");

	if(typeof column.definition.formatterPrint !== "undefined"){
		column.modules.format.print = this.lookupFormatter(column, "Print");
	}

	if(typeof column.definition.formatterClipboard !== "undefined"){
		column.modules.format.clipboard = this.lookupFormatter(column, "Clipboard");
	}

	if(typeof column.definition.formatterHtmlOutput !== "undefined"){
		column.modules.format.htmlOutput = this.lookupFormatter(column, "HtmlOutput");
	}
};


Format.prototype.lookupFormatter = function(column, type){
	var config = {params:column.definition["formatter" + type + "Params"] || {}},
	formatter = column.definition["formatter" + type];

	//set column formatter
	switch(typeof formatter){
		case "string":

		if(formatter === "tick"){
			formatter = "tickCross";

			if(typeof config.params.crossElement == "undefined"){
				config.params.crossElement = false;
			}

			console.warn("DEPRECATION WARNING - the tick formatter has been deprecated, please use the tickCross formatter with the crossElement param set to false");
		}

		if(this.formatters[formatter]){
			config.formatter = this.formatters[formatter];
		}else{
			console.warn("Formatter Error - No such formatter found: ", formatter);
			config.formatter = this.formatters.plaintext;
		}
		break;

		case "function":
		config.formatter = formatter;
		break;

		default:
		config.formatter = this.formatters.plaintext;
		break;
	}

	return config;
};

Format.prototype.cellRendered = function(cell){
	if(cell.modules.format && cell.modules.format.renderedCallback && !cell.modules.format.rendered){
		cell.modules.format.renderedCallback();
		cell.modules.format.rendered = true;
	}
};

//return a formatted value for a cell
Format.prototype.formatValue = function(cell){
	var component = cell.getComponent(),
	params = typeof cell.column.modules.format.params === "function" ? cell.column.modules.format.params(component) : cell.column.modules.format.params;

	function onRendered(callback){
		if(!cell.modules.format){
			cell.modules.format = {};
		}

		cell.modules.format.renderedCallback = callback;
		cell.modules.format.rendered = false;
	}

	return cell.column.modules.format.formatter.call(this, component, params, onRendered);
};


Format.prototype.formatExportValue = function(cell, type){
	var formatter = cell.column.modules.format[type],
	params;

	if(formatter){
		params = typeof formatter.params === "function" ? formatter.params(component) : formatter.params;

		function onRendered(callback){
			if(!cell.modules.format){
				cell.modules.format = {};
			}

			cell.modules.format.renderedCallback = callback;
			cell.modules.format.rendered = false;
		}

		return formatter.formatter.call(this, cell.getComponent(), params, onRendered);

	}else{
		return this.formatValue(cell);
	}
};

Format.prototype.sanitizeHTML = function(value){
	if(value){
		var entityMap = {
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;',
			"'": '&#39;',
			'/': '&#x2F;',
			'`': '&#x60;',
			'=': '&#x3D;'
		};

		return String(value).replace(/[&<>"'`=\/]/g, function (s) {
			return entityMap[s];
		});
	}else{
		return value;
	}
};

Format.prototype.emptyToSpace = function(value){
	return value === null || typeof value === "undefined" || value === "" ? "&nbsp;" : value;
};

//get formatter for cell
Format.prototype.getFormatter = function(formatter){
	var formatter;

	switch(typeof formatter){
		case "string":
		if(this.formatters[formatter]){
			formatter = this.formatters[formatter]
		}else{
			console.warn("Formatter Error - No such formatter found: ", formatter);
			formatter = this.formatters.plaintext;
		}
		break;

		case "function":
		formatter = formatter;
		break;

		default:
		formatter = this.formatters.plaintext;
		break;
	}

	return formatter;

};

//default data formatters
Format.prototype.formatters = {
	//plain text value
	plaintext:function(cell, formatterParams, onRendered){
		return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
	},

	//html text value
	html:function(cell, formatterParams, onRendered){
		return cell.getValue();
	},

	//multiline text area
	textarea:function(cell, formatterParams, onRendered){
		cell.getElement().style.whiteSpace = "pre-wrap";
		return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
	},

	//currency formatting
	money:function(cell, formatterParams, onRendered){
		var floatVal = parseFloat(cell.getValue()),
		number, integer, decimal, rgx;

		var decimalSym = formatterParams.decimal || ".";
		var thousandSym = formatterParams.thousand || ",";
		var symbol = formatterParams.symbol || "";
		var after = !!formatterParams.symbolAfter;
		var precision = typeof formatterParams.precision !== "undefined" ? formatterParams.precision : 2;

		if(isNaN(floatVal)){
			return this.emptyToSpace(this.sanitizeHTML(cell.getValue()));
		}

		number = precision !== false ? floatVal.toFixed(precision) : floatVal;
		number = String(number).split(".");

		integer = number[0];
		decimal = number.length > 1 ? decimalSym + number[1] : "";

		rgx = /(\d+)(\d{3})/;

		while (rgx.test(integer)){
			integer = integer.replace(rgx, "$1" + thousandSym + "$2");
		}

		return after ? integer + decimal + symbol : symbol + integer + decimal;
	},

	//clickable anchor tag
	link:function(cell, formatterParams, onRendered){
		var value = cell.getValue(),
		urlPrefix = formatterParams.urlPrefix || "",
		download = formatterParams.download,
		label = value,
		el = document.createElement("a"),
		data;

		if(formatterParams.labelField){
			data = cell.getData();
			label = data[formatterParams.labelField];
		}

		if(formatterParams.label){
			switch(typeof formatterParams.label){
				case "string":
				label = formatterParams.label;
				break;

				case "function":
				label = formatterParams.label(cell);
				break;
			}
		}

		if(label){
			if(formatterParams.urlField){
				data = cell.getData();
				value = data[formatterParams.urlField];
			}

			if(formatterParams.url){
				switch(typeof formatterParams.url){
					case "string":
					value = formatterParams.url;
					break;

					case "function":
					value = formatterParams.url(cell);
					break;
				}
			}

			el.setAttribute("href", urlPrefix + value);

			if(formatterParams.target){
				el.setAttribute("target", formatterParams.target);
			}

			if(formatterParams.download){

				if(typeof download == "function"){
					download = download(cell);
				}else{
					download = download === true ? "" : download;
				}

				el.setAttribute("download", download);
			}

			el.innerHTML = this.emptyToSpace(this.sanitizeHTML(label));

			return el;
		}else{
			return "&nbsp;";
		}
	},

	//image element
	image:function(cell, formatterParams, onRendered){
		var el = document.createElement("img"),
		    src = cell.getValue();

		if(formatterParams.urlPrefix){
			src = formatterParams.urlPrefix + cell.getValue();
		}

		if(formatterParams.urlSuffix){
			src = src + formatterParams.urlSuffix;
		}

		el.setAttribute("src", src);

		switch(typeof formatterParams.height){
			case "number":
			el.style.height = formatterParams.height + "px";
			break;

			case "string":
			el.style.height = formatterParams.height;
			break;
		}

		switch(typeof formatterParams.width){
			case "number":
			el.style.width = formatterParams.width + "px";
			break;

			case "string":
			el.style.width = formatterParams.width;
			break;
		}

		el.addEventListener("load", function(){
			cell.getRow().normalizeHeight();
		});

		return el;
	},

	//tick or cross
	tickCross:function(cell, formatterParams, onRendered){
		var value = cell.getValue(),
		element = cell.getElement(),
		empty = formatterParams.allowEmpty,
		truthy = formatterParams.allowTruthy,
		tick = typeof formatterParams.tickElement !== "undefined" ? formatterParams.tickElement : '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34  c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351  l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07  l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>',
		cross = typeof formatterParams.crossElement !== "undefined" ? formatterParams.crossElement : '<svg enable-background="new 0 0 24 24" height="14" width="14"  viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272  c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0  l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269  c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73  L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';

		if((truthy && value) || (value === true || value === "true" || value === "True" || value === 1 || value === "1")){
			element.setAttribute("aria-checked", true);
			return tick || "";
		}else{
			if(empty && (value === "null" || value === "" || value === null || typeof value === "undefined")){
				element.setAttribute("aria-checked", "mixed");
				return "";
			}else{
				element.setAttribute("aria-checked", false);
				return cross || "";
			}
		}
	},

	datetime:function(cell, formatterParams, onRendered){
		var inputFormat = formatterParams.inputFormat || "YYYY-MM-DD hh:mm:ss";
		var	outputFormat = formatterParams.outputFormat || "DD/MM/YYYY hh:mm:ss";
		var	invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
		var value = cell.getValue();

		var newDatetime = moment(value, inputFormat);

		if(newDatetime.isValid()){
			return formatterParams.timezone ? newDatetime.tz(formatterParams.timezone).format(outputFormat) : newDatetime.format(outputFormat);
		}else{

			if(invalid === true){
				return value;
			}else if(typeof invalid === "function"){
				return invalid(value);
			}else{
				return invalid;
			}
		}
	},

	datetimediff: function datetime(cell, formatterParams, onRendered) {
		var inputFormat = formatterParams.inputFormat || "YYYY-MM-DD hh:mm:ss";
		var invalid = typeof formatterParams.invalidPlaceholder !== "undefined" ? formatterParams.invalidPlaceholder : "";
		var suffix = typeof formatterParams.suffix !== "undefined" ? formatterParams.suffix : false;
		var unit = typeof formatterParams.unit !== "undefined" ? formatterParams.unit : undefined;
		var humanize = typeof formatterParams.humanize !== "undefined" ? formatterParams.humanize : false;
		var date = typeof formatterParams.date !== "undefined" ? formatterParams.date : moment();
		var value = cell.getValue();

		var newDatetime = moment(value, inputFormat);

		if (newDatetime.isValid()) {
			if(humanize){
				return moment.duration(newDatetime.diff(date)).humanize(suffix);
			}else{
				return newDatetime.diff(date, unit) + (suffix ? " " + suffix : "");
			}

		} else {

			if (invalid === true) {
				return value;
			} else if (typeof invalid === "function") {
				return invalid(value);
			} else {
				return invalid;
			}
		}
	},

	//select
	lookup: function (cell, formatterParams, onRendered) {
		var value = cell.getValue();

		if (typeof formatterParams[value] === "undefined") {
			console.warn('Missing display value for ' + value);
			return value;
		}

		return formatterParams[value];
	},

	//star rating
	star:function(cell, formatterParams, onRendered){
		var value = cell.getValue(),
		element = cell.getElement(),
		maxStars = formatterParams && formatterParams.stars ? formatterParams.stars : 5,
		stars = document.createElement("span"),
		star = document.createElementNS('http://www.w3.org/2000/svg', "svg"),
		starActive = '<polygon fill="#FFEA00" stroke="#C1AB60" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>',
		starInactive = '<polygon fill="#D2D2D2" stroke="#686868" stroke-width="37.6152" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="259.216,29.942 330.27,173.919 489.16,197.007 374.185,309.08 401.33,467.31 259.216,392.612 117.104,467.31 144.25,309.08 29.274,197.007 188.165,173.919 "/>';

		//style stars holder
		stars.style.verticalAlign = "middle";

		//style star
		star.setAttribute("width", "14");
		star.setAttribute("height", "14");
		star.setAttribute("viewBox", "0 0 512 512");
		star.setAttribute("xml:space", "preserve");
		star.style.padding = "0 1px";

		value = value && !isNaN(value) ? parseInt(value) : 0;

		value = Math.max(0, Math.min(value, maxStars));

		for(var i=1;i<= maxStars;i++){
			var nextStar = star.cloneNode(true);
			nextStar.innerHTML = i <= value ? starActive : starInactive;

			stars.appendChild(nextStar);
		}

		element.style.whiteSpace = "nowrap";
		element.style.overflow = "hidden";
		element.style.textOverflow = "ellipsis";

		element.setAttribute("aria-label", value);

		return stars;
	},

	traffic:function(cell, formatterParams, onRendered){
		var value = this.sanitizeHTML(cell.getValue()) || 0,
		el = document.createElement("span"),
		max = formatterParams && formatterParams.max ? formatterParams.max : 100,
		min = formatterParams && formatterParams.min ? formatterParams.min : 0,
		colors = formatterParams && typeof formatterParams.color !== "undefined" ? formatterParams.color : ["red", "orange", "green"],
		color = "#666666",
		percent, percentValue;

		if(isNaN(value) || typeof cell.getValue() === "undefined"){
			return;
		}

		el.classList.add("tabulator-traffic-light");

		//make sure value is in range
		percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
		percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;

		//workout percentage
		percent = (max - min) / 100;
		percentValue = Math.round((percentValue - min) / percent);

		//set color
		switch(typeof colors){
			case "string":
			color = colors;
			break;
			case "function":
			color = colors(value);
			break;
			case "object":
			if(Array.isArray(colors)){
				var unit = 100 / colors.length;
				var index = Math.floor(percentValue / unit);

				index = Math.min(index, colors.length - 1);
				index = Math.max(index, 0);
				color = colors[index];
				break;
			}
		}

		el.style.backgroundColor = color;

		return el;
	},

	//progress bar
	progress:function(cell, formatterParams, onRendered){ //progress bar
		var value = this.sanitizeHTML(cell.getValue()) || 0,
		element = cell.getElement(),
		max = formatterParams && formatterParams.max ? formatterParams.max : 100,
		min = formatterParams && formatterParams.min ? formatterParams.min : 0,
		legendAlign = formatterParams && formatterParams.legendAlign ? formatterParams.legendAlign : "center",
		percent, percentValue, color, legend, legendColor, top, left, right, bottom;

		//make sure value is in range
		percentValue = parseFloat(value) <= max ? parseFloat(value) : max;
		percentValue = parseFloat(percentValue) >= min ? parseFloat(percentValue) : min;

		//workout percentage
		percent = (max - min) / 100;
		percentValue = Math.round((percentValue - min) / percent);

		//set bar color
		switch(typeof formatterParams.color){
			case "string":
			color = formatterParams.color;
			break;
			case "function":
			color = formatterParams.color(value);
			break;
			case "object":
			if(Array.isArray(formatterParams.color)){
				var unit = 100 / formatterParams.color.length;
				var index = Math.floor(percentValue / unit);

				index = Math.min(index, formatterParams.color.length - 1);
				index = Math.max(index, 0);
				color = formatterParams.color[index];
				break;
			}
			default:
			color = "#2DC214";
		}

		//generate legend
		switch(typeof formatterParams.legend){
			case "string":
			legend = formatterParams.legend;
			break;
			case "function":
			legend = formatterParams.legend(value);
			break;
			case "boolean":
			legend = value;
			break;
			default:
			legend = false;
		}

		//set legend color
		switch(typeof formatterParams.legendColor){
			case "string":
			legendColor = formatterParams.legendColor;
			break;
			case "function":
			legendColor = formatterParams.legendColor(value);
			break;
			case "object":
			if(Array.isArray(formatterParams.legendColor)){
				var unit = 100 / formatterParams.legendColor.length;
				var index = Math.floor(percentValue / unit);

				index = Math.min(index, formatterParams.legendColor.length - 1);
				index = Math.max(index, 0);
				legendColor = formatterParams.legendColor[index];
			}
			break;
			default:
			legendColor = "#000";
		}

		element.style.minWidth = "30px";
		element.style.position = "relative";

		element.setAttribute("aria-label", percentValue);

		var barEl = document.createElement("div");
		barEl.style.display = "inline-block";
		barEl.style.position = "relative";
		barEl.style.width = percentValue + "%";
		barEl.style.backgroundColor = color;
		barEl.style.height = "100%";

		barEl.setAttribute('data-max', max);
		barEl.setAttribute('data-min', min);


		if(legend){
			var legendEl = document.createElement("div");
			legendEl.style.position = "absolute";
			legendEl.style.top = "4px";
			legendEl.style.left = 0;
			legendEl.style.textAlign = legendAlign;
			legendEl.style.width = "100%";
			legendEl.style.color = legendColor;
			legendEl.innerHTML = legend;
		}

		onRendered(function(){

			//handle custom element needed if formatter is to be included in printed/downloaded output
			if(!(cell instanceof CellComponent)){
				var holderEl = document.createElement("div");
				holderEl.style.position = "absolute";
				holderEl.style.top = "4px";
				holderEl.style.bottom = "4px";
				holderEl.style.left = "4px";
				holderEl.style.right = "4px";

				element.appendChild(holderEl);

				element = holderEl;
			}

			element.appendChild(barEl);

			if(legend){
				element.appendChild(legendEl);
			}
		});

		return "";
	},

	//background color
	color:function(cell, formatterParams, onRendered){
		cell.getElement().style.backgroundColor = this.sanitizeHTML(cell.getValue());
		return "";
	},

	//tick icon
	buttonTick:function(cell, formatterParams, onRendered){
		return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#2DC214" clip-rule="evenodd" d="M21.652,3.211c-0.293-0.295-0.77-0.295-1.061,0L9.41,14.34  c-0.293,0.297-0.771,0.297-1.062,0L3.449,9.351C3.304,9.203,3.114,9.13,2.923,9.129C2.73,9.128,2.534,9.201,2.387,9.351  l-2.165,1.946C0.078,11.445,0,11.63,0,11.823c0,0.194,0.078,0.397,0.223,0.544l4.94,5.184c0.292,0.296,0.771,0.776,1.062,1.07  l2.124,2.141c0.292,0.293,0.769,0.293,1.062,0l14.366-14.34c0.293-0.294,0.293-0.777,0-1.071L21.652,3.211z" fill-rule="evenodd"/></svg>';
	},

	//cross icon
	buttonCross:function(cell, formatterParams, onRendered){
		return '<svg enable-background="new 0 0 24 24" height="14" width="14" viewBox="0 0 24 24" xml:space="preserve" ><path fill="#CE1515" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272  c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0  l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269  c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73  L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>';
	},

	//current row number
	rownum:function(cell, formatterParams, onRendered){
		return this.table.rowManager.activeRows.indexOf(cell.getRow()._getSelf()) + 1;
	},

	//row handle
	handle:function(cell, formatterParams, onRendered){
		cell.getElement().classList.add("tabulator-row-handle");
		return "<div class='tabulator-row-handle-box'><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div><div class='tabulator-row-handle-bar'></div></div>";
	},

	responsiveCollapse:function(cell, formatterParams, onRendered){
		var self = this,
		open = false,
		el = document.createElement("div"),
		config = cell.getRow()._row.modules.responsiveLayout;

		el.classList.add("tabulator-responsive-collapse-toggle");
		el.innerHTML = "<span class='tabulator-responsive-collapse-toggle-open'>+</span><span class='tabulator-responsive-collapse-toggle-close'>-</span>";

		cell.getElement().classList.add("tabulator-row-handle");

		function toggleList(isOpen){
			var collapseEl = config.element;

			config.open = isOpen;

			if(collapseEl){

				if(config.open){
					el.classList.add("open");
					collapseEl.style.display = '';
				}else{
					el.classList.remove("open");
					collapseEl.style.display = 'none';
				}
			}
		}

		el.addEventListener("click", function(e){
			e.stopImmediatePropagation();
			toggleList(!config.open);
		});

		toggleList(config.open);

		return el;
	},

	rowSelection:function(cell, formatterParams, onRendered){
		var checkbox = document.createElement("input");

		checkbox.type = 'checkbox';

		if(this.table.modExists("selectRow", true)){

			checkbox.addEventListener("click", (e) => {
				e.stopPropagation();
			});

			if(typeof cell.getRow == 'function'){
				var row = cell.getRow();

				if(row instanceof RowComponent){

					checkbox.addEventListener("change", (e) => {
						row.toggleSelect();
					});

					checkbox.checked = row.isSelected && row.isSelected();
					this.table.modules.selectRow.registerRowSelectCheckbox(row, checkbox);
				}else{
					checkbox = "";
				}
			}else {
				checkbox.addEventListener("change", (e) => {
					if(this.table.modules.selectRow.selectedRows.length){
						this.table.deselectRow();
					}else {
						this.table.selectRow(formatterParams.rowRange);
					}
				});

				this.table.modules.selectRow.registerHeaderSelectCheckbox(checkbox);
			}
		}
		return checkbox;
	},
};

Tabulator.prototype.registerModule("format", Format);
