var Download = function(table){
	this.table = table; //hold Tabulator object
	this.fields = {}; //hold filed multi dimension arrays
	this.columnsByIndex = []; //hold columns in their order in the table
	this.columnsByField = {}; //hold columns with lookup by field name
	this.config = {};
};

//trigger file download
Download.prototype.download = function(type, filename, options, interceptCallback){
	var self = this,
	downloadFunc = false;
	this.processConfig();

	function buildLink(data, mime){
		if(interceptCallback){
			if(interceptCallback === true){
				self.triggerDownload(data, mime, type, filename, true);
			}else{
				interceptCallback(data);
			}

		}else{
			self.triggerDownload(data, mime, type, filename);
		}
	}

	if(typeof type == "function"){
		downloadFunc = type;
	}else{
		if(self.downloaders[type]){
			downloadFunc = self.downloaders[type];
		}else{
			console.warn("Download Error - No such download type found: ", type);
		}
	}

	this.processColumns();

	if(downloadFunc){
		downloadFunc.call(this, self.processDefinitions(), self.processData() , options || {}, buildLink, this.config);
	}
};

Download.prototype.processConfig = function(){
	var config = {	//download config
		columnGroups:true,
		rowGroups:true,
		columnCalcs:true,
	};

	if(this.table.options.downloadConfig){
		for(var key in this.table.options.downloadConfig){
			config[key] = this.table.options.downloadConfig[key];
		}
	}

	if (config.rowGroups && this.table.options.groupBy && this.table.modExists("groupRows")){
		this.config.rowGroups = true;
	}

	if (config.columnGroups && this.table.columnManager.columns.length != this.table.columnManager.columnsByIndex.length){
		this.config.columnGroups = true;
	}

	if (config.columnCalcs && this.table.modExists("columnCalcs")){
		this.config.columnCalcs = true;
	}
};

Download.prototype.processColumns = function () {
	var self = this;

	self.columnsByIndex = [];
	self.columnsByField = {};

	self.table.columnManager.columnsByIndex.forEach(function (column) {

		if (column.field && column.definition.download !== false && (column.visible || (!column.visible && column.definition.download))) {
			self.columnsByIndex.push(column);
			self.columnsByField[column.field] = column;
		}
	});
};

Download.prototype.processDefinitions = function(){
	var self = this,
	processedDefinitions = [];

	if(this.config.columnGroups){
		self.table.columnManager.columns.forEach(function(column){
			var colData = self.processColumnGroup(column);

			if(colData){
				processedDefinitions.push(colData);
			}
		});
	}else{
		self.columnsByIndex.forEach(function(column){
			if(column.download !== false){
				//isolate definiton from defintion object
				processedDefinitions.push(self.processDefinition(column));
			}
		});
	}

	return  processedDefinitions;
};

Download.prototype.processColumnGroup = function(column){
	var subGroups = column.columns,
	maxDepth = 0;
	var processedColumn = this.processDefinition(column);
	var groupData = {
		type:"group",
		title:processedColumn.title,
		depth:1,
	};


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

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

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

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

		groupData.depth += maxDepth;

		if(!groupData.width){
			return false;
		}
	}else{
		if(column.field && column.definition.download !== false && (column.visible || (!column.visible && column.definition.download))){
			groupData.width = 1;
			groupData.definition = processedColumn;
		}else{
			return false;
		}
	}

	return groupData;
};

Download.prototype.processDefinition = function(column){
	var def = {};

	for(var key in column.definition){
		def[key] = column.definition[key];
	}

	if(typeof column.definition.downloadTitle != "undefined"){
		def.title = column.definition.downloadTitle;
	}

	return def;
};

Download.prototype.processData = function(){
	var self = this,
	data = [],
	groups = [],
	calcs =  {};

	if(this.config.rowGroups){
		groups = this.table.modules.groupRows.getGroups();

		groups.forEach((group) => {
			data.push(this.processGroupData(group));
		});
	}else{
		data = self.table.rowManager.getData(true, "download");
	}

	if(this.config.columnCalcs){
		calcs = this.table.getCalcResults();

		data = {
			calcs: calcs,
			data: data,
		};
	}

	//bulk data processing
	if(typeof self.table.options.downloadDataFormatter == "function"){
		data = self.table.options.downloadDataFormatter(data);
	}

	return data;
};

Download.prototype.processGroupData = function(group){
	var subGroups = group.getSubGroups();

	var groupData = {
		type:"group",
		key:group.key
	};

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

		subGroups.forEach((subGroup) => {
			groupData.subGroups.push(this.processGroupData(subGroup));
		});
	}else{
		groupData.rows = group.getData(true, "download");
	}

	return groupData;
};

Download.prototype.triggerDownload = function(data, mime, type, filename, newTab){
	var element = document.createElement('a'),
	blob = new Blob([data],{type:mime}),
	filename = filename || "Tabulator." + (typeof type === "function" ? "txt" : type);

	blob = this.table.options.downloadReady.call(this.table, data, blob);

	if(blob){

		if(newTab){
			window.open(window.URL.createObjectURL(blob));
		}else{
			if(navigator.msSaveOrOpenBlob){
				navigator.msSaveOrOpenBlob(blob, filename);
			}else{
				element.setAttribute('href', window.URL.createObjectURL(blob));

				//set file title
				element.setAttribute('download', filename);

				//trigger download
				element.style.display = 'none';
				document.body.appendChild(element);
				element.click();

				//remove temporary link element
				document.body.removeChild(element);
			}
		}


		if(this.table.options.downloadComplete){
			this.table.options.downloadComplete();
		}
	}

};

//nested field lookup
Download.prototype.getFieldValue = function(field, data){
	var column = this.columnsByField[field];

	if(column){
		return	column.getFieldValue(data);
	}

	return false;
};


Download.prototype.commsReceived = function(table, action, data){
	switch(action){
		case "intercept":
		this.download(data.type, "", data.options, data.intercept);
		break;
	}
};


//downloaders
Download.prototype.downloaders = {
	csv:function(columns, data, options, setFileContents, config){
		var self = this,
		titles = [],
		fields = [],
		delimiter = options && options.delimiter ? options.delimiter : ",",
		fileContents, output;

		//build column headers
		function parseSimpleTitles(){
			columns.forEach(function(column){
				titles.push('"' + String(column.title).split('"').join('""') + '"');
				fields.push(column.field);
			});
		}

		function parseColumnGroup(column, level){
			if(column.subGroups){
				column.subGroups.forEach(function(subGroup){
					parseColumnGroup(subGroup, level+1);
				});
			}else{
				titles.push('"' + String(column.title).split('"').join('""') + '"');
				fields.push(column.definition.field);
			}
		}

		if(config.columnGroups){
			console.warn("Download Warning - CSV downloader cannot process column groups");

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


		//generate header row
		fileContents = [titles.join(delimiter)];

		function parseRows(data){
			//generate each row of the table
			data.forEach(function(row){
				var rowData = [];

				fields.forEach(function(field){
					var value = self.getFieldValue(field, row);

					switch(typeof value){
						case "object":
						value = JSON.stringify(value);
						break;

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

						default:
						value = value;
					}

					//escape quotation marks
					rowData.push('"' + String(value).split('"').join('""') + '"');
				});

				fileContents.push(rowData.join(delimiter));
			});
		}

		function parseGroup(group){
			if(group.subGroups){
				group.subGroups.forEach(function(subGroup){
					parseGroup(subGroup);
				});
			}else{
				parseRows(group.rows);
			}
		}

		if(config.columnCalcs){
			console.warn("Download Warning - CSV downloader cannot process column calculations");
			data = data.data;
		}

		if(config.rowGroups){
			console.warn("Download Warning - CSV downloader cannot process row groups");

			data.forEach(function(group){
				parseGroup(group);
			});
		}else{
			parseRows(data);
		}

		output = fileContents.join("\n");

		if(options.bom){
			output = "\ufeff" + output;
		}

		setFileContents(output, "text/csv");
	},

	json:function(columns, data, options, setFileContents, config){
		var fileContents;

		if(config.columnCalcs){
			console.warn("Download Warning - CSV downloader cannot process column calculations");
			data = data.data;
		}

		fileContents = JSON.stringify(data, null, '\t');

		setFileContents(fileContents, "application/json");
	},

	pdf:function(columns, data, options, setFileContents, config){
		var self = this,
		fields = [],
		header = [],
		body = [],
		calcs = {},
		headerDepth = 1,
		table = "",
		autoTableParams = {},
		rowGroupStyles = options.rowGroupStyles || {
			fontStyle: "bold",
			fontSize: 12,
			cellPadding: 6,
			fillColor: 220,
		},
		rowCalcStyles = options.rowCalcStyles || {
			fontStyle: "bold",
			fontSize: 10,
			cellPadding: 4,
			fillColor: 232,
		},
		jsPDFParams = options.jsPDF || {},
		title = options && options.title ? options.title : "";

		if(config.columnCalcs){
			calcs = data.calcs;
			data = data.data;
		}

		if(!jsPDFParams.orientation){
			jsPDFParams.orientation = options.orientation || "landscape";
		}

		if(!jsPDFParams.unit){
			jsPDFParams.unit = "pt";
		}

		//build column headers
		function parseSimpleTitles(){
			columns.forEach(function(column){
				if(column.field){
					header.push(column.title || "");
					fields.push(column.field);
				}
			});

			header = [header];
		}

		function parseColumnGroup(column, level){
			var colSpan = column.width,
			rowSpan = 1,
			col = {
				content:column.title || "",
			};

			if(column.subGroups){
				column.subGroups.forEach(function(subGroup){
					parseColumnGroup(subGroup, level+1);
				});
				rowSpan = 1;
			}else{
				fields.push(column.definition.field);
				rowSpan = headerDepth - level;
			}

			col.rowSpan = rowSpan;
			// col.colSpan = colSpan;

			header[level].push(col);

			colSpan--;

			if(rowSpan > 1){
				for(var i = level + 1; i < headerDepth; i++){
					header[i].push("");
				}
			}

			for(var i = 0; i < colSpan; i++){
				header[level].push("");
			}
		}

		if(config.columnGroups){
			columns.forEach(function(column){
				if(column.depth > headerDepth){
					headerDepth = column.depth;
				}
			});

			for(var i=0; i < headerDepth; i++){
				header.push([]);
			}

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

		}else{
			parseSimpleTitles();
		}

		function parseValue(value){
			switch(typeof value){
				case "object":
				value = JSON.stringify(value);
				break;

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

				default:
				value = value;
			}

			return value;
		}

		function parseRows(data){
			//build table rows
			data.forEach(function(row){
				body.push(parseRow(row));
			});
		}

		function parseRow(row, styles){
			var rowData = [];

			fields.forEach(function(field){
				var value = self.getFieldValue(field, row);
				value = parseValue(value);

				if(styles){
					rowData.push({
						content:value,
						styles:styles,
					});
				}else{
					rowData.push(value);
				}
			});

			return rowData;
		}

		function parseGroup(group, calcObj){
			var groupData = [];

			groupData.push({content:parseValue(group.key), colSpan:fields.length, styles:rowGroupStyles});

			body.push(groupData);

			if(group.subGroups){
				group.subGroups.forEach(function(subGroup){
					parseGroup(subGroup,  calcObj[group.key] ? calcObj[group.key].groups || {} : {});
				});
			}else{

				if(config.columnCalcs){
					addCalcRow(calcObj, group.key, "top");
				}

				parseRows(group.rows);

				if(config.columnCalcs){
					addCalcRow(calcObj, group.key, "bottom");
				}
			}
		}

		function addCalcRow(calcs, selector, pos){
			var calcData = calcs[selector];

			if(calcData){
				if(pos){
					calcData = calcData[pos];
				}

				if(Object.keys(calcData).length){
					body.push(parseRow(calcData, rowCalcStyles));
				}
			}
		}

		if(config.rowGroups){
			data.forEach(function(group){
				parseGroup(group, calcs);
			});
		}else{
			if(config.columnCalcs){
				addCalcRow(calcs, "top");
			}

			parseRows(data);

			if(config.columnCalcs){
				addCalcRow(calcs, "bottom");
			}
		}

		var doc = new jsPDF(jsPDFParams); //set document to landscape, better for most tables

		if(options && options.autoTable){
			if(typeof options.autoTable === "function"){
				autoTableParams = options.autoTable(doc) || {};
			}else{
				autoTableParams = options.autoTable;
			}
		}

		if(title){
			autoTableParams.addPageContent = function(data) {
				doc.text(title, 40, 30);
			};
		}

		autoTableParams.head = header;
		autoTableParams.body = body;

		doc.autoTable(autoTableParams);

		if(options && options.documentProcessing){
			options.documentProcessing(doc);
		}

		setFileContents(doc.output("arraybuffer"), "application/pdf");
	},

	xlsx:function(columns, data, options, setFileContents, config){
		var self = this,
		sheetName = options.sheetName || "Sheet1",
		workbook = {SheetNames:[], Sheets:{}},
		calcs = {},
		groupRowIndexs = [],
		groupColumnIndexs = [],
		calcRowIndexs = [],

		output;

		if(config.columnCalcs){
			calcs = data.calcs;
			data = data.data;
		}

		function generateSheet(){
			var titles = [],
			fields = [],
			rows = [],
			worksheet;

			//convert rows to worksheet
			function rowsToSheet(){
				var sheet = {};
				var range = {s: {c:0, r:0}, e: {c:fields.length, r:rows.length }};

				XLSX.utils.sheet_add_aoa(sheet, rows);

				sheet['!ref'] = XLSX.utils.encode_range(range);

				var merges = generateMerges();

				if(merges.length){
					sheet["!merges"] = merges;
				}

				return sheet;
			}

			function parseSimpleTitles(){
				//get field lists
				columns.forEach(function(column){
					titles.push(column.title);
					fields.push(column.field);
				});

				rows.push(titles);
			}

			function parseColumnGroup(column, level){

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

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

				if(column.width > 1){

					groupColumnIndexs[level].push({
						type:"hoz",
						start:titles[level].length,
						end:titles[level].length + column.width - 1,
					});
				}

				titles[level].push(column.title);

				if(column.subGroups){
					column.subGroups.forEach(function(subGroup){
						parseColumnGroup(subGroup, level+1);
					});
				}else{
					fields.push(column.definition.field);
					padColumnTitles(fields.length - 1, level);

					groupColumnIndexs[level].push({
						type:"vert",
						start:fields.length - 1,
					});

				}
			}


			function padColumnTitles(){
				var max = 0;

				titles.forEach(function(title){
					var len = title.length;
					if(len > max){
						max = len;
					}
				});

				titles.forEach(function(title){
					var len = title.length;
					if(len < max){
						for(var i = len; i < max; i++){
							title.push("");
						}
					}
				});
			}

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

				titles.forEach(function(title){
					rows.push(title);
				});
			}else{
				parseSimpleTitles();
			}

			function generateMerges(){
				var output = [];

				groupRowIndexs.forEach(function(index){
					output.push({s:{r:index,c:0},e:{r:index,c:fields.length - 1}});
				});

				groupColumnIndexs.forEach(function(merges, level){
					merges.forEach(function(merge){
						if(merge.type === "hoz"){
							output.push({s:{r:level,c:merge.start},e:{r:level,c:merge.end}});
						}else{
							if(level != titles.length - 1){
								output.push({s:{r:level,c:merge.start},e:{r:titles.length - 1,c:merge.start}});
							}
						}
					});
				});

				return output;
			}

			//generate each row of the table
			function parseRows(data){
				data.forEach(function(row){
					rows.push(parseRow(row));
				});
			}

			function parseRow(row){
				var rowData = [];

				fields.forEach(function(field){
					var value = self.getFieldValue(field, row);
					rowData.push(!(value instanceof Date) && typeof value === "object" ? JSON.stringify(value) : value);
				});

				return rowData;
			}


			function addCalcRow(calcs, selector, pos){
				var calcData = calcs[selector];

				if(calcData){
					if(pos){
						calcData = calcData[pos];
					}

					if(Object.keys(calcData).length){
						calcRowIndexs.push(rows.length);
						rows.push(parseRow(calcData));
					}
				}
			}

			function parseGroup(group, calcObj){
				var groupData = [];

				groupData.push(group.key);

				groupRowIndexs.push(rows.length);

				rows.push(groupData);

				if(group.subGroups){
					group.subGroups.forEach(function(subGroup){
						parseGroup(subGroup, calcObj[group.key] ? calcObj[group.key].groups || {} : {});
					});
				}else{

					if(config.columnCalcs){
						addCalcRow(calcObj, group.key, "top");
					}

					parseRows(group.rows);

					if(config.columnCalcs){
						addCalcRow(calcObj, group.key, "bottom");
					}
				}


			}

			if(config.rowGroups){
				data.forEach(function(group){
					parseGroup(group, calcs);
				});
			}else{
				if(config.columnCalcs){
					addCalcRow(calcs, "top");
				}

				parseRows(data);

				if(config.columnCalcs){
					addCalcRow(calcs, "bottom");
				}
			}

			worksheet = rowsToSheet();

			return worksheet;
		}

		if(options.sheetOnly){
			setFileContents(generateSheet());
			return;
		}

		if(options.sheets){
			for(var sheet in options.sheets){

				if(options.sheets[sheet] === true){
					workbook.SheetNames.push(sheet);
					workbook.Sheets[sheet] = generateSheet();
				}else{

					workbook.SheetNames.push(sheet);

					this.table.modules.comms.send(options.sheets[sheet], "download", "intercept",{
						type:"xlsx",
						options:{sheetOnly:true},
						intercept:function(data){
							workbook.Sheets[sheet] = data;
						}
					});
				}
			}
		}else{
			workbook.SheetNames.push(sheetName);
			workbook.Sheets[sheetName] = generateSheet();
		}

		//convert workbook to binary array
		function s2ab(s) {
			var buf = new ArrayBuffer(s.length);
			var view = new Uint8Array(buf);
			for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
				return buf;
		}

		output = XLSX.write(workbook, {bookType:'xlsx', bookSST:true, type: 'binary'});

		setFileContents(s2ab(output), "application/octet-stream");
	},

};


Tabulator.prototype.registerModule("download", Download);
