/**
 * Used for different actions with tables.
 * @package com.componence.tablemanager
 */
 
/**
 * @access public
 * @type String
 * @constructor
 */
function TableManager() {
	this.DEFAULT_HTML = "&nbsp;";
	
	/**
	 * Constant
	 * @access public
	 */
	this.ADD_AT_START = 1;
	/**
	 * Constant
	 * @access public
	 */
	this.ADD_BEFORE = 2;
	/**
	 * Constant
	 * @access public
	 */
	this.ADD_AFTER = 3;
	/**
	 * Constant
	 * @access public
	 */
	this.ADD_AT_END = 4;
	/**
	 * Constant
	 * @access public
	 */
	this.MERGE_TOP = 1;
	/**
	 * Constant
	 * @access public
	 */
	this.MERGE_RIGHT = 2;
	/**
	 * Constant
	 * @access public
	 */
	this.MERGE_BOTTOM = 3;
	/**
	 * Constant
	 * @access public
	 */
	this.MERGE_LEFT = 4;
	/**
	 * Constant
	 * @access public
	 */
	this.SPLIT_HORIZONTAL = 1;
	/**
	 * Constant
	 * @access public
	 */
	this.SPLIT_VERTICAL = 2;
	
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS = {};
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS["no.right.cell"] = "This cell cannot be merged, because it is the last cell in the column.";
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS["is.defferent.row"] = "Only cells with the same row spanning can be merged.";
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS["no.below.cell"] = "This cell cannot be merged, because it is the last cell in the row.";
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS["is.different.span"] = "Only cells with the same cell spanning can be merged.";
	/**
	 * Constant
	 * @access public
	 */
	this.ALERTS["is.not.merged"] = "Can split just only merged cells";
	
	/**
	 * @access private
	 */
	function __error__(key) {
		var val = TableManager.ALERTS[key];
		if(val) alert(val);
		return;
	}
	
	/**
	 * Removes table row
	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */
	function removeRow(cell) {
		var tableElement = this.getTable(cell);
		if(cell && tableElement) {
			var tableArr = getTableArray(tableElement);
			var cellCoords = getCellCoords(cell, tableArr);
			for(var i=0; i<tableArr[cellCoords.row].length; i++) {
				var curCell = tableArr[cellCoords.row][i];
				if(!curCell) continue;
				if(curCell.rowSpan>1){
					var curCellCoords = getCellCoords(curCell, tableArr);
					if(curCellCoords.row==cellCoords.row){
						var cellIndex = this.getCellIndex(curCell);
						if(tableElement.rows[tableElement.rows.length-1]!=curCell.parentNode){
							var nextRow = getNextSibling(curCell.parentNode);
							var cellUnder = nextRow.cells[cellIndex];
							nextRow.insertBefore(curCell, cellUnder);
						}
					}
					curCell.rowSpan--;
				}
			}
			return removeNode(cell.parentNode);
		}
		return false;
	}
	this.removeRow = removeRow;


	/**
	 * Removes table column
	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */	
	function removeColumn(cell) {
		var tableElement = this.getTable(cell);
		var tableArr = getTableArray(tableElement);
		var prevCell = null;
		var cellCoords = getCellCoords(cell, tableArr);
		for(var i=0; i<tableArr.length; i++) {
			var curCell = tableArr[i][cellCoords.col];
			prevCell = curCell;
			if(curCell.colSpan<2){
				removeNode(curCell);
			}else{
				curCell.colSpan--;
			}
		}
		return true;
	}
	this.removeColumn = removeColumn;

	/**
 	 * Splits table cells
 	 * @param cell HTMLTableCellElement
 	 * @param dir Number Direction of splitting
	 * @access public
	 * @type Boolean
	 */
	function split(/*HTMLTableCellElement*/ cell, /*int*/ dir){
		if(dir == this.SPLIT_HORIZONTAL) return this.splitHorizontal(cell);
		else if(dir == this.SPLIT_VERTICAL) return this.splitVertical(cell);
	}
	this.split = split;

	/**
 	 * Splits table cells to the right
 	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */
	function splitHorizontal(cell) {
		var w, cellObject, i;
		var tableElement = this.getTable(cell);
		if(cell){
			if(cell.colSpan < 2) {
				// @TODO handle this:
				//return __error__("is.not.merged");
				var tableArr = getTableArray(tableElement);
				var cellCoords = getCellCoords(cell, tableArr);
				var newCell = cell.parentNode.insertCell(this.getCellIndex(cell)+1);
				var prevCellChanged = null;
				newCell.rowSpan = cell.rowSpan;
				newCell.innerHTML = this.DEFAULT_HTML;
				for(i=0; i<tableArr.length; i++) {
					var curCell = tableArr[i][cellCoords.col];
					if(i!=cellCoords.row && cell!=curCell && prevCellChanged!=curCell){
						prevCellChanged = curCell;
						curCell.colSpan = curCell.colSpan+1;
					}
				}
				return true;
			} else {
				i = this.getCellIndex(cell);
				cellObject = cell.parentNode.insertCell(i + 1);
				cellObject.rowSpan = cell.rowSpan;
				cellObject.innerHTML = this.DEFAULT_HTML;
				cellObject.vAlign = "top";
				cell.colSpan = cell.colSpan - 1;
				if(cell.colSpan < 2) {
					if(cell.width) { 
						cell.width = (String(cell.width).replace("%", "") / 2) + "%";
						cellObject.width = cell.width;
					}
				} else {
					if(cell.width) {
						w = String(cell.width).replace("%", "") / cell.colSpan;
						cell.width = (w * (cell.colSpan - 1)) + "%";
						cellObject.width = width + "%";
					}
				}
				return true;
			}
		}
		return false;
	}
	this.splitHorizontal = splitHorizontal;

	/**
	 * Unmerge table cells down
 	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */
	function splitVertical(cell) {
		var i, rowObject, cellObject, tableElement, rowElement;
		tableElement = this.getTable(cell);
		if(cell){
			rowElement = cell.parentNode;
			if(cell.rowSpan < 2) {
				// @TODO handle this:
				//return __error__("is.not.merged");
				var n = 0
				var rowIndex = this.getRowIndex(rowElement, tableElement) + 1;
				while (rowElement.cells[n]){
					if(rowElement.cells[n] != cell) rowElement.cells[n].rowSpan++;
					n++;
				}
				rowObject = tableElement.insertRow(rowIndex);
				cellObject = rowObject.insertCell(0);
			} else {
				i = this.getRowIndex(rowElement, tableElement) + cell.rowSpan - 1;
				rowObject = rowElement.parentNode.rows[i];
				if(!rowObject) return __error__("is.not.merged");
				var z=0;
				while(true){
					try{
						cellObject = rowObject.insertCell(this.getCellIndex(cell)-z);
						break;
					}catch(err){
						z++;
					}
				}
				cell.rowSpan -= 1;
			}
			cellObject.width = cell.width;
			cellObject.innerHTML = this.DEFAULT_HTML;
			cellObject.vAlign = "top";
			cellObject.colSpan = cell.colSpan;
			return true;
		}
		return false;
	}
	this.splitVertical = splitVertical;


	/**
	 * Merge table cells
 	 * @param cell HTMLTableCellElement
 	 * @param dir Number Direction for merging
	 * @access public
	 * @type Boolean
	 */
	function merge(/*HTMLTableCellElement*/ cell, /*int*/ dir) {
		if(dir == this.MERGE_BOTTOM) return this.mergeDown(cell);
		else if(dir == this.MERGE_RIGHT) return this.mergeRight(cell);
	}
	this.merge = merge;

	/**
	 * Merge table cells to the right
 	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */
	function mergeRight(cell){
		var w;
		if(cell) {
			if(!cell.nextSibling) return __error__("no.right.cell");
			if(cell.rowSpan != cell.nextSibling.rowSpan) return __error__("is.defferent.row");
			if(cell.nextSibling.innerHTML != '&nbsp;') {
				if(cell.innerHTML != '&nbsp;') cell.innerHTML += " " + cell.nextSibling.innerHTML;
				else cell.innerHTML += cell.nextSibling.innerHTML;
			}
			w = cell.width;
			if(w) cell.width = (((w.replace('%', '') * 100) + (cell.nextSibling.width.replace('%', '') * 100)) / 100) + '%';
			cell.colSpan += cell.nextSibling.colSpan;
			cell.parentNode.removeChild(cell.nextSibling);
			return true;
		}
		return false;
	}
	this.mergeRight = mergeRight;
	
	/**
	 * Merge table cells down
 	 * @param cell HTMLTableCellElement
	 * @access public
	 * @type Boolean
	 */
	function mergeDown(cell) {
		var tableElement, rowElement;
		tableElement = this.getTable(cell);
		rowElement = cell.parentNode;
		
		if(cell && rowElement && tableElement) {
			var tableArr = getTableArray(tableElement);
			var cellCoords = getCellCoords(cell, tableArr);
			if(cellCoords.row+cell.rowSpan==tableArr.length) return __error__("no.below.cell");
			var targetCell = tableArr[cellCoords.row+cell.rowSpan][cellCoords.col];
			cell.innerHTML += targetCell.innerHTML;
			cell.rowSpan += targetCell.rowSpan;
			cell.vAlign = "top";
			removeNode(targetCell);
			return true;
		}
		return false;
	}
	this.mergeDown = mergeDown;
	
	/**
	 * Adds column
 	 * @param cell HTMLTableCellElement
 	 * @param pos Number Direction for adding
	 * @access public
	 * @type Array
	 */
	function addColumn(/*HTMLTableCellElement*/ cell, /*int*/ pos) {
		var cellIndex;
		var tableElement = this.getTable(cell);
		var i = this.getCellIndex(cell);
		if(pos==this.ADD_AT_START) cellIndex = 0;
		else if(pos == this.ADD_BEFORE) cellIndex = i;
		else if(pos == this.ADD_AFTER) cellIndex = i + 1;
		else if(pos == this.ADD_AT_END) cellIndex = cell.parentNode.cells.length;
		return this.addColumnAt(cell, cellIndex);
	}
	this.addColumn = addColumn;

	/**
	 * Adds column by index
 	 * @param cell HTMLTableCellElement
 	 * @param index Number Index of column
	 * @access public
	 * @returns Array
	 * @type Array
	 */
	function addColumnAt(cell, index) {
		var result = [];
		var cellObject, colObject, tableElement;
		tableElement = this.getTable(cell);
		if(cell && tableElement) {
			if(index==cell.parentNode.cells.length || index==0){
				index = index!=0?-1:index;
				for(var i=0; i<tableElement.rows.length; i++){
					var newCell = tableElement.rows[i].insertCell(index);
					newCell.innerHTML = this.DEFAULT_HTML;
					result[result.length] = newCell;
				}
			}else{
				var targetCell = cell.parentNode.cells[index];
				var tableArr = getTableArray(tableElement);
				var targetCellCoords = getCellCoords(targetCell, tableArr);//this.getCellIndex(cell, tableElement);
				for(var i=0; i<tableArr.length; i++) {
					var curCell = tableArr[i][targetCellCoords.col];
					var prevCell = tableArr[i][targetCellCoords.col-1];
					if(curCell.colSpan<2 || curCell!=prevCell){
						index = this.getCellIndex(curCell);
						//var curCellIndex = this.getCellIndex(curCell, tableElement);
						var newCell = curCell.parentNode.insertCell(index);
						newCell.innerHTML = this.DEFAULT_HTML;
						result[result.length] = newCell;
						/*try{
						} catch(e){
							cellObject = tableElement.rows[j].insertCell(i-1);
							cellObject.innerHTML = "" + this.DEFAULT_HTML + "";
						}*/
					}else{
						curCell.colSpan++;
					}
				}
			}
		}
		return result;
	}
	this.addColumnAt = addColumnAt;
	
	/**
	 * Adds row
 	 * @param cell HTMLTableCellElement
 	 * @param pos Number Direction for adding
	 * @access public
	 * @returns HTMLTableRowElement
	 * @type Object
	 */
	function addRow(/*HTMLTableRowElement*/ row, /*int*/ pos) {
		if(row.tagName != "TR") 
			row = this.getRow(row);
		
		var realRowIndex;
		var tableElement = this.getTable(row);
		var i = this.getRowIndex(row, tableElement);
		if(pos==this.ADD_AT_START) realRowIndex = 0;
		else if(pos == this.ADD_BEFORE) realRowIndex = i;
		else if(pos == this.ADD_AFTER) realRowIndex = i + 1;
		else if(pos == this.ADD_AT_END) realRowIndex = -1;
		//if(realRowIndex+1==tableElement.rows.length && tableElement.rows.length>1) realRowIndex=-1;
		return this.addRowAt(row, realRowIndex);
	}
	this.addRow = addRow;
	
	/**
	 * Adds row by index
 	 * @param cell HTMLTableCellElement
 	 * @param index Number Index of row
	 * @access public
	 * @returns HTMLTableRowElement
	 * @type Object
	 */
	function addRowAt(row, i) {
		if(row.tagName != "TR") 
			row = this.getRow(row);
		var j, cellObject, rowObject;
		var tableElement = this.getTable(row);
		var tableArr = getTableArray(tableElement);
		if(row && tableElement) {
			rowObject = tableElement.insertRow(i);
			for(j=0; j<tableArr[0].length; j++) {
				cellObject = rowObject.insertCell(j);
				cellObject.innerHTML = this.DEFAULT_HTML;
			}
		}
		return rowObject;
	}
	this.addRowAt = addRowAt;
	
	/**
	 * @access private
	 * @param e HTMLElement any HTMLElement
	 * @param n String tag name
	 */
	function getParentNodeByTagName(e,n){
		if(!e) return null;
		while(e.tagName!=n){
			e=e.parentNode;
		}
		return e;
	}

	/**
	 * @access private
	 * @param e HTMLElement any HTMLElement in table
	 */
	function getTable(/*HTMLElement*/e){
		return getParentNodeByTagName(e, "TABLE");
	}
	this.getTable = getTable;
	
	/**
	 * @access private
	 * @param e HTMLElement any HTMLElement in row
	 */
	function getRow(e){
		return getParentNodeByTagName(e, "TR");
	}
	this.getRow = getRow;
	
	/**
	 * @access private
	 * @param e HTMLElement any HTMLElement in cell
	 */
	function getCell(e){
		return getParentNodeByTagName(e, "TD");
	}
	this.getCell = getCell;

	function getRowIndex(r, t){
		if(!t) t=this.getTable(r);
		for(var i=0; i<t.rows.length; i++) {
			if(t.rows[i] == r) return i;
		}
		return 0;
	}
	this.getRowIndex = getRowIndex;

	/**
	 * Gets cell Index
	 * @access private
	 * @param c TableCellElement
	 * @type int
	 */
	function getCellIndex(c) {
		var i, tr = c.parentNode;
		for(i=0; i<tr.cells.length; i++) {
			if(tr.cells[i] == c) return i;
		}
		return 0;
	}
	this.getCellIndex = getCellIndex;

	/**
	 * Returns table array, which contains cells
	 * [
	 * 	[ 
	 * 	 cellObj1, cellObj2, ...
	 * 	],
	 * 	.....
	 * ]
	 * The array helps to calculate the right count of cells, or the cell which is above the current cell, no matter if cells contain colSpan or rowSpan.
	 * For example, if row has 2 cells, but one cell has colSpan=2, the array will be like:
	 * [
	 * ....
	 * 	[
	 * 	 cellObj1, cellObj2, cellObj2
	 *  ]
	 * ....
	 * ]
	 *
	 * @access private
	 * @param t HTMLTableElement
	 */
	function getTableArray(t){
		var tableArray = [];
		var tmpNext = [];
		for(var i=0; i<t.rows.length; i++) {
			var c = 0;
			var cells = t.rows[i].cells;
			var tmp = tableArray[i]?tableArray[i]:[];
			var cellLength = cells.length;
			for(var j=0; j<cellLength; j++) {
				var cS = cells[j].colSpan;
				var rS = cells[j].rowSpan;
				var fixIndex = tmp[j]?(getNextFreeIndex(tmp,j)-j):0;
				for(var z=0; z<cS; z++) {
					tmp[j+fixIndex+z] = cells[j];
					for(var x=1; x<rS; x++) {
						if(!tableArray[i+x]) tableArray[i+x] = [];
						tableArray[i+x][j] = cells[j];
					}
				}
			}
			tableArray[i] = tmp;
		}
		return tableArray;
	}


	/**
	 * @access private
	 */
	function getNextFreeIndex(arr, i){
		i++;
		while(arr[i]) i++;
		return i;
	}
	
	/**
	 * Returns the position of cell in tableArray as object with properties "row" and "col"
	 *
	 * @access private
	 * @param cell HTMLTableCellElement
	 * @param arr TableArray
	 */
	function getCellCoords(cell, arr){
		for(var i=0; i<arr.length; i++) {
			for(var j=0; j<arr[i].length; j++) {
				if(arr[i][j]==cell) return {row:i,col:j};
			}
		}
		return null;
	}

	/**
	 * Removes the object <code>o</code> from the document hierarchy.<br>
	 * Returns a reference to the object that is removed.
	 * 
	 * @access private
	 * @link http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/removenode.asp
	 * @link http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/removechild.asp
	 */
	function removeNode(o){
		if(!o) return null;
		if(o.removeNode) return o.removeNode(true);
		else if(o.parentNode.removeChild) return o.parentNode.removeChild(o);
		return null;
	}

	/**
	 * @access private
	 */
	function getNextSibling (o) {
		o=o.nextSibling;
		while (!o.tagName){
			if(!o.nextSibling) return null;
			o=o.nextSibling;
		}
		return o;
	}
	
}
TableManager = new TableManager();
