var RowManager = function(table){

	this.table = table;
	this.element = this.createHolderElement(); //containing element
	this.tableElement = this.createTableElement(); //table element
	this.columnManager = null; //hold column manager object
	this.height = 0; //hold height of table element

	this.firstRender = false; //handle first render
	this.renderMode = "classic"; //current rendering mode

	this.rows = []; //hold row data objects
	this.activeRows = []; //rows currently available to on display in the table
	this.activeRowsCount = 0; //count of active rows

	this.displayRows = []; //rows currently on display in the table
	this.displayRowsCount = 0; //count of display rows

	this.scrollTop = 0;
	this.scrollLeft = 0;

	this.vDomRowHeight = 20; //approximation of row heights for padding

	this.vDomTop = 0; //hold position for first rendered row in the virtual DOM
	this.vDomBottom = 0; //hold possition for last rendered row in the virtual DOM

	this.vDomScrollPosTop = 0; //last scroll position of the vDom top;
	this.vDomScrollPosBottom = 0; //last scroll position of the vDom bottom;

	this.vDomTopPad = 0; //hold value of padding for top of virtual DOM
	this.vDomBottomPad = 0; //hold value of padding for bottom of virtual DOM

	this.vDomMaxRenderChain = 90; //the maximum number of dom elements that can be rendered in 1 go

	this.vDomWindowBuffer = 0; //window row buffer before removing elements, to smooth scrolling

	this.vDomWindowMinTotalRows = 20; //minimum number of rows to be generated in virtual dom (prevent buffering issues on tables with tall rows)
	this.vDomWindowMinMarginRows = 5; //minimum number of rows to be generated in virtual dom margin

	this.vDomTopNewRows = []; //rows to normalize after appending to optimize render speed
	this.vDomBottomNewRows = []; //rows to normalize after appending to optimize render speed
};

//////////////// Setup Functions /////////////////

RowManager.prototype.createHolderElement = function (){
	var el = document.createElement("div");

	el.classList.add("tabulator-tableHolder");
	el.setAttribute("tabindex", 0);

	return el;
};

RowManager.prototype.createTableElement = function (){
	var el = document.createElement("div");

	el.classList.add("tabulator-table");

	return el;
};

//return containing element
RowManager.prototype.getElement = function(){
	return this.element;
};

//return table element
RowManager.prototype.getTableElement = function(){
	return this.tableElement;
};

//return position of row in table
RowManager.prototype.getRowPosition = function(row, active){
	if(active){
		return this.activeRows.indexOf(row);
	}else{
		return this.rows.indexOf(row);
	}
};


//link to column manager
RowManager.prototype.setColumnManager = function(manager){
	this.columnManager = manager;
};

RowManager.prototype.initialize = function(){
	var self = this;

	self.setRenderMode();

	//initialize manager
	self.element.appendChild(self.tableElement);

	self.firstRender = true;

	//scroll header along with table body
	self.element.addEventListener("scroll", function(){
		var left = self.element.scrollLeft;

		//handle horizontal scrolling
		if(self.scrollLeft != left){
			self.columnManager.scrollHorizontal(left);

			if(self.table.options.groupBy){
				self.table.modules.groupRows.scrollHeaders(left);
			}

			if(self.table.modExists("columnCalcs")){
				self.table.modules.columnCalcs.scrollHorizontal(left);
			}
		}

		self.scrollLeft = left;
	});

	//handle virtual dom scrolling
	if(this.renderMode === "virtual"){

		self.element.addEventListener("scroll", function(){
			var top = self.element.scrollTop;
			var dir = self.scrollTop > top;

			//handle verical scrolling
			if(self.scrollTop != top){
				self.scrollTop = top;
				self.scrollVertical(dir);

				if(self.table.options.ajaxProgressiveLoad == "scroll"){
					self.table.modules.ajax.nextPage(self.element.scrollHeight - self.element.clientHeight - top);
				}
			}else{
				self.scrollTop = top;
			}

		});
	}
};


////////////////// Row Manipulation //////////////////

RowManager.prototype.findRow = function(subject){
	var self = this;

	if(typeof subject == "object"){

		if(subject instanceof Row){
			//subject is row element
			return subject;
		}else if(subject instanceof RowComponent){
			//subject is public row component
			return subject._getSelf() || false;
		}else if(typeof HTMLElement !== "undefined" && subject instanceof HTMLElement){
			//subject is a HTML element of the row
			let match = self.rows.find(function(row){
				return row.element === subject;
			});

			return match || false;
		}

	}else if(typeof subject == "undefined" || subject === null){
		return false;
	}else{
		//subject should be treated as the index of the row
		let match = self.rows.find(function(row){
			return row.data[self.table.options.index] == subject;
		});

		return match || false;
	}

	//catch all for any other type of input

	return false;
};


RowManager.prototype.getRowFromDataObject = function(data){
	var match = this.rows.find(function(row){
		return row.data === data;
	});

	return match || false;
};

RowManager.prototype.getRowFromPosition = function(position, active){
	if(active){
		return this.activeRows[position];
	}else{
		return this.rows[position];
	}
};

RowManager.prototype.scrollToRow = function(row, position, ifVisible){
	var rowIndex = this.getDisplayRows().indexOf(row),
	rowEl = row.getElement(),
	rowTop,
	offset = 0;

	return new Promise((resolve, reject) => {
		if(rowIndex > -1){

			if(typeof position === "undefined"){
				position = this.table.options.scrollToRowPosition;
			}

			if(typeof ifVisible === "undefined"){
				ifVisible = this.table.options.scrollToRowIfVisible;
			}


			if(position === "nearest"){
				switch(this.renderMode){
					case"classic":
					rowTop = Tabulator.prototype.helpers.elOffset(rowEl).top;
					position = Math.abs(this.element.scrollTop - rowTop) > Math.abs(this.element.scrollTop + this.element.clientHeight - rowTop) ? "bottom" : "top";
					break;
					case"virtual":
					position = Math.abs(this.vDomTop - rowIndex) > Math.abs(this.vDomBottom - rowIndex) ? "bottom" : "top";
					break;
				}
			}

			//check row visibility
			if(!ifVisible){
				if(Tabulator.prototype.helpers.elVisible(rowEl)){
					offset = Tabulator.prototype.helpers.elOffset(rowEl).top - Tabulator.prototype.helpers.elOffset(this.element).top;

					if(offset > 0 && offset < this.element.clientHeight - rowEl.offsetHeight){
						return false;
					}
				}
			}

			//scroll to row
			switch(this.renderMode){
				case"classic":
				this.element.scrollTop = Tabulator.prototype.helpers.elOffset(rowEl).top - Tabulator.prototype.helpers.elOffset(this.element).top + this.element.scrollTop;
				break;
				case"virtual":
				this._virtualRenderFill(rowIndex, true);
				break;
			}

			//align to correct position
			switch(position){
				case "middle":
				case "center":

				if(this.element.scrollHeight - this.element.scrollTop == this.element.clientHeight){
					this.element.scrollTop = this.element.scrollTop + (rowEl.offsetTop - this.element.scrollTop) - ((this.element.scrollHeight - rowEl.offsetTop) / 2);
				}else{
					this.element.scrollTop = this.element.scrollTop - (this.element.clientHeight / 2);
				}

				break;

				case "bottom":

				if(this.element.scrollHeight - this.element.scrollTop == this.element.clientHeight){
					this.element.scrollTop = this.element.scrollTop - (this.element.scrollHeight - rowEl.offsetTop) + rowEl.offsetHeight;
				}else{
					this.element.scrollTop = this.element.scrollTop - this.element.clientHeight + rowEl.offsetHeight;
				}

				break;
			}

			resolve();

		}else{
			console.warn("Scroll Error - Row not visible");
			reject("Scroll Error - Row not visible");
		}
	});
};


////////////////// Data Handling //////////////////

RowManager.prototype.setData = function(data, renderInPosition){
	var self = this;

	return new Promise((resolve, reject)=>{
		if(renderInPosition && this.getDisplayRows().length){
			if(self.table.options.pagination){
				self._setDataActual(data, true);
			}else{
				this.reRenderInPosition(function(){
					self._setDataActual(data);
				});
			}
		}else{
			if(this.table.options.autoColumns){
				this.table.columnManager.generateColumnsFromRowData(data);
			}
			this.resetScroll();
			this._setDataActual(data);
		}

		resolve();
	});
};

RowManager.prototype._setDataActual = function(data, renderInPosition){
	var self = this;

	self.table.options.dataLoading.call(this.table, data);

	this._wipeElements();

	if(this.table.options.history && this.table.modExists("history")){
		this.table.modules.history.clear();
	}

	if(Array.isArray(data)){

		if(this.table.modExists("selectRow")){
			this.table.modules.selectRow.clearSelectionData();
		}

		if(this.table.options.reactiveData && this.table.modExists("reactiveData", true)){
			this.table.modules.reactiveData.watchData(data);
		}

		data.forEach(function(def, i){
			if(def && typeof def === "object"){
				var row = new Row(def, self);
				self.rows.push(row);
			}else{
				console.warn("Data Loading Warning - Invalid row data detected and ignored, expecting object but received:", def);
			}
		});

		self.table.options.dataLoaded.call(this.table, data);

		self.refreshActiveData(false, false, renderInPosition);
	}else{
		console.error("Data Loading Error - Unable to process data due to invalid data type \nExpecting: array \nReceived: ", typeof data, "\nData:     ", data);
	}
};

RowManager.prototype._wipeElements = function(){
	this.rows.forEach(function(row){
		row.wipe();
	});

	if(this.table.options.groupBy && this.table.modExists("groupRows")){
		this.table.modules.groupRows.wipe();
	}

	this.rows = [];
}

RowManager.prototype.deleteRow = function(row, blockRedraw){
	var allIndex = this.rows.indexOf(row),
	activeIndex = this.activeRows.indexOf(row);

	if(activeIndex > -1){
		this.activeRows.splice(activeIndex, 1);
	}

	if(allIndex > -1){
		this.rows.splice(allIndex, 1);
	}

	this.setActiveRows(this.activeRows);

	this.displayRowIterator(function(rows){
		var displayIndex = rows.indexOf(row);

		if(displayIndex > -1){
			rows.splice(displayIndex, 1);
		}
	});

	if(!blockRedraw){
		this.reRenderInPosition();
	}

	this.table.options.rowDeleted.call(this.table, row.getComponent());

	this.table.options.dataEdited.call(this.table, this.getData());

	if(this.table.options.groupBy && this.table.modExists("groupRows")){
		this.table.modules.groupRows.updateGroupRows(true);
	}else if(this.table.options.pagination && this.table.modExists("page")){
		this.refreshActiveData(false, false, true);
	}else{
		if(this.table.options.pagination && this.table.modExists("page")){
			this.refreshActiveData("page");
		}
	}

};

RowManager.prototype.addRow = function(data, pos, index, blockRedraw){

	var row = this.addRowActual(data, pos, index, blockRedraw);

	if(this.table.options.history && this.table.modExists("history")){
		this.table.modules.history.action("rowAdd", row, {data:data, pos:pos, index:index});
	}

	return row;
};

//add multiple rows
RowManager.prototype.addRows = function(data, pos, index){
	var self = this,
	length = 0,
	rows = [];

	return new Promise((resolve, reject) => {
		pos = this.findAddRowPos(pos);

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

		length = data.length - 1;

		if((typeof index == "undefined" && pos) || (typeof index !== "undefined" && !pos)){
			data.reverse();
		}

		data.forEach(function(item, i){
			var row = self.addRow(item, pos, index, true);
			rows.push(row);
		});

		if(this.table.options.groupBy && this.table.modExists("groupRows")){
			this.table.modules.groupRows.updateGroupRows(true);
		}else if(this.table.options.pagination && this.table.modExists("page")){
			this.refreshActiveData(false, false, true);
		}else{
			this.reRenderInPosition();
		}

		//recalc column calculations if present
		if(this.table.modExists("columnCalcs")){
			this.table.modules.columnCalcs.recalc(this.table.rowManager.activeRows);
		}

		resolve(rows);
	});
};

RowManager.prototype.findAddRowPos = function(pos){
	if(typeof pos === "undefined"){
		pos = this.table.options.addRowPos;
	}

	if(pos === "pos"){
		pos = true;
	}

	if(pos === "bottom"){
		pos = false;
	}

	return pos;
};


RowManager.prototype.addRowActual = function(data, pos, index, blockRedraw){
	var row = data instanceof Row ? data : new Row(data || {}, this),
	top = this.findAddRowPos(pos),
	dispRows;

	if(!index && this.table.options.pagination && this.table.options.paginationAddRow == "page"){
		dispRows = this.getDisplayRows();

		if(top){
			if(dispRows.length){
				index = dispRows[0];
			}else{
				if(this.activeRows.length){
					index = this.activeRows[this.activeRows.length-1];
					top = false;
				}
			}
		}else{
			if(dispRows.length){
				index = dispRows[dispRows.length - 1];
				top = dispRows.length < this.table.modules.page.getPageSize() ? false : true;
			}
		}
	}

	if(index){
		index = this.findRow(index);
	}

	if(this.table.options.groupBy && this.table.modExists("groupRows")){
		this.table.modules.groupRows.assignRowToGroup(row);

		var groupRows = row.getGroup().rows;

		if(groupRows.length > 1){

			if(!index || (index && groupRows.indexOf(index) == -1)){
				if(top){
					if(groupRows[0] !== row){
						index = groupRows[0];
						this._moveRowInArray(row.getGroup().rows, row, index, !top);
					}
				}else{
					if(groupRows[groupRows.length -1] !== row){
						index = groupRows[groupRows.length -1];
						this._moveRowInArray(row.getGroup().rows, row, index, !top);
					}
				}
			}else{
				this._moveRowInArray(row.getGroup().rows, row, index, !top);
			}
		}
	}

	if(index){
		let allIndex = this.rows.indexOf(index),
		activeIndex = this.activeRows.indexOf(index);

		this.displayRowIterator(function(rows){
			var displayIndex = rows.indexOf(index);

			if(displayIndex > -1){
				rows.splice((top ? displayIndex : displayIndex + 1), 0, row);
			}
		});

		if(activeIndex > -1){
			this.activeRows.splice((top ? activeIndex : activeIndex + 1), 0, row);
		}

		if(allIndex > -1){
			this.rows.splice((top ? allIndex : allIndex + 1), 0, row);
		}

	}else{

		if(top){

			this.displayRowIterator(function(rows){
				rows.unshift(row);
			});

			this.activeRows.unshift(row);
			this.rows.unshift(row);
		}else{
			this.displayRowIterator(function(rows){
				rows.push(row);
			});

			this.activeRows.push(row);
			this.rows.push(row);
		}
	}

	this.setActiveRows(this.activeRows);

	this.table.options.rowAdded.call(this.table, row.getComponent());

	this.table.options.dataEdited.call(this.table, this.getData());

	if(!blockRedraw){
		this.reRenderInPosition();
	}

	return row;
};

RowManager.prototype.moveRow = function(from, to, after){
	if(this.table.options.history && this.table.modExists("history")){
		this.table.modules.history.action("rowMove", from, {pos:this.getRowPosition(from), to:to, after:after});
	}

	this.moveRowActual(from, to, after);

	this.table.options.rowMoved.call(this.table, from.getComponent());
};


RowManager.prototype.moveRowActual = function(from, to, after){
	var self = this;
	this._moveRowInArray(this.rows, from, to, after);
	this._moveRowInArray(this.activeRows, from, to, after);

	this.displayRowIterator(function(rows){
		self._moveRowInArray(rows, from, to, after);
	});

	if(this.table.options.groupBy && this.table.modExists("groupRows")){
		var toGroup = to.getGroup();
		var fromGroup = from.getGroup();

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

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


RowManager.prototype._moveRowInArray = function(rows, from, to, after){
	var	fromIndex, toIndex, start, end;

	if(from !== to){

		fromIndex = rows.indexOf(from);

		if (fromIndex > -1) {

			rows.splice(fromIndex, 1);

			toIndex = rows.indexOf(to);

			if (toIndex > -1) {

				if(after){
					rows.splice(toIndex+1, 0, from);
				}else{
					rows.splice(toIndex, 0, from);
				}

			}else{
				rows.splice(fromIndex, 0, from);
			}
		}

		//restyle rows
		if(rows === this.getDisplayRows()){

			start = fromIndex < toIndex ? fromIndex : toIndex;
			end = toIndex > fromIndex ? toIndex : fromIndex +1;

			for(let i = start; i <= end; i++){
				if(rows[i]){
					this.styleRow(rows[i], i);
				}
			}
		}
	}
};

RowManager.prototype.clearData = function(){
	this.setData([]);
};

RowManager.prototype.getRowIndex = function(row){
	return this.findRowIndex(row, this.rows);
};


RowManager.prototype.getDisplayRowIndex = function(row){
	var index = this.getDisplayRows().indexOf(row);
	return index > -1 ? index : false;
};

RowManager.prototype.nextDisplayRow = function(row, rowOnly){
	var index = this.getDisplayRowIndex(row),
	nextRow = false;


	if(index !== false && index < this.displayRowsCount -1){
		nextRow = this.getDisplayRows()[index+1];
	}

	if(nextRow && (!(nextRow instanceof Row) || nextRow.type != "row")){
		return this.nextDisplayRow(nextRow, rowOnly);
	}

	return nextRow;
};

RowManager.prototype.prevDisplayRow = function(row, rowOnly){
	var index = this.getDisplayRowIndex(row),
	prevRow = false;

	if(index){
		prevRow = this.getDisplayRows()[index-1];
	}

	if(prevRow && (!(prevRow instanceof Row) || prevRow.type != "row")){
		return this.prevDisplayRow(prevRow, rowOnly);
	}

	return prevRow;
};

RowManager.prototype.findRowIndex = function(row, list){
	var rowIndex;

	row = this.findRow(row);

	if(row){
		rowIndex = list.indexOf(row);

		if(rowIndex > -1){
			return rowIndex;
		}
	}

	return false;
};


RowManager.prototype.getData = function(active, transform){
	var self = this,
	output = [];

	var rows = active ? self.activeRows : self.rows;

	rows.forEach(function(row){
		output.push(row.getData(transform || "data"));
	});

	return output;
};

RowManager.prototype.getComponents = function(active){
	var self = this,
	output = [];

	var rows = active ? self.activeRows : self.rows;

	rows.forEach(function(row){
		output.push(row.getComponent());
	});

	return output;
}

RowManager.prototype.getDataCount = function(active){
	return active ? this.activeRows.length : this.rows.length;
};

RowManager.prototype._genRemoteRequest = function(){
	var self = this,
	table = self.table,
	options = table.options,
	params = {};

	if(table.modExists("page")){
		//set sort data if defined
		if(options.ajaxSorting){
			let sorters = self.table.modules.sort.getSort();

			sorters.forEach(function(item){
				delete item.column;
			});

			params[self.table.modules.page.paginationDataSentNames.sorters] = sorters;
		}

		//set filter data if defined
		if(options.ajaxFiltering){
			let filters = self.table.modules.filter.getFilters(true, true);

			params[self.table.modules.page.paginationDataSentNames.filters] = filters;
		}


		self.table.modules.ajax.setParams(params, true);
	}

	table.modules.ajax.sendRequest()
	.then((data)=>{
		self.setData(data);
	})
	.catch((e)=>{});

};

//choose the path to refresh data after a filter update
RowManager.prototype.filterRefresh = function(){
	var table = this.table,
	options = table.options,
	left = this.scrollLeft;


	if(options.ajaxFiltering){
		if(options.pagination == "remote" && table.modExists("page")){
			table.modules.page.reset(true);
			table.modules.page.setPage(1).then(()=>{}).catch(()=>{});
		}else if(options.ajaxProgressiveLoad){
			table.modules.ajax.loadData().then(()=>{}).catch(()=>{});
		}else{
			//assume data is url, make ajax call to url to get data
			this._genRemoteRequest();
		}
	}else{
		this.refreshActiveData("filter");
	}

	this.scrollHorizontal(left);
};

//choose the path to refresh data after a sorter update
RowManager.prototype.sorterRefresh = function(loadOrignalData){
	var table = this.table,
	options = this.table.options,
	left = this.scrollLeft;

	if(options.ajaxSorting){
		if((options.pagination == "remote" || options.progressiveLoad) && table.modExists("page")){
			table.modules.page.reset(true);
			table.modules.page.setPage(1).then(()=>{}).catch(()=>{});
		}else if(options.ajaxProgressiveLoad){
			table.modules.ajax.loadData().then(()=>{}).catch(()=>{});
		}else{
			//assume data is url, make ajax call to url to get data
			this._genRemoteRequest();
		}
	}else{
		this.refreshActiveData(loadOrignalData ? "filter" : "sort");
	}

	this.scrollHorizontal(left);
};

RowManager.prototype.scrollHorizontal = function(left){
	this.scrollLeft = left;
	this.element.scrollLeft = left;

	if(this.table.options.groupBy){
		this.table.modules.groupRows.scrollHeaders(left);
	}

	if(this.table.modExists("columnCalcs")){
		this.table.modules.columnCalcs.scrollHorizontal(left);
	}
};

//set active data set
RowManager.prototype.refreshActiveData = function(stage, skipStage, renderInPosition){
	var self = this,
	table = this.table,
	displayIndex;

	if(self.table.modExists("edit")){
		self.table.modules.edit.cancelEdit();
	}

	if(!stage){
		stage = "all";
	}

	if(table.options.selectable && !table.options.selectablePersistence && table.modExists("selectRow")){
		table.modules.selectRow.deselectRows();
	}

	//cascade through data refresh stages
	switch(stage){
		case "all":

		case "filter":
		if(!skipStage){
			if(table.modExists("filter")){
				self.setActiveRows(table.modules.filter.filter(self.rows));
			}else{
				self.setActiveRows(self.rows.slice(0));
			}
		}else{
			skipStage = false;
		}

		case "sort":
		if(!skipStage){
			if(table.modExists("sort")){
				table.modules.sort.sort(this.activeRows);
			}
		}else{
			skipStage = false;
		}

		//generic stage to allow for pipeline trigger after the data manipulation stage
		case "display":
		this.resetDisplayRows();

		case "freeze":
		if(!skipStage){
			if(this.table.modExists("frozenRows")){
				if(table.modules.frozenRows.isFrozen()){
					if(!table.modules.frozenRows.getDisplayIndex()){
						table.modules.frozenRows.setDisplayIndex(this.getNextDisplayIndex());
					}

					displayIndex = table.modules.frozenRows.getDisplayIndex();

					displayIndex = self.setDisplayRows(table.modules.frozenRows.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);

					if(displayIndex !== true){
						table.modules.frozenRows.setDisplayIndex(displayIndex);
					}
				}
			}
		}else{
			skipStage = false;
		}

		case "group":
		if(!skipStage){
			if(table.options.groupBy && table.modExists("groupRows")){

				if(!table.modules.groupRows.getDisplayIndex()){
					table.modules.groupRows.setDisplayIndex(this.getNextDisplayIndex());
				}

				displayIndex = table.modules.groupRows.getDisplayIndex();

				displayIndex = self.setDisplayRows(table.modules.groupRows.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);

				if(displayIndex !== true){
					table.modules.groupRows.setDisplayIndex(displayIndex);
				}
			}
		}else{
			skipStage = false;
		}



		case "tree":

		if(!skipStage){
			if(table.options.dataTree && table.modExists("dataTree")){
				if(!table.modules.dataTree.getDisplayIndex()){
					table.modules.dataTree.setDisplayIndex(this.getNextDisplayIndex());
				}

				displayIndex = table.modules.dataTree.getDisplayIndex();

				displayIndex = self.setDisplayRows(table.modules.dataTree.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);

				if(displayIndex !== true){
					table.modules.dataTree.setDisplayIndex(displayIndex);
				}
			}
		}else{
			skipStage = false;
		}

		if(table.options.pagination && table.modExists("page") && !renderInPosition){
			if(table.modules.page.getMode() == "local"){
				table.modules.page.reset();
			}
		}

		case "page":
		if(!skipStage){
			if(table.options.pagination && table.modExists("page")){

				if(!table.modules.page.getDisplayIndex()){
					table.modules.page.setDisplayIndex(this.getNextDisplayIndex());
				}

				displayIndex = table.modules.page.getDisplayIndex();

				if(table.modules.page.getMode() == "local"){
					table.modules.page.setMaxRows(this.getDisplayRows(displayIndex - 1).length);
				}


				displayIndex = self.setDisplayRows(table.modules.page.getRows(this.getDisplayRows(displayIndex - 1)), displayIndex);

				if(displayIndex !== true){
					table.modules.page.setDisplayIndex(displayIndex);
				}
			}
		}else{
			skipStage = false;
		}
	}


	if(Tabulator.prototype.helpers.elVisible(self.element)){
		if(renderInPosition){
			self.reRenderInPosition();
		}else{
			self.renderTable();
			if(table.options.layoutColumnsOnNewData){
				self.table.columnManager.redraw(true);
			}
		}
	}

	if(table.modExists("columnCalcs")){
		table.modules.columnCalcs.recalc(this.activeRows);
	}
};

RowManager.prototype.setActiveRows = function(activeRows){
	this.activeRows = activeRows;
	this.activeRowsCount = this.activeRows.length;
};

//reset display rows array
RowManager.prototype.resetDisplayRows = function(){
	this.displayRows = [];

	this.displayRows.push(this.activeRows.slice(0));

	this.displayRowsCount = this.displayRows[0].length;

	if(this.table.modExists("frozenRows")){
		this.table.modules.frozenRows.setDisplayIndex(0);
	}

	if(this.table.options.groupBy && this.table.modExists("groupRows")){
		this.table.modules.groupRows.setDisplayIndex(0);
	}

	if(this.table.options.pagination && this.table.modExists("page")){
		this.table.modules.page.setDisplayIndex(0);
	}
};


RowManager.prototype.getNextDisplayIndex = function(){
	return this.displayRows.length;
};

//set display row pipeline data
RowManager.prototype.setDisplayRows = function(displayRows, index){

	var output = true;

	if(index && typeof this.displayRows[index] != "undefined"){
		this.displayRows[index] = displayRows;
		output = true;
	}else{
		this.displayRows.push(displayRows)
		output = index = this.displayRows.length -1;
	}

	if(index == this.displayRows.length -1){
		this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
	}

	return output;
};

RowManager.prototype.getDisplayRows = function(index){
	if(typeof index == "undefined"){
		return this.displayRows.length ? this.displayRows[this.displayRows.length -1] : [];
	}else{
		return this.displayRows[index] || [];
	}

};


RowManager.prototype.getVisibleRows = function(viewable){
	var topEdge = this.element.scrollTop,
	bottomEdge = this.element.clientHeight + topEdge,
	topFound = false,
	topRow = 0,
	bottomRow = 0,
	rows = this.getDisplayRows();

	if(viewable){

		this.getDisplayRows();

		for(var i = this.vDomTop; i <= this.vDomBottom; i++){

			if(rows[i]){
				if(!topFound){
					if((topEdge - rows[i].getElement().offsetTop) >= 0){
						topRow = i;
					}else{
						topFound = true;
					}
				}else{
					if(bottomEdge - rows[i].getElement().offsetTop >= 0){
						bottomRow = i;
					}else{
						break;
					}
				}
			}
		}
	}else{
		topRow = this.vDomTop;
		bottomRow = this.vDomBottom;
	}

	return rows.slice(topRow, bottomRow + 1);
};


//repeat action accross display rows
RowManager.prototype.displayRowIterator = function(callback){
	this.displayRows.forEach(callback);

	this.displayRowsCount = this.displayRows[this.displayRows.length -1].length;
};

//return only actual rows (not group headers etc)
RowManager.prototype.getRows = function(){
	return this.rows;
};

///////////////// Table Rendering /////////////////

//trigger rerender of table in current position
RowManager.prototype.reRenderInPosition = function(callback){
	if(this.getRenderMode() == "virtual"){

		var scrollTop = this.element.scrollTop;
		var topRow = false;
		var topOffset = false;

		var left = this.scrollLeft;

		var rows = this.getDisplayRows();

		for(var i = this.vDomTop; i <= this.vDomBottom; i++){

			if(rows[i]){
				var diff = scrollTop - rows[i].getElement().offsetTop;

				if(topOffset === false || Math.abs(diff) < topOffset){
					topOffset = diff;
					topRow = i;
				}else{
					break;
				}
			}
		}

		if(callback){
			callback();
		}

		this._virtualRenderFill((topRow === false ? this.displayRowsCount - 1 : topRow), true, topOffset || 0);

		this.scrollHorizontal(left);
	}else{
		this.renderTable();

		if(callback){
			callback();
		}
	}
};

RowManager.prototype.setRenderMode = function(){
	if((this.table.element.clientHeight || this.table.options.height) && this.table.options.virtualDom){
		this.renderMode = "virtual";
	}else{
		this.renderMode = "classic";
	}
};


RowManager.prototype.getRenderMode = function(){
	return this.renderMode;
};

RowManager.prototype.renderTable = function(){
	var self = this;

	self.table.options.renderStarted.call(this.table);

	self.element.scrollTop = 0;

	switch(self.renderMode){
		case "classic":
		self._simpleRender();
		break;

		case "virtual":
		self._virtualRenderFill();
		break;
	}

	if(self.firstRender){
		if(self.displayRowsCount){
			self.firstRender = false;
			self.table.modules.layout.layout();
		}else{
			self.renderEmptyScroll();
		}
	}

	if(self.table.modExists("frozenColumns")){
		self.table.modules.frozenColumns.layout();
	}


	if(!self.displayRowsCount){
		if(self.table.options.placeholder){

			if(this.renderMode){
				self.table.options.placeholder.setAttribute("tabulator-render-mode", this.renderMode);
			}

			self.getElement().appendChild(self.table.options.placeholder);
		}
	}

	self.table.options.renderComplete.call(this.table);
};

//simple render on heightless table
RowManager.prototype._simpleRender = function(){
	this._clearVirtualDom();

	if(this.displayRowsCount){
		this.checkClassicModeGroupHeaderWidth();
	}else{
		this.renderEmptyScroll();
	}
};

RowManager.prototype.checkClassicModeGroupHeaderWidth = function(){
	var self = this,
	element = this.tableElement,
	onlyGroupHeaders = true;

	self.getDisplayRows().forEach(function(row, index){
		self.styleRow(row, index);
		element.appendChild(row.getElement());
		row.initialize(true);

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

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

//show scrollbars on empty table div
RowManager.prototype.renderEmptyScroll = function(){
	this.tableElement.style.minWidth = this.table.columnManager.getWidth() + "px";
	this.tableElement.style.minHeight = "1px";
	this.tableElement.style.visibility = "hidden";
};

RowManager.prototype._clearVirtualDom = function(){
	var element = this.tableElement;

	if(this.table.options.placeholder && this.table.options.placeholder.parentNode){
		this.table.options.placeholder.parentNode.removeChild(this.table.options.placeholder);
	}

	// element.children.detach();
	while(element.firstChild) element.removeChild(element.firstChild);

	element.style.paddingTop = "";
	element.style.paddingBottom = "";
	element.style.minWidth = "";
	element.style.minHeight = "";
	element.style.visibility = "";

	this.scrollTop = 0;
	this.scrollLeft = 0;
	this.vDomTop = 0;
	this.vDomBottom = 0;
	this.vDomTopPad = 0;
	this.vDomBottomPad = 0;
};

RowManager.prototype.styleRow = function(row, index){
	var rowEl = row.getElement();

	if(index % 2){
		rowEl.classList.add("tabulator-row-even");
		rowEl.classList.remove("tabulator-row-odd");
	}else{
		rowEl.classList.add("tabulator-row-odd");
		rowEl.classList.remove("tabulator-row-even");
	}
};

//full virtual render
RowManager.prototype._virtualRenderFill = function(position, forceMove, offset){
	var self = this,
	element = self.tableElement,
	holder = self.element,
	topPad = 0,
	rowsHeight = 0,
	topPadHeight = 0,
	i = 0,
	onlyGroupHeaders = true,
	rows = self.getDisplayRows();

	position = position || 0;

	offset = offset || 0;

	if(!position){
		self._clearVirtualDom();
	}else{
		while(element.firstChild) element.removeChild(element.firstChild);

		//check if position is too close to bottom of table
		let heightOccupied  = (self.displayRowsCount - position + 1) * self.vDomRowHeight;

		if(heightOccupied  < self.height){
			position -= Math.ceil((self.height - heightOccupied ) / self.vDomRowHeight);

			if(position < 0){
				position = 0;
			}
		}

		//calculate initial pad
		topPad = Math.min(Math.max(Math.floor(self.vDomWindowBuffer / self.vDomRowHeight),  self.vDomWindowMinMarginRows), position);
		position -= topPad;
	}

	if(self.displayRowsCount && Tabulator.prototype.helpers.elVisible(self.element)){

		self.vDomTop = position;

		self.vDomBottom = position -1;

		while ((rowsHeight <= self.height + self.vDomWindowBuffer || i < self.vDomWindowMinTotalRows) && self.vDomBottom < self.displayRowsCount -1){
			var index = self.vDomBottom + 1,
			row = rows[index],
			rowHeight = 0;

			self.styleRow(row, index);

			element.appendChild(row.getElement());
			if(!row.initialized){
				row.initialize(true);
			}else{
				if(!row.heightInitialized){
					row.normalizeHeight(true);
				}
			}

			rowHeight = row.getHeight();

			if(i < topPad){
				topPadHeight += rowHeight;
			}else{
				rowsHeight += rowHeight;
			}


			if(rowHeight > this.vDomWindowBuffer){
				this.vDomWindowBuffer = rowHeight * 2;
			}

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

			self.vDomBottom ++;
			i++;
		}

		if(!position){
			this.vDomTopPad = 0;
			//adjust rowheight to match average of rendered elements
			self.vDomRowHeight = Math.floor((rowsHeight + topPadHeight) / i);
			self.vDomBottomPad = self.vDomRowHeight * (self.displayRowsCount - self.vDomBottom -1);

			self.vDomScrollHeight = topPadHeight + rowsHeight + self.vDomBottomPad - self.height;
		}else{
			self.vDomTopPad = !forceMove ? self.scrollTop - topPadHeight : (self.vDomRowHeight * this.vDomTop) + offset;
			self.vDomBottomPad = self.vDomBottom == self.displayRowsCount-1 ? 0 : Math.max(self.vDomScrollHeight - self.vDomTopPad - rowsHeight - topPadHeight, 0);
		}

		element.style.paddingTop = self.vDomTopPad + "px";
		element.style.paddingBottom = self.vDomBottomPad + "px";

		if(forceMove){
			this.scrollTop = self.vDomTopPad + (topPadHeight) + offset - (this.element.scrollWidth > this.element.clientWidth ? this.element.offsetHeight - this.element.clientHeight : 0);
		}

		this.scrollTop = Math.min(this.scrollTop, this.element.scrollHeight - this.height);

		//adjust for horizontal scrollbar if present (and not at top of table)
		if(this.element.scrollWidth > this.element.offsetWidth && forceMove){
			this.scrollTop += this.element.offsetHeight - this.element.clientHeight;
		}

		this.vDomScrollPosTop = this.scrollTop;
		this.vDomScrollPosBottom = this.scrollTop;

		holder.scrollTop = this.scrollTop;

		element.style.minWidth = onlyGroupHeaders ? self.table.columnManager.getWidth() + "px" : "";

		if(self.table.options.groupBy){
			if(self.table.modules.layout.getMode() != "fitDataFill" && self.displayRowsCount == self.table.modules.groupRows.countGroups()){
				self.tableElement.style.minWidth = self.table.columnManager.getWidth();
			}
		}

	}else{
		this.renderEmptyScroll();
	}
};

//handle vertical scrolling
RowManager.prototype.scrollVertical = function(dir){
	var topDiff = this.scrollTop - this.vDomScrollPosTop;
	var bottomDiff = this.scrollTop - this.vDomScrollPosBottom;
	var margin = this.vDomWindowBuffer * 2;

	if(-topDiff > margin || bottomDiff > margin){
		//if big scroll redraw table;
		var left = this.scrollLeft;
		this._virtualRenderFill(Math.floor((this.element.scrollTop / this.element.scrollHeight) * this.displayRowsCount));
		this.scrollHorizontal(left);
	}else{

		if(dir){
			//scrolling up
			if(topDiff < 0){
				this._addTopRow(-topDiff);
			}

			if(bottomDiff < 0){

				//hide bottom row if needed
				if(this.vDomScrollHeight - this.scrollTop > this.vDomWindowBuffer){
					this._removeBottomRow(-bottomDiff);
				}
			}
		}else{
			//scrolling down
			if(topDiff >= 0){

				//hide top row if needed
				if(this.scrollTop > this.vDomWindowBuffer){
					this._removeTopRow(topDiff);
				}
			}

			if(bottomDiff >= 0){
				this._addBottomRow(bottomDiff);
			}
		}
	}
};

RowManager.prototype._addTopRow = function(topDiff, i=0){
	var table = this.tableElement,
	rows = this.getDisplayRows();

	if(this.vDomTop){
		let index = this.vDomTop -1,
		topRow = rows[index],
		topRowHeight = topRow.getHeight() || this.vDomRowHeight;

		//hide top row if needed
		if(topDiff >= topRowHeight){
			this.styleRow(topRow, index);
			table.insertBefore(topRow.getElement(), table.firstChild);
			if(!topRow.initialized || !topRow.heightInitialized){
				this.vDomTopNewRows.push(topRow);

				if(!topRow.heightInitialized){
					topRow.clearCellHeight();
				}
			}
			topRow.initialize();

			this.vDomTopPad -= topRowHeight;

			if(this.vDomTopPad < 0){
				this.vDomTopPad = index * this.vDomRowHeight;
			}

			if(!index){
				this.vDomTopPad = 0;
			}

			table.style.paddingTop = this.vDomTopPad + "px";
			this.vDomScrollPosTop -= topRowHeight;
			this.vDomTop--;
		}

		topDiff = -(this.scrollTop - this.vDomScrollPosTop);

		if(topRow.getHeight() > this.vDomWindowBuffer){
			this.vDomWindowBuffer = topRow.getHeight() * 2;
		}

		if(i < this.vDomMaxRenderChain && this.vDomTop && topDiff >= (rows[this.vDomTop -1].getHeight() || this.vDomRowHeight)){
			this._addTopRow(topDiff, i+1);
		}else{
			this._quickNormalizeRowHeight(this.vDomTopNewRows);
		}

	}

};

RowManager.prototype._removeTopRow = function(topDiff){
	var table = this.tableElement,
	topRow = this.getDisplayRows()[this.vDomTop],
	topRowHeight = topRow.getHeight() || this.vDomRowHeight;

	if(topDiff >= topRowHeight){

		var rowEl = topRow.getElement();
		rowEl.parentNode.removeChild(rowEl);

		this.vDomTopPad += topRowHeight;
		table.style.paddingTop = this.vDomTopPad + "px";
		this.vDomScrollPosTop += this.vDomTop ? topRowHeight : topRowHeight + this.vDomWindowBuffer;
		this.vDomTop++;

		topDiff = this.scrollTop - this.vDomScrollPosTop;

		this._removeTopRow(topDiff);
	}

};

RowManager.prototype._addBottomRow = function(bottomDiff, i=0){
	var table = this.tableElement,
	rows = this.getDisplayRows();

	if(this.vDomBottom < this.displayRowsCount -1){
		let index = this.vDomBottom + 1,
		bottomRow = rows[index],
		bottomRowHeight = bottomRow.getHeight() || this.vDomRowHeight;

		//hide bottom row if needed
		if(bottomDiff >= bottomRowHeight){
			this.styleRow(bottomRow, index);
			table.appendChild(bottomRow.getElement());

			if(!bottomRow.initialized || !bottomRow.heightInitialized){
				this.vDomBottomNewRows.push(bottomRow);

				if(!bottomRow.heightInitialized){
					bottomRow.clearCellHeight();
				}
			}

			bottomRow.initialize();

			this.vDomBottomPad -= bottomRowHeight;

			if(this.vDomBottomPad < 0 || index == this.displayRowsCount -1){
				this.vDomBottomPad = 0;
			}

			table.style.paddingBottom = this.vDomBottomPad + "px";
			this.vDomScrollPosBottom += bottomRowHeight;
			this.vDomBottom++;
		}

		bottomDiff = this.scrollTop - this.vDomScrollPosBottom;

		if(bottomRow.getHeight() > this.vDomWindowBuffer){
			this.vDomWindowBuffer = bottomRow.getHeight() * 2;
		}

		if(i < this.vDomMaxRenderChain && this.vDomBottom < this.displayRowsCount -1 && bottomDiff >= (rows[this.vDomBottom + 1].getHeight() || this.vDomRowHeight)){
			this._addBottomRow(bottomDiff, i+1);
		}else{
			this._quickNormalizeRowHeight(this.vDomBottomNewRows);
		}
	}
};

RowManager.prototype._removeBottomRow = function(bottomDiff){
	var table = this.tableElement,
	bottomRow = this.getDisplayRows()[this.vDomBottom],
	bottomRowHeight = bottomRow.getHeight() || this.vDomRowHeight;

	if(bottomDiff >= bottomRowHeight){

		var rowEl = bottomRow.getElement();

		if(rowEl.parentNode){
			rowEl.parentNode.removeChild(rowEl);
		}

		this.vDomBottomPad += bottomRowHeight;

		if(this.vDomBottomPad < 0){
			this.vDomBottomPad = 0;
		}

		table.style.paddingBottom = this.vDomBottomPad + "px";
		this.vDomScrollPosBottom -= bottomRowHeight;
		this.vDomBottom--;

		bottomDiff = -(this.scrollTop - this.vDomScrollPosBottom);

		this._removeBottomRow(bottomDiff);
	}
};

RowManager.prototype._quickNormalizeRowHeight = function(rows){
	rows.forEach(function(row){
		row.calcHeight();
	});

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

	rows.length = 0;
};

//normalize height of active rows
RowManager.prototype.normalizeHeight = function(){
	this.activeRows.forEach(function(row){
		row.normalizeHeight();
	});
};

//adjust the height of the table holder to fit in the Tabulator element
RowManager.prototype.adjustTableSize = function(){

	if(this.renderMode === "virtual"){
		this.height = this.element.clientHeight;
		this.vDomWindowBuffer = this.table.options.virtualDomBuffer || this.height;

		let otherHeight = this.columnManager.getElement().offsetHeight + (this.table.footerManager && !this.table.footerManager.external ? this.table.footerManager.getElement().offsetHeight : 0);

		this.element.style.minHeight = "calc(100% - " + otherHeight + "px)";
		this.element.style.height = "calc(100% - " + otherHeight + "px)";
		this.element.style.maxHeight = "calc(100% - " + otherHeight + "px)";
	}
};

//renitialize all rows
RowManager.prototype.reinitialize = function(){
	this.rows.forEach(function(row){
		row.reinitialize();
	});
};


//redraw table
RowManager.prototype.redraw = function (force){
	var pos = 0,
	left = this.scrollLeft;

	this.adjustTableSize();

	this.table.tableWidth = this.table.element.clientWidth;

	if(!force){
		if(this.renderMode == "classic"){

			if(this.table.options.groupBy){
				this.refreshActiveData("group", false, false);
			}else{
				this._simpleRender();
			}

		}else{
			this.reRenderInPosition();
			this.scrollHorizontal(left);
		}

		if(!this.displayRowsCount){
			if(this.table.options.placeholder){
				this.getElement().appendChild(this.table.options.placeholder);
			}
		}

	}else{
		this.renderTable();
	}
};

RowManager.prototype.resetScroll = function(){
	this.element.scrollLeft = 0;
	this.element.scrollTop = 0;

	if(this.table.browser === "ie"){
		var event = document.createEvent("Event");
		event.initEvent("scroll", false, true);
		this.element.dispatchEvent(event);
	}else{
		this.element.dispatchEvent(new Event('scroll'));
	}
};
