
var ExportRow = function(type, columns, component, indent){
	this.type = type;
	this.columns = columns;
	this.component = component || false;
	this.indent = indent || 0;
};

var ExportColumn = function(value, component, width, height, depth){
	this.value = value;
	this.component = component || false;
	this.width = width;
	this.height = height;
	this.depth = depth;
};

var Export = function(table){
	this.table = table; //hold Tabulator object
	this.config = {};
	this.cloneTableStyle = true;
	this.colVisProp = "";
};

Export.prototype.generateExportList = function(config, style, range, colVisProp){
	this.cloneTableStyle = style;
	this.config = config || {};
	this.colVisProp = colVisProp;

	var headers = this.config.columnHeaders !== false ? this.headersToExportRows(this.generateColumnGroupHeaders()) : [];
	var body = this.bodyToExportRows(this.rowLookup(range));

	return headers.concat(body);
};

Export.prototype.genereateTable = function(config, style, range, colVisProp){
	var list = this.generateExportList(config, style, range, colVisProp);

	return this.genereateTableElement(list);
};

Export.prototype.rowLookup = function(range){
	var rows = [];

	if(typeof range == "function"){
		range.call(this.table).forEach((row) =>{
			row = this.table.rowManager.findRow(row);

			if(row){
				rows.push(row);
			}
		});
	}else{
		switch(range){
			case true:
			case "visible":
			rows = this.table.rowManager.getVisibleRows(true);
			break;

			case "all":
			rows = this.table.rowManager.rows;
			break;

			case "selected":
			rows = this.table.modules.selectRow.selectedRows;
			break;

			case "active":
			default:
			if(this.table.options.pagination){
				rows = this.table.rowManager.getDisplayRows(this.table.rowManager.displayRows.length - 2);
			}else{
				rows = this.table.rowManager.getDisplayRows();
			}
		}
	}

	return Object.assign([], rows);
};


Export.prototype.generateColumnGroupHeaders = function(){
	var output = [];

	var columns = this.config.columnGroups !== false ? this.table.columnManager.columns : this.table.columnManager.columnsByIndex;

	columns.forEach((column) => {
		var colData = this.processColumnGroup(column);

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

	return output;
};

Export.prototype.processColumnGroup = function(column){
	var subGroups = column.columns,
	maxDepth = 0,
	title = column.definition["title" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))] || column.definition.title;

	var groupData = {
		title:title,
		column:column,
		depth:1,
	};

	if(subGroups.length){
		groupData.subGroups = [];
		groupData.width = 0;

		subGroups.forEach((subGroup) => {
			var subGroupData = this.processColumnGroup(subGroup);

			if(subGroupData){
				groupData.width += subGroupData.width;
				groupData.subGroups.push(subGroupData);

				if(subGroupData.depth > maxDepth){
					maxDepth = subGroupData.depth;
				}
			}
		});

		groupData.depth += maxDepth;

		if(!groupData.width){
			return false;
		}
	}else{
		if(this.columnVisCheck(column)){
			groupData.width = 1;
		}else{
			return false;
		}
	}

	return groupData;
};

Export.prototype.columnVisCheck = function(column){
	return column.definition[this.colVisProp] !== false && (column.visible || (!column.visible && column.definition[this.colVisProp]));
};


Export.prototype.headersToExportRows = function(columns){
	var headers = [],
	headerDepth = 0,
	exportRows = [];

	function parseColumnGroup(column, level){

		var depth = headerDepth - level;

		if(typeof headers[level] === "undefined"){
			headers[level] = [];
		}

		column.height = column.subGroups ? 1 : (depth - column.depth) + 1;

		headers[level].push(column);

		if(column.height > 1){
			for(let i = 1; i < column.height; i ++){

				if(typeof headers[level + i] === "undefined"){
					headers[level + i] = [];
				}

				headers[level + i].push(false);
			}
		}

		if(column.width > 1){
			for(let i = 1; i < column.width; i ++){
				headers[level].push(false);
			}
		}

		if(column.subGroups){
			column.subGroups.forEach(function(subGroup){
				parseColumnGroup(subGroup, level+1);
			});
		}
	}

	//calculate maximum header debth
	columns.forEach(function(column){
		if(column.depth > headerDepth){
			headerDepth = column.depth;
		}
	});

	columns.forEach(function(column){
		parseColumnGroup(column,0);
	});

	headers.forEach((header) => {
		var columns = [];

		header.forEach((col) => {
			if(col){
				columns.push(new ExportColumn(col.title, col.column.getComponent(), col.width, col.height, col.depth));
			}else{
				columns.push(null);
			}
		});

		exportRows.push(new ExportRow("header", columns));
	});

	return exportRows;
};


Export.prototype.bodyToExportRows = function(rows){

	var columns = [];
	var exportRows = [];

	this.table.columnManager.columnsByIndex.forEach((column) => {
		if (this.columnVisCheck(column)) {
			columns.push(column.getComponent());
		}
	});

	if(this.config.columnCalcs !== false && this.table.modExists("columnCalcs")){
		if(this.table.modules.columnCalcs.topInitialized){
			rows.unshift(this.table.modules.columnCalcs.topRow);
		}

		if(this.table.modules.columnCalcs.botInitialized){
			rows.push(this.table.modules.columnCalcs.botRow);
		}
	}

	rows = rows.filter((row) => {
		switch(row.type){
			case "group":
			return this.config.rowGroups !== false;
			break;

			case "calc":
			return this.config.columnCalcs !== false;
			break;

			case "row":
			return !(this.table.options.dataTree && this.config.dataTree === false && row.modules.dataTree.parent);
			break;
		}

		return true;
	});


	rows.forEach((row, i) => {
		var rowData = row.getData(this.colVisProp);
		var exportCols = [];
		var indent = 0;

		switch(row.type){
			case "group":
			indent = row.level;
			exportCols.push(new ExportColumn(row.key, row.getComponent(), columns.length, 1));
			break;

			case "calc" :
			case "row" :
			columns.forEach((col) => {
				exportCols.push(new ExportColumn(col._column.getFieldValue(rowData), col, 1, 1));
			});

			if(this.table.options.dataTree && this.config.dataTree !== false){
				indent = row.modules.dataTree.index;
			}
			break;
		}

		exportRows.push(new ExportRow(row.type, exportCols, row.getComponent(), indent));
	});

	return exportRows;
};


Export.prototype.genereateTableElement = function(list){
	var table = document.createElement("table"),
	headerEl = document.createElement("thead"),
	bodyEl = document.createElement("tbody"),
	styles = this.lookupTableStyles(),
	rowFormatter = this.table.options["rowFormatter" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))],
	setup = {};

	setup.rowFormatter = rowFormatter !== null ? rowFormatter : this.table.options.rowFormatter;

	if(this.table.options.dataTree &&this.config.dataTree !== false && this.table.modExists("columnCalcs")){
		setup.treeElementField = this.table.modules.dataTree.elementField;
	}

	//assign group header formatter
	setup.groupHeader = this.table.options["groupHeader" + (this.colVisProp.charAt(0).toUpperCase() + this.colVisProp.slice(1))];

	if(setup.groupHeader && !Array.isArray(setup.groupHeader)){
		setup.groupHeader = [setup.groupHeader];
	}

	table.classList.add("tabulator-print-table");

	this.mapElementStyles(this.table.columnManager.getHeadersElement(), headerEl, ["border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);


	if(list.length > 1000){
		console.warn("It may take a long time to render an HTML table with more than 1000 rows");
	}

	list.forEach((row, i) => {
		switch(row.type){
			case "header":
			headerEl.appendChild(this.genereateHeaderElement(row, setup, styles));
			break;

			case "group":
			bodyEl.appendChild(this.genereateGroupElement(row, setup, styles));
			break;

			case "calc":
			bodyEl.appendChild(this.genereateCalcElement(row, setup, styles));
			break;

			case "row":
			let rowEl = this.genereateRowElement(row, setup, styles);
			this.mapElementStyles(((i % 2) && styles.evenRow) ? styles.evenRow : styles.oddRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
			bodyEl.appendChild(rowEl);
			break;
		}
	});

	if(headerEl.innerHTML){
		table.appendChild(headerEl);
	}

	table.appendChild(bodyEl);


	this.mapElementStyles(this.table.element, table, ["border-top", "border-left", "border-right", "border-bottom"]);
	return table;
};

Export.prototype.lookupTableStyles = function(){
	var styles = {};

	//lookup row styles
	if(this.cloneTableStyle && window.getComputedStyle){
		styles.oddRow = this.table.element.querySelector(".tabulator-row-odd:not(.tabulator-group):not(.tabulator-calcs)");
		styles.evenRow = this.table.element.querySelector(".tabulator-row-even:not(.tabulator-group):not(.tabulator-calcs)");
		styles.calcRow = this.table.element.querySelector(".tabulator-row.tabulator-calcs");
		styles.firstRow = this.table.element.querySelector(".tabulator-row:not(.tabulator-group):not(.tabulator-calcs)");
		styles.firstGroup = this.table.element.getElementsByClassName("tabulator-group")[0];

		if(styles.firstRow){
			styles.styleCells = styles.firstRow.getElementsByClassName("tabulator-cell");
			styles.firstCell = styles.styleCells[0];
			styles.lastCell = styles.styleCells[styles.styleCells.length - 1];
		}
	}

	return styles;
};

Export.prototype.genereateHeaderElement = function(row, setup, styles){
	var rowEl = document.createElement("tr");

	row.columns.forEach((column) => {
		if(column){
			var cellEl = document.createElement("th");
			var classNames = column.component._column.definition.cssClass ? column.component._column.definition.cssClass.split(" ") : [];

			cellEl.colSpan = column.width;
			cellEl.rowSpan = column.height;

			cellEl.innerHTML = column.value;

			if(this.cloneTableStyle){
				cellEl.style.boxSizing = "border-box";
			}

			classNames.forEach(function(className) {
				cellEl.classList.add(className);
			});

			this.mapElementStyles(column.component.getElement(), cellEl, ["text-align", "border-top", "border-left", "border-right", "border-bottom", "background-color", "color", "font-weight", "font-family", "font-size"]);
			this.mapElementStyles(column.component._column.contentElement, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);

			if(column.component._column.visible){
				this.mapElementStyles(column.component.getElement(), cellEl, ["width"]);
			}else{
				if(column.component._column.definition.width){
					cellEl.style.width = column.component._column.definition.width + "px";
				}
			}

			if(column.component._column.parent){
				this.mapElementStyles(column.component._column.parent.groupElement, cellEl, ["border-top"]);
			}

			rowEl.appendChild(cellEl);
		}
	});

	return rowEl;
};

Export.prototype.genereateGroupElement = function(row, setup, styles){

	var rowEl = document.createElement("tr"),
	cellEl = document.createElement("td"),
	group = row.columns[0];

	rowEl.classList.add("tabulator-print-table-row");

	if(setup.groupHeader && setup.groupHeader[row.indent]){
		group.value = setup.groupHeader[row.indent](group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
	}else{
		if(setup.groupHeader === false){
			group.value = group.value;
		}else{
			group.value = row.component._group.generator(group.value, row.component._group.getRowCount(), row.component._group.getData(), row.component);
		}
	}

	cellEl.colSpan = group.width;
	cellEl.innerHTML = group.value;

	rowEl.classList.add("tabulator-print-table-group");
	rowEl.classList.add("tabulator-group-level-" + row.indent);

	if(group.component.isVisible()){
		rowEl.classList.add("tabulator-group-visible");
	}

	this.mapElementStyles(styles.firstGroup, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);
	this.mapElementStyles(styles.firstGroup, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom"]);

	rowEl.appendChild(cellEl);

	return rowEl;
};

Export.prototype.genereateCalcElement = function(row, setup, styles){
	var rowEl = this.genereateRowElement(row, setup, styles);

	rowEl.classList.add("tabulator-print-table-calcs");
	this.mapElementStyles(styles.calcRow, rowEl, ["border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size", "background-color"]);

	return rowEl;
};

Export.prototype.genereateRowElement = function(row, setup, styles){
	var rowEl = document.createElement("tr");

	rowEl.classList.add("tabulator-print-table-row");

	row.columns.forEach((col) => {

		if(col){
			var cellEl = document.createElement("td"),
			column = col.component._column,
			value = col.value;

			var cellWrapper = {
				modules:{},
				getValue:function(){
					return value;
				},
				getField:function(){
					return column.definition.field;
				},
				getElement:function(){
					return cellEl;
				},
				getColumn:function(){
					return column.getComponent();
				},
				getData:function(){
					return row.component.getData();
				},
				getRow:function(){
					return row.component;
				},
				getComponent:function(){
					return cellWrapper;
				},
				column:column,
			};

			var classNames = column.definition.cssClass ? column.definition.cssClass.split(" ") : [];

			classNames.forEach(function(className) {
				cellEl.classList.add(className);
			});

			if(this.table.modExists("format") && this.config.formatCells !== false){
				value = this.table.modules.format.formatExportValue(cellWrapper, this.colVisProp);
			}else{
				switch(typeof value){
					case "object":
					value = JSON.stringify(value);
					break;

					case "undefined":
					case "null":
					value = "";
					break;

					default:
					value = value;
				}
			}

			if(value instanceof Node){
				cellEl.appendChild(value);
			}else{
				cellEl.innerHTML = value;
			}

			if(styles.firstCell){
				this.mapElementStyles(styles.firstCell, cellEl, ["padding-top", "padding-left", "padding-right", "padding-bottom", "border-top", "border-left", "border-right", "border-bottom", "color", "font-weight", "font-family", "font-size"]);

				if(column.definition.align){
					cellEl.style.textAlign = column.definition.align;
				}
			}

			if(this.table.options.dataTree && this.config.dataTree !== false){
				if((setup.treeElementField && setup.treeElementField == column.field) || (!setup.treeElementField && i == 0)){
					if(row.component._row.modules.dataTree.controlEl){
						cellEl.insertBefore(row.component._row.modules.dataTree.controlEl.cloneNode(true), cellEl.firstChild);
					}
					if(row.component._row.modules.dataTree.branchEl){
						cellEl.insertBefore(row.component._row.modules.dataTree.branchEl.cloneNode(true), cellEl.firstChild);
					}
				}
			}

			rowEl.appendChild(cellEl);

			if(cellWrapper.modules.format && cellWrapper.modules.format.renderedCallback){
				cellWrapper.modules.format.renderedCallback();
			}

			if(setup.rowFormatter && this.config.formatCells !== false){
				setup.rowFormatter(row.component);
			}
		}
	});

	return rowEl;
};


Export.prototype.genereateHTMLTable = function(list){
	var holder = document.createElement("div");

	holder.appendChild(this.genereateTableElement(list));

	return holder.innerHTML;
};


Export.prototype.getHtml = function(visible, style, config, colVisProp){
	var list = this.generateExportList(config || this.table.options.htmlOutputConfig, style, visible, colVisProp || "htmlOutput");

	return this.genereateHTMLTable(list);
};


Export.prototype.mapElementStyles = function(from, to, props){
	if(this.cloneTableStyle && from && to){

		var lookup = {
			"background-color" : "backgroundColor",
			"color" : "fontColor",
			"width" : "width",
			"font-weight" : "fontWeight",
			"font-family" : "fontFamily",
			"font-size" : "fontSize",
			"text-align" : "textAlign",
			"border-top" : "borderTop",
			"border-left" : "borderLeft",
			"border-right" : "borderRight",
			"border-bottom" : "borderBottom",
			"padding-top" : "paddingTop",
			"padding-left" : "paddingLeft",
			"padding-right" : "paddingRight",
			"padding-bottom" : "paddingBottom",
		};

		if(window.getComputedStyle){
			var fromStyle = window.getComputedStyle(from);

			props.forEach(function(prop){
				to.style[lookup[prop]] = fromStyle.getPropertyValue(prop);
			});
		}
	}
};


Tabulator.prototype.registerModule("export", Export);
