import Module from '../../core/Module.js';

import CalcComponent from './CalcComponent.js';

import Cell from '../../core/cell/Cell.js';
import Column from '../../core/column/Column.js';
import Row from '../../core/row/Row.js';

import defaultCalculations from './defaults/calculations.js';

class ColumnCalcs extends Module{

	constructor(table){
		super(table);

		this.topCalcs = [];
		this.botCalcs = [];
		this.genColumn = false;
		this.topElement = this.createElement();
		this.botElement = this.createElement();
		this.topRow = false;
		this.botRow = false;
		this.topInitialized = false;
		this.botInitialized = false;

		this.registerTableOption("columnCalcs", true);

		this.registerColumnOption("topCalc");
		this.registerColumnOption("topCalcParams");
		this.registerColumnOption("topCalcFormatter");
		this.registerColumnOption("topCalcFormatterParams");
		this.registerColumnOption("bottomCalc");
		this.registerColumnOption("bottomCalcParams");
		this.registerColumnOption("bottomCalcFormatter");
		this.registerColumnOption("bottomCalcFormatterParams");
	}

	createElement (){
		var el = document.createElement("div");
		el.classList.add("tabulator-calcs-holder");
		return el;
	}

	initialize(){
		this.genColumn = new Column({field:"value"}, this);

		this.subscribe("cell-value-changed", this.cellValueChanged.bind(this));
		this.subscribe("column-init", this.initializeColumnCheck.bind(this));
		this.subscribe("row-deleted", this.rowsUpdated.bind(this));
		this.subscribe("scroll-horizontal", this.scrollHorizontal.bind(this));
		this.subscribe("row-added", this.rowsUpdated.bind(this));
		this.subscribe("column-moved", this.recalcActiveRows.bind(this));
		this.subscribe("column-add", this.recalcActiveRows.bind(this));
		this.subscribe("data-refreshed", this.recalcActiveRowsRefresh.bind(this));
		this.subscribe("table-redraw", this.tableRedraw.bind(this));
		this.subscribe("rows-visible", this.visibleRows.bind(this));

		this.registerTableFunction("getCalcResults", this.getResults.bind(this));
		this.registerTableFunction("recalc", this.userRecalc.bind(this));
	}

	tableRedraw(force){
		this.recalc(this.table.rowManager.activeRows);

		if(force){
			this.redraw();
		}
	}


	///////////////////////////////////
	///////// Table Functions /////////
	///////////////////////////////////
	userRecalc(){
		this.recalc(this.table.rowManager.activeRows);
	}

	///////////////////////////////////
	///////// Internal Logic //////////
	///////////////////////////////////

	visibleRows(viewable, rows){
		if(this.topRow){
			rows.unshift(this.topRow);
		}

		if(this.botRow){
			rows.push(this.botRow);
		}
	
		return rows;
	}

	rowsUpdated(row){
		if(this.table.options.groupBy){
			this.recalcRowGroup(row);
		}else{
			this.recalcActiveRows();
		}
	}

	recalcActiveRowsRefresh(){
		if(this.table.options.groupBy && this.table.options.dataTreeStartExpanded && this.table.options.dataTree){
			this.recalcAll();
		}else{
			this.recalcActiveRows();
		}
	}

	recalcActiveRows(){
		this.recalc(this.table.rowManager.activeRows);
	}

	cellValueChanged(cell){
		if(cell.column.definition.topCalc || cell.column.definition.bottomCalc){
			if(this.table.options.groupBy){
				if(this.table.options.columnCalcs == "table" || this.table.options.columnCalcs == "both"){
					this.recalcActiveRows();
				}

				if(this.table.options.columnCalcs != "table"){
					this.recalcRowGroup(cell.row);
				}
			}else{
				this.recalcActiveRows();
			}
		}
	}

	initializeColumnCheck(column){
		if(column.definition.topCalc || column.definition.bottomCalc){
			this.initializeColumn(column);
		}
	}

	//initialize column calcs
	initializeColumn(column){
		var def = column.definition;

		var config = {
			topCalcParams:def.topCalcParams || {},
			botCalcParams:def.bottomCalcParams || {},
		};

		if(def.topCalc){

			switch(typeof def.topCalc){
				case "string":
					if(ColumnCalcs.calculations[def.topCalc]){
						config.topCalc = ColumnCalcs.calculations[def.topCalc];
					}else{
						console.warn("Column Calc Error - No such calculation found, ignoring: ", def.topCalc);
					}
					break;

				case "function":
					config.topCalc = def.topCalc;
					break;

			}

			if(config.topCalc){
				column.modules.columnCalcs = config;
				this.topCalcs.push(column);

				if(this.table.options.columnCalcs != "group"){
					this.initializeTopRow();
				}
			}

		}

		if(def.bottomCalc){
			switch(typeof def.bottomCalc){
				case "string":
					if(ColumnCalcs.calculations[def.bottomCalc]){
						config.botCalc = ColumnCalcs.calculations[def.bottomCalc];
					}else{
						console.warn("Column Calc Error - No such calculation found, ignoring: ", def.bottomCalc);
					}
					break;

				case "function":
					config.botCalc = def.bottomCalc;
					break;

			}

			if(config.botCalc){
				column.modules.columnCalcs = config;
				this.botCalcs.push(column);

				if(this.table.options.columnCalcs != "group"){
					this.initializeBottomRow();
				}
			}
		}

	}

	//dummy functions to handle being mock column manager
	registerColumnField(){}

	removeCalcs(){
		var changed = false;

		if(this.topInitialized){
			this.topInitialized = false;
			this.topElement.parentNode.removeChild(this.topElement);
			changed = true;
		}

		if(this.botInitialized){
			this.botInitialized = false;
			this.footerRemove(this.botElement);
			changed = true;
		}

		if(changed){
			this.table.rowManager.adjustTableSize();
		}
	}

	initializeTopRow(){
		if(!this.topInitialized){
			this.table.columnManager.getElement().insertBefore(this.topElement, this.table.columnManager.headersElement.nextSibling);
			this.topInitialized = true;
		}
	}

	initializeBottomRow(){
		if(!this.botInitialized){
			this.footerPrepend(this.botElement);
			this.botInitialized = true;
		}
	}

	scrollHorizontal(left){
		if(this.botInitialized && this.botRow){
			if(this.table.rtl){
				this.botRow.getElement().style.marginRight = (left) + "px";
			}else{
				this.botRow.getElement().style.marginLeft = (-left) + "px";
			}	
		}
	}

	recalc(rows){
		var data, row;

		if(this.topInitialized || this.botInitialized){
			data = this.rowsToData(rows);

			if(this.topInitialized){
				if(this.topRow){
					this.topRow.deleteCells();
				}

				row = this.generateRow("top", data);
				this.topRow = row;
				while(this.topElement.firstChild) this.topElement.removeChild(this.topElement.firstChild);
				this.topElement.appendChild(row.getElement());
				row.initialize(true);
			}

			if(this.botInitialized){
				if(this.botRow){
					this.botRow.deleteCells();
				}

				row = this.generateRow("bottom", data);
				this.botRow = row;
				while(this.botElement.firstChild) this.botElement.removeChild(this.botElement.firstChild);
				this.botElement.appendChild(row.getElement());
				row.initialize(true);
			}

			this.table.rowManager.adjustTableSize();

			//set resizable handles
			if(this.table.modExists("frozenColumns")){
				this.table.modules.frozenColumns.layout();
			}
		}
	}

	recalcRowGroup(row){
		this.recalcGroup(this.table.modules.groupRows.getRowGroup(row));
	}

	recalcAll(){
		if(this.topCalcs.length || this.botCalcs.length){
			if(this.table.options.columnCalcs !== "group"){
				this.recalcActiveRows();
			}

			if(this.table.options.groupBy && this.table.options.columnCalcs !== "table"){

				var groups = this.table.modules.groupRows.getChildGroups();

				groups.forEach((group) => {
					this.recalcGroup(group);
				});
			}
		}
	}

	recalcGroup(group){
		var data, rowData;

		if(group){
			if(group.calcs){
				if(group.calcs.bottom){
					data = this.rowsToData(group.rows);
					rowData = this.generateRowData("bottom", data);

					group.calcs.bottom.updateData(rowData);
					group.calcs.bottom.reinitialize();
				}

				if(group.calcs.top){
					data = this.rowsToData(group.rows);
					rowData = this.generateRowData("top", data);

					group.calcs.top.updateData(rowData);
					group.calcs.top.reinitialize();
				}
			}
		}
	}

	//generate top stats row
	generateTopRow(rows){
		return this.generateRow("top", this.rowsToData(rows));
	}
	//generate bottom stats row
	generateBottomRow(rows){
		return this.generateRow("bottom", this.rowsToData(rows));
	}

	rowsToData(rows){
		var data = [];
		
		rows.forEach((row) => {
			data.push(row.getData());

			if(this.table.options.dataTree && this.table.options.dataTreeChildColumnCalcs){
				if(row.modules.dataTree && row.modules.dataTree.open){
					var children = this.rowsToData(this.table.modules.dataTree.getFilteredTreeChildren(row));
					data = data.concat(children);
				}
			}
		});

		return data;
	}

	//generate stats row
	generateRow(pos, data){
		var rowData = this.generateRowData(pos, data),
		row;

		if(this.table.modExists("mutator")){
			this.table.modules.mutator.disable();
		}

		row = new Row(rowData, this, "calc");

		if(this.table.modExists("mutator")){
			this.table.modules.mutator.enable();
		}

		row.getElement().classList.add("tabulator-calcs", "tabulator-calcs-" + pos);

		row.component = false;

		row.getComponent = () => {
			if(!row.component){
				row.component = new CalcComponent(row);
			}

			return row.component;
		};

		row.generateCells = () => {

			var cells = [];

			this.table.columnManager.columnsByIndex.forEach((column) => {

				//set field name of mock column
				this.genColumn.setField(column.getField());
				this.genColumn.hozAlign = column.hozAlign;

				if(column.definition[pos + "CalcFormatter"] && this.table.modExists("format")){
					this.genColumn.modules.format = {
						formatter: this.table.modules.format.getFormatter(column.definition[pos + "CalcFormatter"]),
						params: column.definition[pos + "CalcFormatterParams"] || {},
					};
				}else{
					this.genColumn.modules.format = {
						formatter: this.table.modules.format.getFormatter("plaintext"),
						params:{}
					};
				}

				//ensure css class definition is replicated to calculation cell
				this.genColumn.definition.cssClass = column.definition.cssClass;

				//generate cell and assign to correct column
				var cell = new Cell(this.genColumn, row);
				cell.getElement();
				cell.column = column;
				cell.setWidth();

				column.cells.push(cell);
				cells.push(cell);

				if(!column.visible){
					cell.hide();
				}
			});

			row.cells = cells;
		};

		return row;
	}

	//generate stats row
	generateRowData(pos, data){
		var rowData = {},
		calcs = pos == "top" ? this.topCalcs : this.botCalcs,
		type = pos == "top" ? "topCalc" : "botCalc",
		params, paramKey;

		calcs.forEach(function(column){
			var values = [];

			if(column.modules.columnCalcs && column.modules.columnCalcs[type]){
				data.forEach(function(item){
					values.push(column.getFieldValue(item));
				});

				paramKey = type + "Params";
				params = typeof column.modules.columnCalcs[paramKey] === "function" ? column.modules.columnCalcs[paramKey](values, data) : column.modules.columnCalcs[paramKey];

				column.setFieldValue(rowData, column.modules.columnCalcs[type](values, data, params));
			}
		});

		return rowData;
	}

	hasTopCalcs(){
		return	!!(this.topCalcs.length);
	}

	hasBottomCalcs(){
		return	!!(this.botCalcs.length);
	}

	//handle table redraw
	redraw(){
		if(this.topRow){
			this.topRow.normalizeHeight(true);
		}
		if(this.botRow){
			this.botRow.normalizeHeight(true);
		}
	}

	//return the calculated
	getResults(){
		var results = {},
		groups;

		if(this.table.options.groupBy && this.table.modExists("groupRows")){
			groups = this.table.modules.groupRows.getGroups(true);

			groups.forEach((group) => {
				results[group.getKey()] = this.getGroupResults(group);
			});
		}else{
			results = {
				top: this.topRow ? this.topRow.getData() : {},
				bottom: this.botRow ? this.botRow.getData() : {},
			};
		}

		return results;
	}

	//get results from a group
	getGroupResults(group){
		var groupObj = group._getSelf(),
		subGroups = group.getSubGroups(),
		subGroupResults = {},
		results = {};

		subGroups.forEach((subgroup) => {
			subGroupResults[subgroup.getKey()] = this.getGroupResults(subgroup);
		});

		results = {
			top: groupObj.calcs.top ? groupObj.calcs.top.getData() : {},
			bottom: groupObj.calcs.bottom ? groupObj.calcs.bottom.getData() : {},
			groups: subGroupResults,
		};

		return results;
	}
}

ColumnCalcs.moduleName = "columnCalcs";

//load defaults
ColumnCalcs.calculations = defaultCalculations;

export default ColumnCalcs;