import Module from "../../core/Module.js";
import Range from "./Range.js";
import extensions from './extensions/extensions.js';


export default class SelectRange extends Module {
	
	static moduleName = "selectRange";
	static moduleInitOrder = 1;
	static moduleExtensions = extensions;
	
	constructor(table) {
		super(table);
		
		this.selecting = "cell";
		this.mousedown = false;
		this.ranges = [];
		this.overlay = null;
		this.rowHeader = null;
		this.layoutChangeTimeout = null;
		this.columnSelection = false;
		this.rowSelection = false;
		this.maxRanges = 0;
		this.activeRange = false;
		this.blockKeydown = false;
		
		this.keyDownEvent = this._handleKeyDown.bind(this);
		this.mouseUpEvent = this._handleMouseUp.bind(this);
		
		this.registerTableOption("selectableRange", false); //enable selectable range
		this.registerTableOption("selectableRangeColumns", false); //enable selectable range
		this.registerTableOption("selectableRangeRows", false); //enable selectable range
		this.registerTableOption("selectableRangeClearCells", false); //allow clearing of active range
		this.registerTableOption("selectableRangeClearCellsValue", undefined); //value for cleared active range
		this.registerTableOption("selectableRangeAutoFocus", true); //focus on a cell after resetRanges
		this.registerTableOption("selectableRangeBlurEditOnNavigate", undefined); //prevent editing on navigation
		
		this.registerTableFunction("getRangesData", this.getRangesData.bind(this));
		this.registerTableFunction("getRanges", this.getRanges.bind(this));
		this.registerTableFunction("addRange", this.addRangeFromComponent.bind(this));
		
		this.registerComponentFunction("cell", "getRanges", this.cellGetRanges.bind(this));
		this.registerComponentFunction("row", "getRanges", this.rowGetRanges.bind(this));
		this.registerComponentFunction("column", "getRanges", this.colGetRanges.bind(this));
	}
	
	///////////////////////////////////
	///////    Initialization   ///////
	///////////////////////////////////
	
	initialize() {
		if (this.options("selectableRange")) {	
			if(!this.options("selectableRows")){
				this.maxRanges = this.options("selectableRange");
				
				this.initializeTable();
				this.initializeWatchers();
			}else{
				console.warn("SelectRange functionality cannot be used in conjunction with row selection");
			}

			if(this.options('columns').findIndex((column) => column.frozen) > 0) {
				console.warn("Having frozen column in arbitrary position with selectRange option may result in unpredictable behavior.");
			}

			if(this.options('columns').filter((column) => column.frozen) > 1) {
				console.warn("Having multiple frozen columns with selectRange option may result in unpredictable behavior.");
			}
		}
		
		this.subscribe("edit-nav-disabled", () => {
			return true; // Disable navigation in edit module
		});
	}
	
	
	initializeTable() {		
		this.overlay = document.createElement("div");
		this.overlay.classList.add("tabulator-range-overlay");
		
		this.rangeContainer = document.createElement("div");
		this.rangeContainer.classList.add("tabulator-range-container");
		
		this.activeRangeCellElement = document.createElement("div");
		this.activeRangeCellElement.classList.add("tabulator-range-cell-active");
		
		this.overlay.appendChild(this.rangeContainer);
		this.overlay.appendChild(this.activeRangeCellElement);
		
		this.table.rowManager.element.addEventListener("keydown", this.keyDownEvent);
		
		this.resetRanges();
		
		this.table.rowManager.element.appendChild(this.overlay);
		this.table.columnManager.element.setAttribute("tabindex", 0);
		this.table.element.classList.add("tabulator-ranges");
	}
	
	initializeWatchers() {
		this.columnSelection = this.options("selectableRangeColumns");
		this.rowSelection = this.options("selectableRangeRows");
		
		this.subscribe("column-init", this.initializeColumn.bind(this));
		this.subscribe("column-mousedown", this.handleColumnMouseDown.bind(this));
		this.subscribe("column-mousemove", this.handleColumnMouseMove.bind(this));
		this.subscribe("column-resized", this.handleColumnResized.bind(this));
		this.subscribe("column-moving", this.handleColumnMoving.bind(this));
		this.subscribe("column-moved", this.handleColumnMoved.bind(this));
		this.subscribe("column-width", this.layoutChange.bind(this));
		this.subscribe("column-height", this.layoutChange.bind(this));
		this.subscribe("column-resized", this.layoutChange.bind(this));
		this.subscribe("columns-loaded", this.updateHeaderColumn.bind(this));
		
		this.subscribe("cell-height", this.layoutChange.bind(this));
		this.subscribe("cell-rendered", this.renderCell.bind(this));
		this.subscribe("cell-mousedown", this.handleCellMouseDown.bind(this));
		this.subscribe("cell-mousemove", this.handleCellMouseMove.bind(this));
		this.subscribe("cell-click", this.handleCellClick.bind(this));
		this.subscribe("cell-editing", this.handleEditingCell.bind(this));
		
		this.subscribe("page-changed", this.redraw.bind(this));
		
		this.subscribe("scroll-vertical", this.layoutChange.bind(this));
		this.subscribe("scroll-horizontal", this.layoutChange.bind(this));
		
		this.subscribe("data-destroy", this.tableDestroyed.bind(this));
		this.subscribe("data-processed", this.resetRanges.bind(this));
		
		this.subscribe("table-layout", this.layoutElement.bind(this));
		this.subscribe("table-redraw", this.redraw.bind(this));
		this.subscribe("table-destroy", this.tableDestroyed.bind(this));
		
		this.subscribe("edit-editor-clear", this.finishEditingCell.bind(this));
		this.subscribe("edit-blur", this.restoreFocus.bind(this));
		
		this.subscribe("keybinding-nav-prev", this.keyNavigate.bind(this, "prev"));
		this.subscribe("keybinding-nav-next", this.keyNavigate.bind(this, "next"));
		this.subscribe("keybinding-nav-left", this.keyNavigate.bind(this, "left"));
		this.subscribe("keybinding-nav-right", this.keyNavigate.bind(this, "right"));
		this.subscribe("keybinding-nav-up", this.keyNavigate.bind(this, "up"));
		this.subscribe("keybinding-nav-down", this.keyNavigate.bind(this, "down"));
		this.subscribe("keybinding-nav-range", this.keyNavigateRange.bind(this));
	}
	
	
	initializeColumn(column) {
		if(this.columnSelection && column.definition.headerSort && this.options("headerSortClickElement") !== "icon"){
			console.warn("Using column headerSort with selectableRangeColumns option may result in unpredictable behavior. Consider using headerSortClickElement: 'icon'.");
		}
	}
	
	updateHeaderColumn(){
		var frozenCols;

		if(this.rowSelection){
			this.rowHeader = this.table.columnManager.getVisibleColumnsByIndex()[0];
			
			if(this.rowHeader){
				this.rowHeader.definition.cssClass = this.rowHeader.definition.cssClass + " tabulator-range-row-header";
				
				if(this.rowHeader.definition.headerSort){
					console.warn("Using column headerSort with selectableRangeRows option may result in unpredictable behavior");
				}
				
				if(this.rowHeader.definition.editor){
					console.warn("Using column editor with selectableRangeRows option may result in unpredictable behavior");
				}
			}
		}

		//warn if invalid frozen column configuration detected
		if(this.table.modules.frozenColumns && this.table.modules.frozenColumns.active){
			frozenCols = this.table.modules.frozenColumns.getFrozenColumns();

			if(frozenCols.length > 1 || (frozenCols.length === 1 && frozenCols[0] !== this.rowHeader)){
				console.warn("Using frozen columns that are not the range header in combination with the selectRange option may result in unpredictable behavior");
			}
		}
	}
	
	///////////////////////////////////
	///////   Table Functions   ///////
	///////////////////////////////////
	
	getRanges(){
		return this.ranges.map((range) => range.getComponent());
	}
	
	getRangesData() {
		return this.ranges.map((range) => range.getData());
	}
	
	addRangeFromComponent(start, end){
		start = start ? start._cell : null;
		end = end ? end._cell : null;
		
		return this.addRange(start, end);
	}
	
	///////////////////////////////////
	/////// Component Functions ///////
	///////////////////////////////////
	
	cellGetRanges(cell){
		var ranges = [];
		
		if (cell.column === this.rowHeader) {
			ranges = this.ranges.filter((range) => range.occupiesRow(cell.row));
		} else {
			ranges = this.ranges.filter((range) => range.occupies(cell));
		}
		
		return ranges.map((range) => range.getComponent());
	}
	
	rowGetRanges(row){
		var ranges = this.ranges.filter((range) => range.occupiesRow(row));
		
		return ranges.map((range) => range.getComponent());
	}
	
	colGetRanges(col){
		var ranges = this.ranges.filter((range) => range.occupiesColumn(col));
		
		return ranges.map((range) => range.getComponent());
	}
	
	///////////////////////////////////
	////////// Event Handlers /////////
	///////////////////////////////////
	
	_handleMouseUp(e){
		this.mousedown = false;
		document.removeEventListener("mouseup", this.mouseUpEvent);
	}
	
	_handleKeyDown(e) {
		if (!this.blockKeydown && (!this.table.modules.edit || (this.table.modules.edit && !this.table.modules.edit.currentCell))) {
			if (e.key === "Enter") {
				// is editing a cell?
				if (this.table.modules.edit && this.table.modules.edit.currentCell) {
					return;
				}
				
				this.table.modules.edit.editCell(this.getActiveCell());
				
				e.preventDefault();
			}
			
			if ((e.key === "Backspace" || e.key === "Delete") && this.options("selectableRangeClearCells")) {
				if(this.activeRange){
					this.activeRange.clearValues();
				}
			}
		}
	}
	
	initializeFocus(cell){
		var range;

		this.restoreFocus();
		
		try{
			if (document.selection) { // IE
				range = document.body.createTextRange();
				range.moveToElementText(cell.getElement());
				range.select();
			} else if (window.getSelection) {
				range = document.createRange();
				range.selectNode(cell.getElement());
				window.getSelection().removeAllRanges();
				window.getSelection().addRange(range);
			}
		}catch(e){}
	}
	
	restoreFocus(element){
		this.table.rowManager.element.focus();
		
		return true;
	}
	
	///////////////////////////////////
	////// Column Functionality ///////
	///////////////////////////////////
	
	handleColumnResized(column) {
		var selected;
		
		if (this.selecting !== "column" && this.selecting !== "all") {
			return;
		}
		
		selected = this.ranges.some((range) => range.occupiesColumn(column));
		
		if (!selected) {
			return;
		}
		
		this.ranges.forEach((range) => {
			var selectedColumns = range.getColumns(true);
			
			selectedColumns.forEach((selectedColumn) => {
				if (selectedColumn !== column) {
					selectedColumn.setWidth(column.width);
				}
			});
		});
	}
	
	handleColumnMoving(_event, column) {
		this.resetRanges().setBounds(column);
		this.overlay.style.visibility = "hidden";
	}

	handleColumnMoved(from, _to, _after) {
		this.activeRange.setBounds(from);
		this.layoutElement();
	}

	handleColumnMouseDown(event, column) {
		if (event.button === 2 && (this.selecting === "column" || this.selecting === "all") && this.activeRange.occupiesColumn(column)) {
			return;
		}

		//If columns are movable, allow dragging columns only if they are not
		//selected. Dragging selected columns should move the columns instead.
		if(this.table.options.movableColumns && this.selecting === "column" && this.activeRange.occupiesColumn(column)){
			return;
		}
		
		this.mousedown = true;
		
		document.addEventListener("mouseup", this.mouseUpEvent);
		
		this.newSelection(event, column);
	}
	
	handleColumnMouseMove(e, column) {
		if (column === this.rowHeader || !this.mousedown || this.selecting === 'all') {
			return;
		}
		
		this.activeRange.setBounds(false, column, true);
	}
	
	///////////////////////////////////
	//////// Cell Functionality ///////
	///////////////////////////////////
	
	renderCell(cell) {
		var el = cell.getElement(),
		rangeIdx = this.ranges.findIndex((range) => range.occupies(cell));
		
		el.classList.toggle("tabulator-range-selected", rangeIdx !== -1);
		el.classList.toggle("tabulator-range-only-cell-selected", this.ranges.length === 1 && this.ranges[0].atTopLeft(cell) &&	this.ranges[0].atBottomRight(cell));
		
		el.dataset.range = rangeIdx;
	}
	
	handleCellMouseDown(event, cell) {
		if (event.button === 2 && (this.activeRange.occupies(cell) || ((this.selecting === "row" || this.selecting === "all") && this.activeRange.occupiesRow(cell.row)))) {
			return;
		}
		
		this.mousedown = true;
		
		document.addEventListener("mouseup", this.mouseUpEvent);
		
		this.newSelection(event, cell);
	}
	
	handleCellMouseMove(e, cell) {
		if (!this.mousedown || this.selecting === "all") {
			return;
		}
		
		this.activeRange.setBounds(false, cell, true);
	}
	
	handleCellClick(e, cell){
		this.initializeFocus(cell);
	}
	
	handleEditingCell(cell) {
		if(this.activeRange){
			this.activeRange.setBounds(cell);
		}
	}
	
	finishEditingCell() {
		this.blockKeydown = true;
		this.table.rowManager.element.focus();
		
		setTimeout(() => {
			this.blockKeydown = false;
		}, 10);
	}
	
	///////////////////////////////////
	///////     Navigation      ///////
	///////////////////////////////////
	
	keyNavigate(dir, e){
		if(this.options("selectableRangeBlurEditOnNavigate")){
			const isEditing = this.chain("edit-check-editing");
			
			if(isEditing){
				if(dir === 'next' || dir === 'prev'){
					this.dispatch("edit-cancel-cell");
				}else{
					// Prevent navigating while editing except for next/prev
					return false;
				}
			}
		}

		if (dir === 'prev') {
			dir = 'left';
		} else if (dir === 'next') {
			dir = 'right';
		}

		if(this.navigate(false, false, dir)){
			e.preventDefault();
		}
	}
	
	keyNavigateRange(e, dir, jump, expand){
		if(this.navigate(jump, expand, dir)){
			e.preventDefault();
		}
	}
	
	navigate(jump, expand, dir) {
		var moved = false,
		range, rangeEdge, prevRect, nextRow, nextCol, row, column,
		rowRect, rowManagerRect, columnRect, columnManagerRect;
		
		// Don't navigate while editing
		if (this.table.modules.edit && this.table.modules.edit.currentCell) {
			return false;
		}
		
		// If there are more than 1 range, use the active range and destroy the others
		if (this.ranges.length > 1) {
			this.ranges = this.ranges.filter((range) => {
				if (range === this.activeRange) {
					range.setEnd(range.start.row, range.start.col);
					return true;
				}
				range.destroy();
				return false;
			});
		}
		
		range = this.activeRange;
		prevRect = {
			top: range.top,
			bottom: range.bottom,
			left: range.left,
			right: range.right
		};
		
		rangeEdge = expand ? range.end : range.start;
		nextRow = rangeEdge.row;
		nextCol = rangeEdge.col;
		
		if(jump){
			switch(dir){
				case "left":
					nextCol = this.findJumpCellLeft(range.start.row, rangeEdge.col);
					break;
				case "right":
					nextCol = this.findJumpCellRight(range.start.row, rangeEdge.col);
					break;
				case "up":
					nextRow = this.findJumpCellUp(rangeEdge.row, range.start.col);
					break;
				case "down":
					nextRow = this.findJumpCellDown(rangeEdge.row, range.start.col);
					break;
			}
		}else{
			if(expand){
				if ((this.selecting === 'row' && (dir === 'left' || dir === 'right')) || (this.selecting === 'column' && (dir === 'up' || dir === 'down'))) {
					return;
				}
			}
			
			switch(dir){
				case "left":
					nextCol = Math.max(nextCol - 1, 0);
					break;
				case "right":
					nextCol = Math.min(nextCol + 1, this.getTableColumns().length - 1);
					break;
				case "up":
					nextRow = Math.max(nextRow - 1, 0);
					break;
				case "down":
					nextRow = Math.min(nextRow + 1, this.getTableRows().length - 1);
					break;
			}
		}

		if(this.rowHeader && nextCol === 0) {
			nextCol = 1;
		}
		
		if(!expand){
			range.setStart(nextRow, nextCol);
		}
		
		range.setEnd(nextRow, nextCol);
		
		if(!expand){
			this.selecting = "cell";
		}

		moved = prevRect.top !== range.top || prevRect.bottom !== range.bottom || prevRect.left !== range.left || prevRect.right !== range.right;

		if (moved) {
			row = this.getRowByRangePos(range.end.row);
			column = this.getColumnByRangePos(range.end.col);
			rowRect = row.getElement().getBoundingClientRect();
			columnRect = column.getElement().getBoundingClientRect();
			rowManagerRect = this.table.rowManager.getElement().getBoundingClientRect();
			columnManagerRect = this.table.columnManager.getElement().getBoundingClientRect();
			
			if(!(rowRect.top >= rowManagerRect.top && rowRect.bottom <= rowManagerRect.bottom)){
				if(row.getElement().parentNode && column.getElement().parentNode){
					// Use faster autoScroll when the elements are on the DOM
					this.autoScroll(range, row.getElement(), column.getElement());
				}else{
					row.getComponent().scrollTo(undefined, false);
				}
			}

			if(!(columnRect.left >= columnManagerRect.left + this.getRowHeaderWidth() && columnRect.right <= columnManagerRect.right)){
				if(row.getElement().parentNode && column.getElement().parentNode){
					// Use faster autoScroll when the elements are on the DOM
					this.autoScroll(range, row.getElement(), column.getElement());
				}else{
					column.getComponent().scrollTo(undefined, false);
				}
			}

			this.layoutElement();
		}
		return true;
	}
	
	rangeRemoved(removed){
		this.ranges = this.ranges.filter((range) => range !== removed);
		
		if(this.activeRange === removed){
			if(this.ranges.length){
				this.activeRange = this.ranges[this.ranges.length - 1];
			}else{
				this.addRange();
			}
		}
		
		this.layoutElement(true);
	}
	
	findJumpRow(column, rows, reverse, emptyStart, emptySide){
		if(reverse){
			rows = rows.reverse();
		}

		return this.findJumpItem(emptyStart, emptySide, rows, function(row){return row.getData()[column.getField()];});
	}
	
	findJumpCol(row, columns, reverse, emptyStart, emptySide){
		if(reverse){
			columns = columns.reverse();
		}

		return this.findJumpItem(emptyStart, emptySide, columns, function(column){return row.getData()[column.getField()];});
	}

	findJumpItem(emptyStart, emptySide, items, valueResolver){
		var nextItem;

		for(let currentItem of items){
			let currentValue = valueResolver(currentItem);
			
			if(emptyStart){
				nextItem = currentItem;
				if(currentValue){
					break;
				}
			}else{
				if(emptySide){
					nextItem = currentItem;
					
					if(currentValue){
						break;
					}
				}else{
					if(currentValue){
						nextItem = currentItem;
					}else{
						break;
					}
				}
			}
		}

		return nextItem;
	}

	findJumpCellLeft(rowPos, colPos){
		var row = this.getRowByRangePos(rowPos),
		columns = this.getTableColumns(),
		isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]),
		isLeftOfStartingCellEmpty = columns[colPos - 1] ? this.isEmpty(row.getData()[columns[colPos - 1].getField()]) : false,
		targetCols = this.rowHeader ? columns.slice(1, colPos) : columns.slice(0, colPos),
		jumpCol = this.findJumpCol(row, targetCols, true, isStartingCellEmpty, isLeftOfStartingCellEmpty);
		
		if(jumpCol){
			return jumpCol.getPosition() - 1;
		}
		
		return colPos;
	}
	
	findJumpCellRight(rowPos, colPos){
		var row = this.getRowByRangePos(rowPos),
		columns = this.getTableColumns(),
		isStartingCellEmpty = this.isEmpty(row.getData()[columns[colPos].getField()]),
		isRightOfStartingCellEmpty = columns[colPos + 1] ? this.isEmpty(row.getData()[columns[colPos + 1].getField()]) : false,
		jumpCol = this.findJumpCol(row, columns.slice(colPos + 1, columns.length), false, isStartingCellEmpty, isRightOfStartingCellEmpty);
		
		if(jumpCol){
			return jumpCol.getPosition() - 1;
		}
		
		return colPos;
	}
	
	findJumpCellUp(rowPos, colPos) {
		var column = this.getColumnByRangePos(colPos),
		rows = this.getTableRows(),
		isStartingCellEmpty = this.isEmpty(rows[rowPos].getData()[column.getField()]),
		isTopOfStartingCellEmpty = rows[rowPos - 1] ? this.isEmpty(rows[rowPos - 1].getData()[column.getField()]) : false,
		jumpRow = this.findJumpRow(column, rows.slice(0, rowPos), true, isStartingCellEmpty, isTopOfStartingCellEmpty);
		
		if(jumpRow){
			return jumpRow.position - 1;
		}
		
		return rowPos;
	}
	
	findJumpCellDown(rowPos, colPos) {
		var column = this.getColumnByRangePos(colPos),
		rows = this.getTableRows(),
		isStartingCellEmpty = this.isEmpty(rows[rowPos].getData()[column.getField()]),
		isBottomOfStartingCellEmpty = rows[rowPos + 1] ? this.isEmpty(rows[rowPos + 1].getData()[column.getField()]) : false,
		jumpRow = this.findJumpRow(column, rows.slice(rowPos + 1, rows.length), false, isStartingCellEmpty, isBottomOfStartingCellEmpty);
		
		if(jumpRow){
			return jumpRow.position - 1;
		}
		
		return rowPos;
	}
	
	///////////////////////////////////
	///////      Selection      ///////
	///////////////////////////////////
	newSelection(event, element) {
		var range;
		
		if (element.type === "column") {
			if(!this.columnSelection){
				return;
			}
			
			if (element === this.rowHeader) {
				range = this.resetRanges();
				this.selecting = "all";
				
				var topLeftCell, bottomRightCell = this.getCell(-1, -1);
				
				if(this.rowHeader){
					topLeftCell = this.getCell(0, 1);
				}else{
					topLeftCell = this.getCell(0, 0);
				}
				
				range.setBounds(topLeftCell, bottomRightCell);		
				return;
			} else {
				this.selecting = "column";
			}
		} else if (element.column === this.rowHeader) {
			this.selecting = "row";
		} else {
			this.selecting = "cell";
		}
		
		if (event.shiftKey) {
			this.activeRange.setBounds(false, element, true);
		} else if (event.ctrlKey) {
			this.addRange().setBounds(element, undefined, true);
		} else {
			this.resetRanges().setBounds(element, undefined, true);
		}
	}
	
	autoScroll(range, row, column) {
		var tableHolder = this.table.rowManager.element,
		rect, view, withinHorizontalView, withinVerticalView;
		
		if (typeof row === 'undefined') {
			row = this.getRowByRangePos(range.end.row).getElement();
		}
		
		if (typeof column === 'undefined') {
			column = this.getColumnByRangePos(range.end.col).getElement();
		}
		
		rect = {
			left: column.offsetLeft,
			right: column.offsetLeft + column.offsetWidth,
			top: row.offsetTop,
			bottom: row.offsetTop + row.offsetHeight,
		};
		
		view = {
			left: tableHolder.scrollLeft + this.getRowHeaderWidth(),
			right: Math.ceil(tableHolder.scrollLeft + tableHolder.clientWidth),
			top: tableHolder.scrollTop,
			bottom:	tableHolder.scrollTop +	tableHolder.offsetHeight - this.table.rowManager.scrollbarWidth,
		};
		
		withinHorizontalView = view.left < rect.left &&	rect.left < view.right && view.left < rect.right &&	rect.right < view.right;
		
		withinVerticalView = view.top < rect.top &&	rect.top < view.bottom && view.top < rect.bottom &&	rect.bottom < view.bottom;
		
		if (!withinHorizontalView) {
			if (rect.left < view.left) {
				tableHolder.scrollLeft = rect.left - this.getRowHeaderWidth();
			} else if (rect.right > view.right) {
				tableHolder.scrollLeft = Math.min(rect.right - tableHolder.clientWidth, rect.left - this.getRowHeaderWidth());
			}
		}
		
		if (!withinVerticalView) {
			if (rect.top < view.top) {
				tableHolder.scrollTop = rect.top;
			} else if (rect.bottom > view.bottom) {
				tableHolder.scrollTop = rect.bottom - tableHolder.clientHeight;
			}
		}
	}
	
	
	///////////////////////////////////
	///////       Layout        ///////
	///////////////////////////////////
	
	layoutChange(){
		this.overlay.style.visibility = "hidden";
		clearTimeout(this.layoutChangeTimeout);
		this.layoutChangeTimeout = setTimeout(this.layoutRanges.bind(this), 200);
	}
	
	redraw(force) {
		if (force) {
			this.selecting = 'cell';
			this.resetRanges();
			this.layoutElement();
		}
	}
	
	layoutElement(visibleRows) {
		var rows;
		
		if (visibleRows) {
			rows = this.table.rowManager.getVisibleRows(true);
		} else {
			rows = this.table.rowManager.getRows();
		}
		
		rows.forEach((row) => {
			if (row.type === "row") {
				this.layoutRow(row);
				row.cells.forEach((cell) => this.renderCell(cell));
			}
		});
		
		this.getTableColumns().forEach((column) => {
			this.layoutColumn(column);
		});
		
		this.layoutRanges();
	}
	
	layoutRow(row) {
		var el = row.getElement(),
		selected = false,
		occupied = this.ranges.some((range) => range.occupiesRow(row));
		
		if (this.selecting === "row") {
			selected = occupied;
		} else if (this.selecting === "all") {
			selected = true;
		}
		
		el.classList.toggle("tabulator-range-selected", selected);
		el.classList.toggle("tabulator-range-highlight", occupied);
	}
	
	layoutColumn(column) {
		var el = column.getElement(),		
		selected = false,
		occupied = this.ranges.some((range) => range.occupiesColumn(column));
		
		if (this.selecting === "column") {
			selected = occupied;
		} else if (this.selecting === "all") {
			selected = true;
		}
		
		el.classList.toggle("tabulator-range-selected", selected);
		el.classList.toggle("tabulator-range-highlight", occupied);
	}
	
	layoutRanges() {
		var activeCell, activeCellEl, activeRowEl;
		
		if (!this.table.initialized) {
			return;
		}
		
		activeCell = this.getActiveCell();
		
		if (!activeCell) {
			return;
		}

		activeCellEl = activeCell.getElement();
		activeRowEl = activeCell.row.getElement();

		if(this.table.rtl){
			this.activeRangeCellElement.style.right = activeRowEl.offsetWidth - activeCellEl.offsetLeft - activeCellEl.offsetWidth + "px";
		}else{
			this.activeRangeCellElement.style.left = activeRowEl.offsetLeft + activeCellEl.offsetLeft + "px";
		}

		this.activeRangeCellElement.style.top =	activeRowEl.offsetTop + "px";
		this.activeRangeCellElement.style.width = activeCellEl.offsetWidth + "px";
		this.activeRangeCellElement.style.height =  activeRowEl.offsetHeight  + "px";
		
		this.ranges.forEach((range) => range.layout());
		
		this.overlay.style.visibility = "visible";
	}
	
	
	///////////////////////////////////
	///////  Helper Functions   ///////
	///////////////////////////////////	
	
	getCell(rowIdx, colIdx) {
		var row;
		
		if (colIdx < 0) {
			colIdx = this.getTableColumns().length + colIdx;
			if (colIdx < 0) {
				return null;
			}
		}
		
		if (rowIdx < 0) {
			rowIdx = this.getTableRows().length + rowIdx;
		}
		
		row = this.table.rowManager.getRowFromPosition(rowIdx + 1);
		
		return row ? row.getCells(false, true).filter((cell) => cell.column.visible)[colIdx] : null;
	}
	
	
	getActiveCell() {
		return this.getCell(this.activeRange.start.row, this.activeRange.start.col);
	}
	
	getRowByRangePos(pos) {
		return this.getTableRows()[pos];
	}
	
	getColumnByRangePos(pos) {
		return this.getTableColumns()[pos];
	}
	
	getTableRows() {
		return this.table.rowManager.getDisplayRows().filter(row=> row.type === "row");
	}
	
	getTableColumns() {
		return this.table.columnManager.getVisibleColumnsByIndex();
	}
	
	addRange(start, end) {
		var  range;
		
		if(this.maxRanges !== true && this.ranges.length >= this.maxRanges){
			this.ranges.shift().destroy();
		}
		
		range = new Range(this.table, this, start, end);
		
		this.activeRange = range;
		this.ranges.push(range);
		this.rangeContainer.appendChild(range.element);
		
		return range;
	}
	
	resetRanges() {
		var range, cell, visibleCells;
		
		this.ranges.forEach((range) => range.destroy());
		this.ranges = [];
		
		range = this.addRange();
		
		if(this.table.rowManager.activeRows.length){
			visibleCells = this.table.rowManager.activeRows[0].cells.filter((cell) => cell.column.visible);
			cell = visibleCells[this.rowHeader ? 1 : 0];

			if(cell){
				range.setBounds(cell);
				if(this.options("selectableRangeAutoFocus")){
					this.initializeFocus(cell);
				}
			}
		}
		
		return range;
	}
	
	tableDestroyed(){
		document.removeEventListener("mouseup", this.mouseUpEvent);
		this.table.rowManager.element.removeEventListener("keydown", this.keyDownEvent);
	}
	
	selectedRows(component) {
		return component ? this.activeRange.getRows().map((row) => row.getComponent()) : this.activeRange.getRows();
	}
	
	selectedColumns(component) {
		return component ? this.activeRange.getColumns().map((col) => col.getComponent()) : this.activeRange.getColumns();
	}

	getRowHeaderWidth(){
		if(!this.rowHeader){
			return 0;
		}
		return this.rowHeader.getElement().offsetWidth;
	}

	isEmpty(value) {
		return value === null || value === undefined || value === "";
	}
}
