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

import Group from './Group.js';

class GroupRows extends Module{

	constructor(table){
		super(table);

		this.groupIDLookups = false; //enable table grouping and set field to group by
		this.startOpen = [function(){return false;}]; //starting state of group
		this.headerGenerator = [function(){return "";}];
		this.groupList = []; //ordered list of groups
		this.allowedValues = false;
		this.groups = {}; //hold row groups
		this.displayIndex = 0; //index in display pipeline

		this.displayHandler = this.getRows.bind(this);

		//register table options
		this.registerTableOption("groupBy", false); //enable table grouping and set field to group by
		this.registerTableOption("groupStartOpen", true); //starting state of group
		this.registerTableOption("groupValues", false);
		this.registerTableOption("groupUpdateOnCellEdit", false);
		this.registerTableOption("groupHeader", false); //header generation function
		this.registerTableOption("groupHeaderPrint", null);
		this.registerTableOption("groupHeaderClipboard", null);
		this.registerTableOption("groupHeaderHtmlOutput", null);
		this.registerTableOption("groupHeaderDownload", null);
		this.registerTableOption("groupToggleElement", "arrow");
		this.registerTableOption("groupClosedShowCalcs", false);

		//register table functions
		this.registerTableFunction("setGroupBy", this.setGroupBy.bind(this));
		this.registerTableFunction("setGroupValues", this.setGroupValues.bind(this));
		this.registerTableFunction("setGroupStartOpen", this.setGroupStartOpen.bind(this));
		this.registerTableFunction("setGroupHeader", this.setGroupHeader.bind(this));
		this.registerTableFunction("getGroups", this.userGetGroups.bind(this));
		this.registerTableFunction("getGroupedData", this.userGetGroupedData.bind(this));

		//register component functions
		this.registerComponentFunction("row", "getGroup", this.rowGetGroup.bind(this));
	}

	//initialize group configuration
	initialize(){
		if(this.table.options.groupBy){
			if(this.table.options.groupUpdateOnCellEdit){
				this.subscribe("cell-value-updated", this.cellUpdated.bind(this));
				this.subscribe("row-data-changed", this.reassignRowToGroup.bind(this), 0);
			}

			this.subscribe("table-built", this.configureGroupSetup.bind(this));
			
			this.subscribe("row-deleting", this.rowDeleting.bind(this));
			this.subscribe("row-deleted", this.rowsUpdated.bind(this));
			this.subscribe("scroll-horizontal", this.scrollHeaders.bind(this));
			this.subscribe("rows-wipe", this.wipe.bind(this));
			this.subscribe("rows-added", this.rowsUpdated.bind(this));
			this.subscribe("row-moving", this.rowMoving.bind(this));
			this.subscribe("row-adding-index", this.rowAddingIndex.bind(this));

			this.subscribe("rows-sample", this.rowSample.bind(this));

			this.subscribe("render-virtual-fill", this.virtualRenderFill.bind(this));

			this.registerDisplayHandler(this.displayHandler, 20);

			this.initialized = true;
		}
	}

	configureGroupSetup(){
		if(this.table.options.groupBy){
			var groupBy = this.table.options.groupBy,
			startOpen = this.table.options.groupStartOpen,
			groupHeader = this.table.options.groupHeader;

			this.allowedValues = this.table.options.groupValues;

			if(Array.isArray(groupBy) && Array.isArray(groupHeader) && groupBy.length > groupHeader.length){
				console.warn("Error creating group headers, groupHeader array is shorter than groupBy array");
			}

			this.headerGenerator = [function(){return "";}];
			this.startOpen = [function(){return false;}]; //starting state of group

			this.langBind("groups|item", (langValue, lang) => {
				this.headerGenerator[0] = (value, count, data) => { //header layout function
					return (typeof value === "undefined" ? "" : value) + "<span>(" + count + " " + ((count === 1) ? langValue : lang.groups.items) + ")</span>";
				};
			});

			this.groupIDLookups = [];

			if(Array.isArray(groupBy)){
				if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "table" && this.table.options.columnCalcs != "both"){
					this.table.modules.columnCalcs.removeCalcs();
				}
			}else{
				if(this.table.modExists("columnCalcs") && this.table.options.columnCalcs != "group"){

					var cols = this.table.columnManager.getRealColumns();

					cols.forEach((col) => {
						if(col.definition.topCalc){
							this.table.modules.columnCalcs.initializeTopRow();
						}

						if(col.definition.bottomCalc){
							this.table.modules.columnCalcs.initializeBottomRow();
						}
					});
				}
			}

			if(!Array.isArray(groupBy)){
				groupBy = [groupBy];
			}

			groupBy.forEach((group, i) => {
				var lookupFunc, column;

				if(typeof group == "function"){
					lookupFunc = group;
				}else{
					column = this.table.columnManager.getColumnByField(group);

					if(column){
						lookupFunc = function(data){
							return column.getFieldValue(data);
						};
					}else{
						lookupFunc = function(data){
							return data[group];
						};
					}
				}

				this.groupIDLookups.push({
					field: typeof group === "function" ? false : group,
					func:lookupFunc,
					values:this.allowedValues ? this.allowedValues[i] : false,
				});
			});

			if(startOpen){
				if(!Array.isArray(startOpen)){
					startOpen = [startOpen];
				}

				startOpen.forEach((level) => {
					level = typeof level == "function" ? level : function(){return true;};
				});

				this.startOpen = startOpen;
			}

			if(groupHeader){
				this.headerGenerator = Array.isArray(groupHeader) ? groupHeader : [groupHeader];
			}
		}else{
			this.groupList = [];
			this.groups = {};
		}
	}

	rowSample(rows, prevValue){
		var group = this.getGroups(false)[0];

		prevValue.push(group.getRows(false)[0]);

		return prevValue;
	}

	virtualRenderFill(){
		var el = this.table.rowManager.tableElement;
		var rows = this.table.rowManager.getVisibleRows();

		rows = rows.filter((row) => {
			return row.type !== "group";
		});

		el.style.minWidth = !rows.length ? this.table.columnManager.getWidth() + "px" : "";

		// if(this.table.options.groupBy){
		// 	if(this.layoutMode() != "fitDataFill" && rowsCount == this.table.modules.groupRows.countGroups()){
		// 		el.style.minWidth = this.table.columnManager.getWidth() + "px";
		// 	}
		// }
	}

	rowAddingIndex(row, index, top){
		this.assignRowToGroup(row);

		var groupRows = row.modules.group.rows;

		if(groupRows.length > 1){
			if(!index || (index && groupRows.indexOf(index) == -1)){
				if(top){
					if(groupRows[0] !== row){
						index = groupRows[0];
						this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
					}
				}else{
					if(groupRows[groupRows.length -1] !== row){
						index = groupRows[groupRows.length -1];
						this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
					}
				}
			}else{
				this.table.rowManager.moveRowInArray(row.modules.group.rows, row, index, !top);
			}
		}

		return index;
	}

	trackChanges(){
		this.dispatch("group-changed");
	}

	///////////////////////////////////
	///////// Table Functions /////////
	///////////////////////////////////

	setGroupBy(groups){
		this.table.options.groupBy = groups;

		if(!this.initialized){
			this.initialize();
		}

		this.configureGroupSetup();

		this.refreshData();

		this.trackChanges();
	}

	setGroupValues(groupValues){
		this.table.options.groupValues = groupValues;
		this.configureGroupSetup();
		this.refreshData();

		this.trackChanges();
	}

	setGroupStartOpen(values){
		this.table.options.groupStartOpen = values;
		this.configureGroupSetup();

		if(this.table.options.groupBy){
			this.refreshData();

			this.trackChanges();
		}else{
			console.warn("Grouping Update - cant refresh view, no groups have been set");
		}
	}

	setGroupHeader(values){
		this.table.options.groupHeader = values;
		this.configureGroupSetup();

		if(this.table.options.groupBy){
			this.refreshData();

			this.trackChanges();
		}else{
			console.warn("Grouping Update - cant refresh view, no groups have been set");
		}
	}

	userGetGroups(values){
		return this.getGroups(true);
	}

	// get grouped table data in the same format as getData()
	userGetGroupedData(){
		return this.table.options.groupBy ?
			this.getGroupedData() : this.getData();
	}


	///////////////////////////////////////
	///////// Component Functions /////////
	///////////////////////////////////////

	rowGetGroup(row){
		return row.modules.group ? row.modules.group.getComponent() : false;
	}

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

	rowMoving(from, to, after){
		if(!after && to instanceof Group){
			to = this.table.rowManager.prevDisplayRow(from) || to;
		}

		var toGroup = to instanceof Group ? to : to.modules.group;
		var fromGroup = from instanceof Group ? from : from.modules.group;

		if(toGroup === fromGroup){
			this.table.rowManager.moveRowInArray(toGroup.rows, from, to, after);
		}else{
			if(fromGroup){
				fromGroup.removeRow(from);
			}

			toGroup.insertRow(from, to, after);
		}
	}


	rowDeleting(row){
		//remove from group
		if(row.modules.group){
			row.modules.group.removeRow(row);
		}
	}


	rowsUpdated(row){
		this.updateGroupRows(true);
	}

	cellUpdated(cell){
		this.reassignRowToGroup(cell.row);
	}


	setDisplayIndex(index){
		this.displayIndex = index;
	}

	getDisplayIndex(){
		return this.displayIndex;
	}

	//return appropriate rows with group headers
	getRows(rows){
		if(this.table.options.groupBy && this.groupIDLookups.length){

			this.dispatchExternal("dataGrouping");

			this.generateGroups(rows);

			if(this.subscribedExternal("dataGrouped")){
				this.dispatchExternal("dataGrouped", this.getGroups(true));
			}

			return this.updateGroupRows();

		}else{
			return rows.slice(0);
		}
	}

	getGroups(component){
		var groupComponents = [];

		this.groupList.forEach(function(group){
			groupComponents.push(component ? group.getComponent() : group);
		});

		return groupComponents;
	}

	getChildGroups(group){
		var groupComponents = [];

		if(!group){
			group = this;
		}

		group.groupList.forEach((child) => {
			if(child.groupList.length){
				groupComponents = groupComponents.concat(this.getChildGroups(child));
			}else{
				groupComponents.push(child);
			}
		});

		return groupComponents;
	}

	wipe(){
		this.groupList.forEach(function(group){
			group.wipe();
		});
	}

	pullGroupListData(groupList) {
		var groupListData = [];

		groupList.forEach((group) => {
			var groupHeader = {};
			groupHeader.level = 0;
			groupHeader.rowCount = 0;
			groupHeader.headerContent = "";
			var childData = [];

			if (group.hasSubGroups) {
				childData = this.pullGroupListData(group.groupList);

				groupHeader.level = group.level;
				groupHeader.rowCount = childData.length - group.groupList.length; // data length minus number of sub-headers
				groupHeader.headerContent = group.generator(group.key, groupHeader.rowCount, group.rows, group);

				groupListData.push(groupHeader);
				groupListData = groupListData.concat(childData);
			}

			else {
				groupHeader.level = group.level;
				groupHeader.headerContent = group.generator(group.key, group.rows.length, group.rows, group);
				groupHeader.rowCount = group.getRows().length;

				groupListData.push(groupHeader);

				group.getRows().forEach((row) => {
					groupListData.push(row.getData("data"));
				});
			}
		});

		return groupListData;
	}

	getGroupedData(){

		return this.pullGroupListData(this.groupList);
	}

	getRowGroup(row){
		var match = false;

		if(this.options("dataTree")){
			row = this.table.modules.dataTree.getTreeParentRoot(row);
		}

		this.groupList.forEach((group) => {
			var result = group.getRowGroup(row);

			if(result){
				match = result;
			}
		});

		return match;
	}

	countGroups(){
		return this.groupList.length;
	}

	generateGroups(rows){
		var oldGroups = this.groups;

		this.groups = {};
		this.groupList = [];

		if(this.allowedValues && this.allowedValues[0]){
			this.allowedValues[0].forEach((value) => {
				this.createGroup(value, 0, oldGroups);
			});

			rows.forEach((row) => {
				this.assignRowToExistingGroup(row, oldGroups);
			});
		}else{
			rows.forEach((row) => {
				this.assignRowToGroup(row, oldGroups);
			});
		}
	}

	createGroup(groupID, level, oldGroups){
		var groupKey = level + "_" + groupID,
		group;

		oldGroups = oldGroups || [];

		group = new Group(this, false, level, groupID, this.groupIDLookups[0].field, this.headerGenerator[0], oldGroups[groupKey]);

		this.groups[groupKey] = group;
		this.groupList.push(group);
	}

	assignRowToExistingGroup(row, oldGroups){
		var groupID = this.groupIDLookups[0].func(row.getData()),
		groupKey = "0_" + groupID;

		if(this.groups[groupKey]){
			this.groups[groupKey].addRow(row);
		}
	}

	assignRowToGroup(row, oldGroups){
		var groupID = this.groupIDLookups[0].func(row.getData()),
		newGroupNeeded = !this.groups["0_" + groupID];

		if(newGroupNeeded){
			this.createGroup(groupID, 0, oldGroups);
		}

		this.groups["0_" + groupID].addRow(row);

		return !newGroupNeeded;
	}

	reassignRowToGroup(row){
		if(row.type === "row"){
			var oldRowGroup = row.modules.group,
			oldGroupPath = oldRowGroup.getPath(),
			newGroupPath = this.getExpectedPath(row),
			samePath;

			// figure out if new group path is the same as old group path
			samePath = (oldGroupPath.length == newGroupPath.length) && oldGroupPath.every((element, index) => {
				return element === newGroupPath[index];
			});

			// refresh if they new path and old path aren't the same (aka the row's groupings have changed)
			if(!samePath) {
				oldRowGroup.removeRow(row);
				this.assignRowToGroup(row, this.groups);
				this.refreshData(true);
			}
		}
	}

	getExpectedPath(row) {
		var groupPath = [], rowData = row.getData();

		this.groupIDLookups.forEach((groupId) => {
			groupPath.push(groupId.func(rowData));
		});

		return groupPath;
	}

	updateGroupRows(force){
		var output = [];

		this.groupList.forEach((group) => {
			output = output.concat(group.getHeadersAndRows());
		});

		if(force){
			this.refreshData(true, this.displayHandler);
		}

		return output;
	}

	scrollHeaders(left){
		if(this.table.options.renderHorizontal === "virtual"){
			left -= this.table.columnManager.renderer.vDomPadLeft;
		}

		left = left + "px";

		this.groupList.forEach((group) => {
			group.scrollHeader(left);
		});
	}

	removeGroup(group){
		var groupKey = group.level + "_" + group.key,
		index;

		if(this.groups[groupKey]){
			delete this.groups[groupKey];

			index = this.groupList.indexOf(group);

			if(index > -1){
				this.groupList.splice(index, 1);
			}
		}
	}

	checkBasicModeGroupHeaderWidth(){
		var element = this.table.rowManager.tableElement,
		onlyGroupHeaders = true;

		this.table.rowManager.getDisplayRows().forEach((row, index) =>{
			this.table.rowManager.styleRow(row, index);
			element.appendChild(row.getElement());
			row.initialize(true);

			if(row.type !== "group"){
				onlyGroupHeaders = false;
			}
		});

		if(onlyGroupHeaders){
			element.style.minWidth = this.table.columnManager.getWidth() + "px";
		}else{
			element.style.minWidth = "";
		}
	}

}

GroupRows.moduleName = "groupRows";

export default GroupRows;