import CoreFeature from '../CoreFeature.js';
import ColumnComponent from './ColumnComponent.js';
import defaultOptions from './defaults/options.js';

import Cell from '../cell/Cell.js';

class Column extends CoreFeature{

	constructor(def, parent){
		super(parent.table);

		this.definition = def; //column definition
		this.parent = parent; //hold parent object
		this.type = "column"; //type of element
		this.columns = []; //child columns
		this.cells = []; //cells bound to this column
		this.element = this.createElement(); //column header element
		this.contentElement = false;
		this.titleHolderElement = false;
		this.titleElement = false;
		this.groupElement = this.createGroupElement(); //column group holder element
		this.isGroup = false;
		this.hozAlign = ""; //horizontal text alignment
		this.vertAlign = ""; //vert text alignment

		//multi dimensional filed handling
		this.field ="";
		this.fieldStructure = "";
		this.getFieldValue = "";
		this.setFieldValue = "";

		this.titleDownload = null;
		this.titleFormatterRendered = false;

		this.mapDefinitions();

		this.setField(this.definition.field);

		this.modules = {}; //hold module variables;

		this.width = null; //column width
		this.widthStyled = ""; //column width pre-styled to improve render efficiency
		this.maxWidth = null; //column maximum width
		this.maxWidthStyled = ""; //column maximum pre-styled to improve render efficiency
		this.maxInitialWidth = null;
		this.minWidth = null; //column minimum width
		this.minWidthStyled = ""; //column minimum pre-styled to improve render efficiency
		this.widthFixed = false; //user has specified a width for this column

		this.visible = true; //default visible state

		this.component = null;

		//initialize column
		if(this.definition.columns){

			this.isGroup = true;

			this.definition.columns.forEach((def, i) => {
				var newCol = new Column(def, this);
				this.attachColumn(newCol);
			});

			this.checkColumnVisibility();
		}else{
			parent.registerColumnField(this);
		}

		this._initialize();
	}

	createElement (){
		var el = document.createElement("div");

		el.classList.add("tabulator-col");
		el.setAttribute("role", "columnheader");
		el.setAttribute("aria-sort", "none");

		switch(this.table.options.columnHeaderVertAlign){
			case "middle":
				el.style.justifyContent = "center";
				break;
			case "bottom":
				el.style.justifyContent = "flex-end";
				break;
		}

		return el;
	}

	createGroupElement (){
		var el = document.createElement("div");

		el.classList.add("tabulator-col-group-cols");

		return el;
	}

	mapDefinitions(){
		var defaults = this.table.options.columnDefaults;

		//map columnDefaults onto column definitions
		if(defaults){
			for(let key in defaults){
				if(typeof this.definition[key] === "undefined"){
					this.definition[key] = defaults[key];
				}
			}
		}

		this.definition = this.table.columnManager.optionsList.generate(Column.defaultOptionList, this.definition);
	}

	checkDefinition(){
		Object.keys(this.definition).forEach((key) => {
			if(Column.defaultOptionList.indexOf(key) === -1){
				console.warn("Invalid column definition option in '" + (this.field || this.definition.title) + "' column:", key);
			}
		});
	}

	setField(field){
		this.field = field;
		this.fieldStructure = field ? (this.table.options.nestedFieldSeparator ? field.split(this.table.options.nestedFieldSeparator) : [field]) : [];
		this.getFieldValue = this.fieldStructure.length > 1 ? this._getNestedData : this._getFlatData;
		this.setFieldValue = this.fieldStructure.length > 1 ? this._setNestedData : this._setFlatData;
	}

	//register column position with column manager
	registerColumnPosition(column){
		this.parent.registerColumnPosition(column);
	}

	//register column position with column manager
	registerColumnField(column){
		this.parent.registerColumnField(column);
	}

	//trigger position registration
	reRegisterPosition(){
		if(this.isGroup){
			this.columns.forEach(function(column){
				column.reRegisterPosition();
			});
		}else{
			this.registerColumnPosition(this);
		}
	}

	//build header element
	_initialize(){
		var def = this.definition;

		while(this.element.firstChild) this.element.removeChild(this.element.firstChild);

		if(def.headerVertical){
			this.element.classList.add("tabulator-col-vertical");

			if(def.headerVertical === "flip"){
				this.element.classList.add("tabulator-col-vertical-flip");
			}
		}

		this.contentElement = this._buildColumnHeaderContent();

		this.element.appendChild(this.contentElement);

		if(this.isGroup){
			this._buildGroupHeader();
		}else{
			this._buildColumnHeader();
		}

		this.dispatch("column-init", this);
	}

	//build header element for header
	_buildColumnHeader(){
		var def = this.definition;

		this.dispatch("column-layout", this);

		//set column visibility
		if(typeof def.visible != "undefined"){
			if(def.visible){
				this.show(true);
			}else{
				this.hide(true);
			}
		}

		//assign additional css classes to column header
		if(def.cssClass){
			var classNames = def.cssClass.split(" ");
			classNames.forEach((className) => {
				this.element.classList.add(className);
			});
		}

		if(def.field){
			this.element.setAttribute("tabulator-field", def.field);
		}

		//set min width if present
		this.setMinWidth(parseInt(def.minWidth));

		if (def.maxInitialWidth) {
			this.maxInitialWidth = parseInt(def.maxInitialWidth);
		}
		
		if(def.maxWidth){
			this.setMaxWidth(parseInt(def.maxWidth));
		}

		this.reinitializeWidth();

		//set horizontal text alignment
		this.hozAlign = this.definition.hozAlign;
		this.vertAlign = this.definition.vertAlign;

		this.titleElement.style.textAlign = this.definition.headerHozAlign;
	}

	_buildColumnHeaderContent(){
		var contentElement = document.createElement("div");
		contentElement.classList.add("tabulator-col-content");

		this.titleHolderElement = document.createElement("div");
		this.titleHolderElement.classList.add("tabulator-col-title-holder");

		contentElement.appendChild(this.titleHolderElement);

		this.titleElement = this._buildColumnHeaderTitle();

		this.titleHolderElement.appendChild(this.titleElement);

		return contentElement;
	}

	//build title element of column
	_buildColumnHeaderTitle(){
		var def = this.definition;

		var titleHolderElement = document.createElement("div");
		titleHolderElement.classList.add("tabulator-col-title");

		if(def.editableTitle){
			var titleElement = document.createElement("input");
			titleElement.classList.add("tabulator-title-editor");

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

			titleElement.addEventListener("change", () => {
				def.title = titleElement.value;
				this.dispatchExternal("columnTitleChanged", this.getComponent());
			});

			titleHolderElement.appendChild(titleElement);

			if(def.field){
				this.langBind("columns|" + def.field, (text) => {
					titleElement.value = text || (def.title || "&nbsp;");
				});
			}else{
				titleElement.value  = def.title || "&nbsp;";
			}

		}else{
			if(def.field){
				this.langBind("columns|" + def.field, (text) => {
					this._formatColumnHeaderTitle(titleHolderElement, text || (def.title || "&nbsp;"));
				});
			}else{
				this._formatColumnHeaderTitle(titleHolderElement, def.title || "&nbsp;");
			}
		}

		return titleHolderElement;
	}

	_formatColumnHeaderTitle(el, title){
		var contents = this.chain("column-format", [this, title, el], null, () => {
			return title;
		});

		switch(typeof contents){
			case "object":
				if(contents instanceof Node){
					el.appendChild(contents);
				}else{
					el.innerHTML = "";
					console.warn("Format Error - Title formatter has returned a type of object, the only valid formatter object return is an instance of Node, the formatter returned:", contents);
				}
				break;
			case "undefined":
				el.innerHTML = "";
				break;
			default:
				el.innerHTML = contents;
		}
	}

	//build header element for column group
	_buildGroupHeader(){
		this.element.classList.add("tabulator-col-group");
		this.element.setAttribute("role", "columngroup");
		this.element.setAttribute("aria-title", this.definition.title);

		//asign additional css classes to column header
		if(this.definition.cssClass){
			var classNames = this.definition.cssClass.split(" ");
			classNames.forEach((className) => {
				this.element.classList.add(className);
			});
		}

		this.titleElement.style.textAlign = this.definition.headerHozAlign;

		this.element.appendChild(this.groupElement);
	}

	//flat field lookup
	_getFlatData(data){
		return data[this.field];
	}

	//nested field lookup
	_getNestedData(data){
		var dataObj = data,
		structure = this.fieldStructure,
		length = structure.length,
		output;

		for(let i = 0; i < length; i++){

			dataObj = dataObj[structure[i]];

			output = dataObj;

			if(!dataObj){
				break;
			}
		}

		return output;
	}

	//flat field set
	_setFlatData(data, value){
		if(this.field){
			data[this.field] = value;
		}
	}

	//nested field set
	_setNestedData(data, value){
		var dataObj = data,
		structure = this.fieldStructure,
		length = structure.length;

		for(let i = 0; i < length; i++){

			if(i == length -1){
				dataObj[structure[i]] = value;
			}else{
				if(!dataObj[structure[i]]){
					if(typeof value !== "undefined"){
						dataObj[structure[i]] = {};
					}else{
						break;
					}
				}

				dataObj = dataObj[structure[i]];
			}
		}
	}

	//attach column to this group
	attachColumn(column){
		if(this.groupElement){
			this.columns.push(column);
			this.groupElement.appendChild(column.getElement());

			column.columnRendered();
		}else{
			console.warn("Column Warning - Column being attached to another column instead of column group");
		}
	}

	//vertically align header in column
	verticalAlign(alignment, height){

		//calculate height of column header and group holder element
		var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : (height || this.parent.getHeadersElement().clientHeight);
		// var parentHeight = this.parent.isGroup ? this.parent.getGroupElement().clientHeight : this.parent.getHeadersElement().clientHeight;

		this.element.style.height = parentHeight + "px";

		this.dispatch("column-height", this, this.element.style.height);

		if(this.isGroup){
			this.groupElement.style.minHeight = (parentHeight - this.contentElement.offsetHeight) + "px";
		}

		//vertically align cell contents
		// if(!this.isGroup && alignment !== "top"){
		// 	if(alignment === "bottom"){
		// 		this.element.style.paddingTop = (this.element.clientHeight - this.contentElement.offsetHeight) + "px";
		// 	}else{
		// 		this.element.style.paddingTop = ((this.element.clientHeight - this.contentElement.offsetHeight) / 2) + "px";
		// 	}
		// }

		this.columns.forEach(function(column){
			column.verticalAlign(alignment);
		});
	}

	//clear vertical alignment
	clearVerticalAlign(){
		this.element.style.paddingTop = "";
		this.element.style.height = "";
		this.element.style.minHeight = "";
		this.groupElement.style.minHeight = "";

		this.columns.forEach(function(column){
			column.clearVerticalAlign();
		});

		this.dispatch("column-height", this, "");
	}

	//// Retrieve Column Information ////
	//return column header element
	getElement(){
		return this.element;
	}

	//return column group element
	getGroupElement(){
		return this.groupElement;
	}

	//return field name
	getField(){
		return this.field;
	}

	getTitleDownload() {
		return this.titleDownload;
	}

	//return the first column in a group
	getFirstColumn(){
		if(!this.isGroup){
			return this;
		}else{
			if(this.columns.length){
				return this.columns[0].getFirstColumn();
			}else{
				return false;
			}
		}
	}

	//return the last column in a group
	getLastColumn(){
		if(!this.isGroup){
			return this;
		}else{
			if(this.columns.length){
				return this.columns[this.columns.length -1].getLastColumn();
			}else{
				return false;
			}
		}
	}

	//return all columns in a group
	getColumns(traverse){
		var columns = [];

		if(traverse){
			this.columns.forEach((column) => {
				columns.push(column);
					
				columns = columns.concat(column.getColumns(true));
			});
		}else{
			columns = this.columns;
		}
		
		return columns;
	}

	//return all columns in a group
	getCells(){
		return this.cells;
	}

	//retrieve the top column in a group of columns
	getTopColumn(){
		if(this.parent.isGroup){
			return this.parent.getTopColumn();
		}else{
			return this;
		}
	}

	//return column definition object
	getDefinition(updateBranches){
		var colDefs = [];

		if(this.isGroup && updateBranches){
			this.columns.forEach(function(column){
				colDefs.push(column.getDefinition(true));
			});

			this.definition.columns = colDefs;
		}

		return this.definition;
	}

	//////////////////// Actions ////////////////////
	checkColumnVisibility(){
		var visible = false;

		this.columns.forEach(function(column){
			if(column.visible){
				visible = true;
			}
		});

		if(visible){
			this.show();
			this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
		}else{
			this.hide();
		}
	}

	//show column
	show(silent, responsiveToggle){
		if(!this.visible){
			this.visible = true;

			this.element.style.display = "";

			if(this.parent.isGroup){
				this.parent.checkColumnVisibility();
			}

			this.cells.forEach(function(cell){
				cell.show();
			});

			if(!this.isGroup && this.width === null){
				this.reinitializeWidth();
			}

			this.table.columnManager.verticalAlignHeaders();

			this.dispatch("column-show", this, responsiveToggle);

			if(!silent){
				this.dispatchExternal("columnVisibilityChanged", this.getComponent(), true);
			}

			if(this.parent.isGroup){
				this.parent.matchChildWidths();
			}

			if(!this.silent){
				this.table.columnManager.rerenderColumns();
			}
		}
	}

	//hide column
	hide(silent, responsiveToggle){
		if(this.visible){
			this.visible = false;

			this.element.style.display = "none";

			this.table.columnManager.verticalAlignHeaders();

			if(this.parent.isGroup){
				this.parent.checkColumnVisibility();
			}

			this.cells.forEach(function(cell){
				cell.hide();
			});

			this.dispatch("column-hide", this, responsiveToggle);

			if(!silent){
				this.dispatchExternal("columnVisibilityChanged", this.getComponent(), false);
			}

			if(this.parent.isGroup){
				this.parent.matchChildWidths();
			}

			if(!this.silent){
				this.table.columnManager.rerenderColumns();
			}
		}
	}

	matchChildWidths(){
		var childWidth = 0;

		if(this.contentElement && this.columns.length){
			this.columns.forEach(function(column){
				if(column.visible){
					childWidth += column.getWidth();
				}
			});

			this.contentElement.style.maxWidth = (childWidth - 1) + "px";

			if(this.parent.isGroup){
				this.parent.matchChildWidths();
			}
		}
	}

	removeChild(child){
		var index = this.columns.indexOf(child);

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

		if(!this.columns.length){
			this.delete();
		}
	}

	setWidth(width){
		this.widthFixed = true;
		this.setWidthActual(width);
	}

	setWidthActual(width){
		if(isNaN(width)){
			width = Math.floor((this.table.element.clientWidth/100) * parseInt(width));
		}

		width = Math.max(this.minWidth, width);

		if(this.maxWidth){
			width = Math.min(this.maxWidth, width);
		}

		this.width = width;
		this.widthStyled = width ? width + "px" : "";

		this.element.style.width = this.widthStyled;

		if(!this.isGroup){
			this.cells.forEach(function(cell){
				cell.setWidth();
			});
		}

		if(this.parent.isGroup){
			this.parent.matchChildWidths();
		}

		this.dispatch("column-width", this);
	}

	checkCellHeights(){
		var rows = [];

		this.cells.forEach(function(cell){
			if(cell.row.heightInitialized){
				if(cell.row.getElement().offsetParent !== null){
					rows.push(cell.row);
					cell.row.clearCellHeight();
				}else{
					cell.row.heightInitialized = false;
				}
			}
		});

		rows.forEach(function(row){
			row.calcHeight();
		});

		rows.forEach(function(row){
			row.setCellHeight();
		});
	}

	getWidth(){
		var width = 0;

		if(this.isGroup){
			this.columns.forEach(function(column){
				if(column.visible){
					width += column.getWidth();
				}
			});
		}else{
			width = this.width;
		}

		return width;
	}

	getHeight(){
		return Math.ceil(this.element.getBoundingClientRect().height);
	}

	setMinWidth(minWidth){
		if(this.maxWidth && minWidth > this.maxWidth){
			minWidth = this.maxWidth;

			console.warn("the minWidth ("+ minWidth + "px) for column '" + this.field + "' cannot be bigger that its maxWidth ("+ this.maxWidthStyled + ")");
		}

		this.minWidth = minWidth;
		this.minWidthStyled = minWidth ? minWidth + "px" : "";

		this.element.style.minWidth = this.minWidthStyled;

		this.cells.forEach(function(cell){
			cell.setMinWidth();
		});
	}

	setMaxWidth(maxWidth){
		if(this.minWidth && maxWidth < this.minWidth){
			maxWidth = this.minWidth;

			console.warn("the maxWidth ("+ maxWidth + "px) for column '" + this.field + "' cannot be smaller that its minWidth ("+ this.minWidthStyled + ")");
		}

		this.maxWidth = maxWidth;
		this.maxWidthStyled = maxWidth ? maxWidth + "px" : "";

		this.element.style.maxWidth = this.maxWidthStyled;

		this.cells.forEach(function(cell){
			cell.setMaxWidth();
		});
	}

	delete(){
		return new Promise((resolve, reject) => {
			if(this.isGroup){
				this.columns.forEach(function(column){
					column.delete();
				});
			}

			this.dispatch("column-delete", this);

			var cellCount = this.cells.length;

			for(let i = 0; i < cellCount; i++){
				this.cells[0].delete();
			}

			if(this.element.parentNode){
				this.element.parentNode.removeChild(this.element);
			}

			this.element = false;
			this.contentElement = false;
			this.titleElement = false;
			this.groupElement = false;

			if(this.parent.isGroup){
				this.parent.removeChild(this);
			}

			this.table.columnManager.deregisterColumn(this);

			this.table.columnManager.rerenderColumns(true);

			resolve();
		});
	}

	columnRendered(){
		if(this.titleFormatterRendered){
			this.titleFormatterRendered();
		}

		this.dispatch("column-rendered", this);
	}

	//////////////// Cell Management /////////////////
	//generate cell for this column
	generateCell(row){
		var cell = new Cell(this, row);

		this.cells.push(cell);

		return cell;
	}

	nextColumn(){
		var index = this.table.columnManager.findColumnIndex(this);
		return index > -1 ? this._nextVisibleColumn(index + 1) : false;
	}

	_nextVisibleColumn(index){
		var column = this.table.columnManager.getColumnByIndex(index);
		return !column || column.visible ? column : this._nextVisibleColumn(index + 1);
	}

	prevColumn(){
		var index = this.table.columnManager.findColumnIndex(this);
		return index > -1 ? this._prevVisibleColumn(index - 1) : false;
	}

	_prevVisibleColumn(index){
		var column = this.table.columnManager.getColumnByIndex(index);
		return !column || column.visible ? column : this._prevVisibleColumn(index - 1);
	}

	reinitializeWidth(force){
		this.widthFixed = false;

		//set width if present
		if(typeof this.definition.width !== "undefined" && !force){
			// maxInitialWidth ignored here as width specified
			this.setWidth(this.definition.width);
		}

		this.dispatch("column-width-fit-before", this);

		this.fitToData(force);

		this.dispatch("column-width-fit-after", this);
	}

	//set column width to maximum cell width for non group columns
	fitToData(force){
		if(this.isGroup){
			return;
		}

		if(!this.widthFixed){
			this.element.style.width = "";

			this.cells.forEach((cell) => {
				cell.clearWidth();
			});
		}

		var maxWidth = this.element.offsetWidth;

		if(!this.width || !this.widthFixed){
			this.cells.forEach((cell) => {
				var width = cell.getWidth();

				if(width > maxWidth){
					maxWidth = width;
				}
			});

			if(maxWidth){
				var setTo = maxWidth + 1;
				if (this.maxInitialWidth && !force) {
					setTo = Math.min(setTo, this.maxInitialWidth);
				}
				this.setWidthActual(setTo);
			}
		}
	}

	updateDefinition(updates){
		var definition;

		if(!this.isGroup){
			if(!this.parent.isGroup){
				definition = Object.assign({}, this.getDefinition());
				definition = Object.assign(definition, updates);

				return this.table.columnManager.addColumn(definition, false, this)
					.then((column) => {

						if(definition.field == this.field){
							this.field = false; //clear field name to prevent deletion of duplicate column from arrays
						}

						return this.delete()
							.then(() => {
								return column.getComponent();
							});

					});
			}else{
				console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
				return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
			}
		}else{
			console.error("Column Update Error - The updateDefinition function is only available on ungrouped columns");
			return Promise.reject("Column Update Error - The updateDefinition function is only available on columns, not column groups");
		}
	}

	deleteCell(cell){
		var index = this.cells.indexOf(cell);

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

	//////////////// Object Generation /////////////////
	getComponent(){
		if(!this.component){
			this.component = new ColumnComponent(this);
		}

		return this.component;
	}
}

Column.defaultOptionList = defaultOptions;

export default Column;
