/**
 * Aloha Table Plugin
 * ------------------
 * This plugin provides advanced support for manipulating tables in Aloha
 * Editables.
 * Nested tables are not support. If nested tables are pasted into the
 * editable, they will simply be left alone.
 * Each (non-nested) table in the editable will have a corresponding Aloha
 * Table instance created for it, which will maintain internal state, and
 * information related to its DOM element.
 *
 * @todo: - selectRow/selectColumn should take into account the helper row/column.
 *			ie: selectRow(0) and selectColumn(0), should be zero indexed
 */
define([
	'aloha',
	'jquery',
	'ui/scopes',
	'ui/dialog',
	'i18n!table/nls/i18n',
	'table/table-cell',
	'table/table-selection',
	'table/table-plugin-utils',
	'aloha/ephemera',
	'util/html',
	'util/dom2',
	'aloha/console'
], function (
	Aloha,
	jQuery,
	Scopes,
	Dialog,
	i18n,
	TableCell,
	TableSelection,
	Utils,
	Ephemera,
	Html,
	Dom,
    Console
) {
	var undefined = void 0;
	var GENTICS = window.GENTICS;

	/**
	 * Returns an Array with all elements and textnodes included in the 
	 * hierarchy of the element received. Is Similar to do 
	 * jQuery('*', element).contents(), the diference it's this function returns the 
	 * array in the correct order of apparition
	 * @example
	 * <pre>
	 *		&gt;p&lt;
	 *			textnode
	 *			<b>b textnode</b>
	 *			another text node
	 *		&gt;/p&lt;
	 *		
	 *		jQuery('*', lt).contents();
	 *			// returns ["textnode", "<b>", "another textnode", "b textnode"]
	 *		getPlainHierarchy(lt)
	 *			// returns ["textnode", "<b>", "b textnode", "another textnode"]
	 * </pre>
	 * 
	 * @return {Array.<HTMLElement|TextNode>}
	 */
	function getPlainHierarchy(element) {
		if (element.jquery) {
			element = element[0];
		}
		var i, result = [], child;
		for (i = 0; i < element.childNodes.length; i++) {
			child = element.childNodes[i];
			result.push(child);
			if (child.nodeType === 1) {
				result = result.concat(getPlainHierarchy(child));
			}
		}

		return result;
	}

	/**
	 * Find the first or the last element inside a table, even if in a td
	 * 
	 * @param {String} type Accepts two values: 'first' or 'last'
	 * @param {HTMLElement|jQuery} parent the parent element to search
	 * 
	 * @return {jQuery}
	 */
	function getNewSelectedElement(type, parent) {
		var toSelectElement;
		if ('first' === type) {
			toSelectElement = jQuery('[contenteditable]', parent).first()[0]
				.firstChild;
			if (undefined === toSelectElement) {
				toSelectElement = jQuery('*', parent).first()[0].firstChild;
			}
		} else if ('last' === type) {
			toSelectElement = getPlainHierarchy(jQuery('td:last', parent))
				.reverse()[0];
		}

		return toSelectElement;
	}

	/**
	 * Constructor of the table object
	 *
	 * @param table
	 *            the dom-representation of the held table
	 * @return void
	 */
	var Table = function ( table, tablePlugin ) {
		// set the table attribut "obj" as a jquery represenation of the dom-table
		this.obj = jQuery( table );

		correctTableStructure( this );

		if ( !this.obj.attr( 'id' ) ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// mark table id as ephemeral
		Ephemera.markAttr(this.obj, 'id');

		this.tablePlugin = tablePlugin;
		this.selection = new TableSelection( this );
		this.refresh();
	};

	jQuery.extend( Table.prototype, {
		/**
		 * Attribute holding the jQuery-table-represenation
		 */
		obj: undefined,

		/**
		 * The DOM-element of the outest div-container wrapped around the cell
		 */
		tableWrapper: undefined,

		/**
		 * An array of all Cells contained in the Table
		 *
		 * @see TableCell
		 */
		cells: undefined,

		/**
		 * Number of rows of the table
		 */
		numRows: undefined,

		/**
		 * Number of rows of the table
		 */
		numCols: undefined,

		/**
		 * Flag wether the table is active or not
		 */
		isActive: false,

		/**
		 * Flag wether the table is focused or not
		 */
		hasFocus: false,

		/**
		 * The editable which contains the table
		 */
		parentEditable: undefined,

		/**
		 * Flag to check if the mouse was pressed. For row- and column-selection.
		 */
		mousedown: false,

		/**
		 * ID of the column which was pressed when selecting columns
		 */
		clickedColumnId: -1,

		/**
		 * ID of the row which was pressed when selecting rows
		 */
		clickedRowId: -1,

		/**
		 * collection of columnindexes of the columns which should be selected
		 */
		columnsToSelect: [],

		/**
		 * collection of rowindexes of the rows which should be selected
		 */
		rowsToSelect: [],

		/**
		 * contains the plugin id used for interaction with the floating menu
		 */
		fmPluginId: undefined
	} );

	/**
	 * @hide
	 */
	Table.prototype.refresh = function () {
		// find the dimensions of the table
		this.numCols = this.countVirtualCols();

		var rows = this.getRows();
		this.numRows = rows.length;

		// init the cell-attribute with an empty array
		this.cells = [];

		// iterate over table cells and create Cell-objects
		for ( var i = 0; i < rows.length; i++ ) {
			var row = jQuery(rows[i]);
			var cols = row.children();
			for ( var j = 0; j < cols.length; j++ ) {
				var col = cols[j];
				var Cell = this.newCell( col );
			}
		}
	};

	Table.prototype.countVirtualCols = function () {
		var $firstRow = this.obj.children().children( 'tr:first-child' ).children();
		return $firstRow.length - $firstRow.filter( '.' + this.get( 'classLeftUpperCorner' ) ).length;
	};

	/**
	 * Wrapper-Mehotd to return a property of TablePlugin.get
	 *
	 * @see TablePlugin.get
	 * @param property
	 *            the property whichs value should be return
	 * @return the value associated with the property
	 */
	Table.prototype.get = function(property) {
		return this.tablePlugin.get(property);
	};

	/**
	 * Wrapper-Method for TablePlugin.set
	 *
	 * @see TablePlugin.set
	 * @param key
	 *            the key whichs value should be set
	 * @param value
	 *            the value for the key
	 * @return void
	 */
	Table.prototype.set = function(key, value) {
		this.tablePlugin.set(key, value);
	};

	/**
	 * Given an unbalanced table structure, pad it with the necessary cells to
	 * make it perfectly rectangular
	 *
	 * @param {Aloha.Table} tableObj
	 */
	function correctTableStructure ( tableObj ) {
		var table = tableObj.obj,

			i,
			j,
		    row,
		    rows = tableObj.getRows(),
		    rowsNum = rows.length,

			cols,
			colsNum,

		    colsCount,
		    maxColsCount = 0,
		    cachedColsCounts = [rowsNum],
		    colsCountDiff,
		    colSpan;

		for ( i = 0; i < rowsNum; i++ ) {
			row = jQuery( rows[ i ] );
			cols = row.children( 'td, th' );
			colsNum = cols.length;
			colsCount = Utils.cellIndexToGridColumn( rows, i, colsNum - 1 ) + 1;

			// Check if the last cell in this row has a col span, to account
			// for it in the total number of colums in this row

			colSpan = parseInt( cols.last().attr( 'colspan' ), 10 );

			if ( colSpan == 0 ) {
				// TODO: support colspan=0
				// http://dev.w3.org/html5/markup/td.html#td.attrs.colspan
				// http://www.w3.org/TR/html401/struct/tables.html#adef-colspan
				// The value zero ("0") means that the cell spans all columns
				// from the current column to the last column of the column
				// group (COLGROUP) in which the cel
			} else if ( !isNaN( colSpan ) ) {
				// The default value of this attribute is one ("1"), so where this
				// is the case, we will remove such superfluous colspan attributes
				if ( colSpan == 1 ) {
					cols.last().removeAttr( 'colspan' );
				}

				colsCount += ( colSpan - 1 );
			}

			// if a rowspan is set in the last element of the row, the row(s) below
			// are supposed to have one less column for every colspan the element has
			rowSpan = parseInt(cols.last().attr('rowspan'), 10);
			if (rowSpan > 1) {
				for (j = 1; j < rowSpan-1; j++) {
					if (colSpan > 1) {
						cachedColsCounts[i+j] += colSpan;
					} else {
						cachedColsCounts[i+j] += 1;
					}
				}
			}

			cachedColsCounts[i] += colsCount;

			if (cachedColsCounts[i] > maxColsCount) {
				maxColsCount = cachedColsCounts[i];
			}
		}

		for ( i = 0; i < rowsNum; i++ ) {
			colsCountDiff = maxColsCount - cachedColsCounts[ i ];
			if ( colsCountDiff > 0 ) {
				// Create as many td's as we need to complete the row
				jQuery( rows[ i ] ).append(
					( new Array( colsCountDiff + 1 ) ).join( '<td></td>' )
				);
			}
		}
	};

	/**
	 * If all of the selected cells have been set to the same predefined style,
	 * then its style-button is toggled on. Otherwise, all style-buttons are toggled off.
	 *
	 * @param selectedCells the cells to be checked
	 * @param config the list of styles as defined in the aloha-configuration
	 * @param items the multisplit-toggle-items
	 * @param button a multisplit-button
	 *
	 * @return void
	 */
	function setActiveStyle(selectedCells, config, items, button) {
		var className;
		var allSelected = false;

		// activate all formatting buttons
		for (var i = 0; i < items.length; i++) {
			button.showItem(items[i].name);
		}

		// clear active style block
		button.setActiveItem();

		// select class of first element as reference
		for (var i = 0; i < config.length; i++) {
			if (jQuery(selectedCells[0]).hasClass(config[i].cssClass)) {
				allSelected = true;
				className = config[i].name;
				break;
			}
		}

		// if all selected cells have the same class, set it as active
		jQuery(selectedCells).each(function(index) {
			if (!jQuery(this).hasClass(className)) {
				allSelected = false;
			}
		});
		if (allSelected) {
			button.setActiveItem(className);
		}
	}

	/**
	 * Transforms the existing dom-table into an editable aloha-table. In fact it
	 * replaces the td-elements with equivalent TableCell-elements
	 * with attached events.
	 * Furthermore it creates wrapping divs to realize a click-area for row- and
	 * column selection and also attaches events.
	 *
	 * @return void
	 */
	Table.prototype.activate = function () {
		if ( this.isActive ) {
			return;
		}

		var that = this,
		    htmlTableWrapper,
		    tableWrapper, eventContainer;

		// alter the table attributes
		this.obj.addClass( this.get( 'className' ) );
		this.obj.contentEditable( false );

		// set an id to the table if not already set
		if ( this.obj.attr( 'id' ) == '' ) {
			this.obj.attr( 'id', GENTICS.Utils.guid() );
		}

		// unset the selection type
		this.selection.selectionType = undefined;

		// the eventContainer will be the tbody (if there is one), or the table (if no tbody exists)
		eventContainer = this.obj.children('tbody');
		if (eventContainer.length === 0) {
			eventContainer = this.obj;
		}

		eventContainer.bind( 'keydown', function ( jqEvent ) {
			if ( !jqEvent.ctrlKey && !jqEvent.shiftKey ) {
				if ( that.selection.selectedCells.length > 0 &&
						that.selection.selectedCells[ 0 ].length > 0 ) {
					that.selection.selectedCells[ 0 ][ 0 ].firstChild.focus();
				}
			}
		} );

		function isNotUnrenderedNode(node) {
			return !Html.isUnrenderedNode(node);
		}

		/**
		 * @param {WrappedRange} range
		 */
		function isRangeVisiblyAtLeftBoundary(range) {
			var offset = range.startOffset;
			var node = range.startContainer;
			if (0 === offset) {
				return true;
			}
			if (1 === node.nodeType) {
				return !Html.findNodeRight(
					node.childNodes[offset - 1],
					isNotUnrenderedNode
				);
			}
			if (3 === node.nodeType) {
				return Html.isWSPorZWSPText(node.data.substr(0, offset));
			}
			return false;
		}

		/**
		 * @param {WrappedRange} range
		 */
		function isRangeVisiblyAtRightBoundary(range) {
			var offset = range.startOffset;
			var node = range.startContainer;
			if (Dom.nodeLength(node) === offset) {
				return true;
			}
			if (1 === node.nodeType) {
				return !Html.findNodeLeft(
					node.childNodes[offset - 1],
					isNotUnrenderedNode
				);
			}
			if (3 === node.nodeType) {
				return Html.isWSPorZWSPText(node.data.substr(offset));
			}
			return false;
		}

		Aloha.bind('aloha-command-will-execute', function (_, event){
			var range = Aloha.getSelection().getRangeAt(0);
			var adjacent;
			if ('forwarddelete' === event.commandId) {
				if (!range.collapsed || !isRangeVisiblyAtRightBoundary(range)) {
					return;
				}
				var node = range.commonAncestorContainer;
				if (3 === node.nodeType) {
					adjacent = node.parentNode
					        && Html.findNodeLeft(
								node.parentNode.nextSibling,
								isNotUnrenderedNode
							);
				} else if (1 === node.nodeType) {
					adjacent = Html.findNodeLeft(
						node.nextSibling,
						isNotUnrenderedNode
					);
				}
				if (adjacent === that.tableWrapper) {
					event.preventDefault = true;
					Aloha.getSelection().collapse(
						getNewSelectedElement('first', that.obj),
						0
					);
				}
			} else if ('delete' === event.commandId) {
				if (!range.collapsed || !isRangeVisiblyAtLeftBoundary(range)) {
					return;
				}
				var node = range.commonAncestorContainer;
				if (3 === node.nodeType) {
					adjacent = node.parentNode
					        && Html.findNodeRight(
								node.parentNode.previousSibling,
								isNotUnrenderedNode
							);
				} else if (1 === node.nodeType) {
					adjacent = Html.findNodeRight(
						node.previousSibling,
						isNotUnrenderedNode
					);
				}
				if (adjacent === that.tableWrapper) {
					var nodeToSelect = getNewSelectedElement('last', that.obj);
					event.preventDefault = true;
					Aloha.getSelection().collapse(
						nodeToSelect,
						Dom.nodeLength(nodeToSelect)
					);
				}
			}
		});


		this.obj.on('keydown', function (jqEvent) {
			// Delete button
			if (jqEvent.keyCode === 46) {
				if (that.selection.selectionType === 'row') {
					that.deleteRows();
				} else if (that.selection.selectionType === 'column') {
					that.deleteColumns();
				}

				// jqEvent.stopPropagation doesn't support cancelBubble
				// in the last jQuery versions. (query/jquery@97fa97f#diff-031bb62d959e7e4949d1847c82507f33L676)
				if (typeof jqEvent.stopPropagation === 'function') {
					jqEvent.stopPropagation();
				} else {
					// Workaround for IE
					jqEvent.cancelBubble = true;
				}
			}
		});

		/*
		We need to make sure that when the user has selected text inside a
		table cell we do not delete the entire row, before we activate this

		this.obj.bind( 'keyup', function ( $event ) {
			if ( $event.keyCode == 46 ) {
				if ( that.selection.selectedColumnIdxs.length ) {
					that.deleteColumns();
					$event.stopPropagation();
				} else if ( that.selection.selectedRowIdxs.length ) {
					that.deleteRows();
					$event.stopPropagation();
				} else {
					// Nothing to delete
				}
			}
		} );
		*/

		// handle click event of the table
	//	this.obj.bind('click', function(e){
	//		// stop bubbling the event to the outer divs, a click in the table
	//		// should only be handled in the table
	//		e.stopPropagation();
	//		return false;
	//	});
	//

	 // handle column/row resize
			eventContainer.delegate( 'th, td', 'mousemove', function( e ) {

				var jqObj = jQuery( this );
				// offset to be used for activating the resize cursor near a table border
				var mouseOffset = 3;

				// filter out the control cells
				if ( jQuery( this ).hasClass( 'aloha-table-selectrow' ) || jQuery( this ).closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ))
					return;

				var closeToLeftBorder = function(cell) {
					return ( ( e.pageX - cell.offset().left ) < mouseOffset );
				};

				var closeToTopBorder = function(cell) {
					return ( ( e.pageY - cell.offset().top ) < mouseOffset );
				};

				var closeToTableBottom = function(cell) {
					var row = cell.closest( 'tr');
					// check if it's the last row
					if ( row.next( 'tr').length > 0 ) {
						return false
					}

					var cursorOffset = e.pageY - ( row.offset().top + row.outerHeight() );
					return cursorOffset > (mouseOffset * -1) && cursorOffset < mouseOffset;
				}

				var colResize = that.tablePlugin.colResize;
				var rowResize = that.tablePlugin.rowResize;

				if ( colResize && closeToLeftBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'col-resize' );
					return that.attachColumnResize( jqObj );
				} else if ( rowResize && closeToTopBorder( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj );
				} else if ( rowResize && closeToTableBottom( jqObj ) ) {
					jqObj.css( 'cursor', 'row-resize' );
					return that.attachRowResize( jqObj, true );
				} else {
					jqObj.css( 'cursor', 'default' );
					return that.detachRowColResize( jqObj );
				}
			});

			eventContainer.bind( 'mousemove', function( e ) {

				var jqObj = jQuery( this ).closest( 'table' );

				var isTableRightBorder = function( table ) {
					var cursorOffset = e.pageX - ( table.offset().left + table.outerWidth() );
					return cursorOffset > -5 && cursorOffset < 5;
				};

				var tableResize = that.tablePlugin.tableResize;

				if ( tableResize && isTableRightBorder( jqObj ) ) {
					return that.attachTableResizeWidth( jqObj );
				}

			});

		eventContainer.bind( 'mousedown', function ( jqEvent ) {
			// focus the table if not already done
			if ( !that.hasFocus ) {
				that.focus();
			}

			// DEACTIVATED by Haymo prevents selecting rows
			//		// if a mousedown is done on the table, just focus the first cell of the table
			//		setTimeout(function() {
			//			var firstCell = that.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//			TableSelection.unselectCells();
			//			jQuery(firstCell).get(0).focus();
			//			// move focus in first cell
			//			that.obj.cells[0].wrapper.get(0).focus();
			//		}, 0);

			// stop bubbling and default-behaviour
			jqEvent.stopPropagation();
			jqEvent.preventDefault();
			return false;
		} );

		// ### create a wrapper for the table (@see HINT below)
		// wrapping div for the table to suppress the display of the resize-controls of
		// the editable divs within the cells
		// tha data-block-skip-scope attribute will keep the block plugin from setting the
		// FloatingMenu's scope when the block is clicked
		tableWrapper = jQuery(
			'<div class="' + this.get( 'classTableWrapper' ) + '" data-block-skip-scope="true"></div>'
		);
		tableWrapper.contentEditable( false );

		// mark the wrapper as ephemeral
		Ephemera.markWrapper(tableWrapper);

		// wrap the tableWrapper around the table
		this.obj.wrap( tableWrapper );

		// Check because the aloha block plugin may not be loaded
		var parent = this.obj.parent();
		if (parent.alohaBlock) {
			parent.alohaBlock();
		}

		// :HINT The outest div (Editable) of the table is still in an editable
		// div. So IE will surround the the wrapper div with a resize-border
		// Workaround => just disable the handles so hopefully won't happen any ugly stuff.
		// Disable resize and selection of the controls (only IE)
		// Events only can be set to elements which are loaded from the DOM (if they
		// were created dynamically before) ;)

		htmlTableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) );
		htmlTableWrapper.get( 0 ).onresizestart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).oncontrolselect = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).ondragstart = function ( e ) { return false; };
		htmlTableWrapper.get( 0 ).onmovestart = function ( e ) { return false; };
		// the following handler prevents proper selection in the editable div in the caption!
		// htmlTableWrapper.get( 0 ).onselectstart = function ( e ) { return false; };

		this.tableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) ).get( 0 );

		jQuery( this.cells ).each( function () {
			this.activate();
		} );

		// after the cells where replaced with contentEditables ... add selection cells
		// first add the additional columns on the left side
		this.attachSelectionColumn();
		// then add the additional row at the top
		this.attachSelectionRow();
		this.makeCaptionEditable();
		this.checkWai();
		this.isActive = true;

		Aloha.trigger( 'aloha-table-activated' );
	};

	/**
	 * Make the table caption editable (if present)
	 */
	Table.prototype.makeCaptionEditable = function() {
		var caption = this.obj.find('caption').eq(0);
		if (caption) {
			this.tablePlugin.makeCaptionEditable(caption);
		}
	};

	/**
	 * Check the WAI conformity of the table and sets the attribute.
	 *
	 * @returns {boolean} True is WAI is activated, False otherwise.
	 */
	Table.prototype.checkWai = function () {
		var thisWai = this.wai;
		if (!thisWai) {
			return false;
		}

		var waiGreen = this.get('waiGreen'),
			waiRed = this.get('waiRed');

		thisWai.removeClass(waiGreen + ' ' + waiRed);

		// Y U NO explain why we must check that summary is longer than 5 characters?
		// http://cdn3.knowyourmeme.com/i/000/089/665/original/tumblr_l96b01l36p1qdhmifo1_500.jpg

		if (jQuery.trim(this.obj[0].summary) !== '') {
			thisWai.addClass(waiGreen);
			return true;
		}

		thisWai.addClass(waiRed);
		return false;
	};

	/**
	 * Add the selection-column to the left side of the table and attach the events
	 * for selection rows
	 *
	 * @return void
	 */
	Table.prototype.attachSelectionColumn = function() {
		// create an empty cell
		var emptyCell = jQuery('<td>'),
			rowIndex, columnToInsert, rowObj, that = this, rows, i;

		// set the unicode '&nbsp;' code
		emptyCell.html('\u00a0');

		that = this;
		rows = this.obj.context.rows;

		// add a column before each first cell of each row
		for ( i = 0; i < rows.length; i++) {
			rowObj = jQuery(rows[i]);
			columnToInsert = emptyCell.clone();
			columnToInsert.addClass(this.get('classSelectionColumn'));
			columnToInsert.css('width', this.get('selectionArea') + 'px');
			//rowObj.find('td:first').before(columnToInsert);
			rowObj.prepend(columnToInsert);
			// rowIndex + 1 because an addtional row is still added
			rowIndex = i + 1;

			// this method sets the selection-events to the cell
			this.attachRowSelectionEventsToCell(columnToInsert);
		}
	};

	/**
	 * Binds the needed selection-mouse events to the given cell
	 *
	 * @param cell
	 *            The jquery object of the table-data field
	 * @return void
	 */
	Table.prototype.attachRowSelectionEventsToCell = function(cell){
		var that = this;

		// mark cell as ephemeral
		Ephemera.markElement(cell);

		// unbind eventually existing events of this cell
		cell.unbind('mousedown');
		cell.unbind('mouseover');

		// prevent ie from selecting the contents of the table
		cell.get(0).onselectstart = function() { return false; };

		cell.bind('mousedown', function(e) {
			// set flag that the mouse is pressed
//TODO to implement the mousedown-select effect not only must the
//mousedown be set here but also be unset when the mouse button is
//released.
//			that.mousedown = true;
			return that.rowSelectionMouseDown(e);
		});

		cell.bind('mouseover', function(e){
			// only select more crows if the mouse is pressed
			if ( that.mousedown ) {
				return that.rowSelectionMouseOver(e);
			}
		});
	};

	/**
	 * Mouse-Down event for the selection-cells on the left side of the table
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.rowsToSelect = [];
		}

		// set the origin-rowId of the mouse-click
		this.clickedRowId = jqEvent.currentTarget.parentNode.rowIndex;

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedRowId, this.rowsToSelect );
			if ( arrayIndex >= 0 ) {
				this.rowsToSelect.splice( arrayIndex, 1 );
			} else {
				this.rowsToSelect.push( this.clickedRowId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.rowsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.rowsToSelect[ 0 ];
			var end = this.clickedRowId;
			if ( start > end ) {
				start = end;
				end = this.rowsToSelect[ 0 ];
			}
			this.rowsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.rowsToSelect.push( i );
			}
		// single column
		} else {
			this.rowsToSelect = [ this.clickedRowId ];
		}

		// mark the selection visual
		this.selectRows();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * The mouse-over event for the selection-cells on the left side of the table.
	 * On mouse-over check which column was clicked, calculate the span between
	 * clicked and mouse-overed cell and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.rowSelectionMouseOver = function (jqEvent) {
		var rowIndex = jqEvent.currentTarget.parentNode.rowIndex,
			indexInArray, start, end, i;

		// only select the row if the mouse was clicked and the clickedRowId isn't
		// from the selection-row (row-id = 0)
		if (this.mousedown && this.clickedRowId >= 0) {

			// select first cell
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

			indexInArray = jQuery.inArray(rowIndex, this.rowsToSelect);

			start = (rowIndex < this.clickedRowId) ? rowIndex : this.clickedRowId;
			end = (rowIndex < this.clickedRowId) ? this.clickedRowId : rowIndex;

			this.rowsToSelect = new Array();
			for ( i = start; i <= end; i++) {
				this.rowsToSelect.push(i);
			}

			// this actually selects the rows
			this.selectRows();

			// prevent browser from selecting the table
			jqEvent.preventDefault();

			// stop bubble, otherwise the mousedown of the table is called ...
			jqEvent.stopPropagation();

			// prevent ff/chrome/safare from selecting the contents of the table
			return false;
		}
	};

  /**
   * Binds the needed selection-mouse events to the given cell
   *
   * @param cell
   *            The jquery object of the table-data field
   * @return void
   */
	Table.prototype.attachSelectionRow = function () {
		var that = this;

		// create an empty td
		var emptyCell = jQuery('<td>');
		emptyCell.html('\u00a0');

		// get the number of columns in the table (first row)
		// iterate through all rows and find the maximum number of columns to add
		var numColumns = 0;
		for( var i = 0; i < this.obj.context.rows.length; i++ ){
			var curNumColumns = 0;

			for( var j = 0; j < this.obj.context.rows[i].cells.length; j++ ){
				var colspan = Utils.colspan( this.obj.context.rows[i].cells[j] );
				curNumColumns += colspan;
			}

			if( numColumns < curNumColumns ) {
				numColumns = curNumColumns;
			}
		}

		var selectionRow = jQuery('<tr>');
		selectionRow.addClass(this.get('classSelectionRow'));
		selectionRow.css('height', this.get('selectionArea') + 'px');

		// mark selection row as ephemeral
		Ephemera.markElement(selectionRow);

		for (var i = 0; i < numColumns; i++) {

			var columnToInsert = emptyCell.clone();
			// the first cell should have no function, so only attach the events for
			// the rest
			if (i > 0) {
				// bind all mouse-events to the cell
				this.attachColumnSelectEventsToCell(columnToInsert);
				//set the colspan of selection column to match the colspan of first row columns
			} else {
				var columnToInsert = jQuery('<td>').clone();
				columnToInsert.addClass(this.get('classLeftUpperCorner'));
				var clickHandler = function (e) {
					// select the Table
					that.focus();
					that.selection.selectAll();

					// set the selection type before updating the scope
					that.tablePlugin.activeTable.selection.selectionType = 'cell';
					that.tablePlugin.updateFloatingMenuScope();

					// As side-effect of the following call the focus
					// will be set on the first selected cell.
					// This will be overwritten with the summary
					// attribute-field, if the setting summaryinsidebar
					// is false.
					that._removeCursorSelection();

					//If the summary should be modified in the sidebar
					//we activate the sidebar panel
					if (that.tablePlugin.settings.summaryinsidebar) {
						that.tablePlugin.sidebar.open();
						that.tablePlugin.sidebarPanel.activate(that.obj);
						that.tablePlugin.sidebar.correctHeight();
					}

					// jump in Summary field
					// attempting to focus on summary input field will occasionally result in the
					// following exception:
					//uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMHTMLInputElement.setSelectionRange]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: src/dep/ext-3.2.1/ext-all.js :: <TOP_LEVEL> :: line 11" data: no]
					// this occurs when the tab in which the summary field is contained is not visible
					// TODO: I'm adding a try catch clause here for the time being, but a proper solution, which addresses the problem of how to handle invisible fields ought to be persued.

					try {
						that.tablePlugin.summary.focus();
						e.stopPropagation();
						e.preventDefault();
					} catch (e) {
						Console.error ('Table', e.message);
					}

					return false;
				};
				this.wai = jQuery('<div/>').width(25).height(12).click(clickHandler);
				columnToInsert.append(this.wai);
			}

			// add the cell to the row
			selectionRow.append(columnToInsert);
		}

		// global mouseup event to reset the selection properties
		jQuery(document).bind('mouseup', function(e) { that.columnSelectionMouseUp(e) } );

		this.obj.find('tr:first').before( selectionRow );
	};

	/**
	 * Binds the events for the column selection to the given cell.
	 *
	 * @param cell
	 *            the jquery object of the td-field
	 * @return void
	 */
	Table.prototype.attachColumnSelectEventsToCell = function ( cell ) {
		var that = this;

		// unbind eventually existing events of this cell
		cell.unbind( 'mousedown' );
		cell.unbind( 'mouseover' );

		// prevent ie from selecting the contents of the table
		cell.get( 0 ).onselectstart = function () { return false; };

		cell.bind( 'mousedown',  function ( e ) { that.columnSelectionMouseDown( e ) } );
		cell.bind( 'mouseover',  function ( e ) { that.columnSelectionMouseOver( e ) } );
	};

	/**
	 * Handles the mouse-down event for the selection-cells on the top of the
	 * menu
	 *
	 * @param {jQuery:Event} jqEvent - the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseDown = function ( jqEvent ) {
		// focus the table (if not already done)
		this.focus();

		// if no cells are selected, reset the selection-array
		if ( this.selection.selectedCells.length == 0 ) {
			this.columnsToSelect = [];
		}

		// set the origin-columnId of the mouse-click
		this.clickedColumnId = jQuery( jqEvent.currentTarget.parentNode )
									.children().index( jqEvent.currentTarget );

		// set single column selection
		if ( jqEvent.metaKey ) {
			var arrayIndex = jQuery.inArray( this.clickedColumnId, this.columnsToSelect );
			if ( arrayIndex >= 0 ) {
				this.columnsToSelect.splice( arrayIndex, 1 );
			} else {
				this.columnsToSelect.push( this.clickedColumnId );
			}
		// block of columns selection
		} else if ( jqEvent.shiftKey ) {
			this.columnsToSelect.sort( function( a, b ) { return a - b; } );
			var start = this.columnsToSelect[ 0 ];
			var end = this.clickedColumnId;
			if ( start > end ) {
				start = end;
				end = this.columnsToSelect[ 0 ];
			}
			this.columnsToSelect = [];
			for ( var i = start; i <= end; i++ ) {
				this.columnsToSelect.push( i );
			}
		// single column
		} else {
			this.columnsToSelect = [ this.clickedColumnId ];
		}

		// mark the selection visual
		this.selectColumns();

		// prevent browser from selecting the table
		jqEvent.preventDefault();

		// stop bubble, otherwise the mousedown of the table is called ...
		jqEvent.stopPropagation();

		// prevent ff/chrome/safare from selecting the contents of the table
		return false;
	};

	/**
	 * Mouseover-event for the column-selection cell. This method calcluates the
	 * span between the clicked column and the mouse-overed cell and selects the
	 * columns inbetween. and mark them as selected
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseOver = function (jqEvent) {

		var
			colIdx = jqEvent.currentTarget.cellIndex,
			columnsToSelect = [],
			start,
			end;

		// select all columns from the last clicked to the hoverd
		if ( this.mouseDownColIdx ) {
			start = (colIdx < this.mouseDownColIdx) ? colIdx : this.mouseDownColIdx;
			end = (colIdx < this.mouseDownColIdx) ? this.mouseDownColIdx : colIdx;
			for (var i = start; i <= end; i++) {
				columnsToSelect.push(i);
			}
			this.selectColumns( columnsToSelect );
		}
	};

	/**
	 * MouseUp-event for the column-selection. This method resets the
	 * selection mode
	 *
	 * @param jqEvent
	 *            the jquery-event object
	 * @return void
	 */
	Table.prototype.columnSelectionMouseUp = function (jqEvent) {
		this.mouseDownColIdx = false;
	};

	/**
	 * Deletes the selected rows. If no row are selected, delete the row, where the
	 * cursor is positioned. If all rows of the table should be deleted, the whole
	 * table is deletet and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteRows = function() {
		var
			rowIDs = [],
			rowsToDelete = {},
			table = this;

		// if a selection was made, delete the selected cells
		if (0 === this.selection.selectedCells.length) {
			return;
		}

		for (var i = 0; i < this.selection.selectedCells.length; i++) {
			rowsToDelete[this.selection.selectedCells[i].parentNode.rowIndex] = true;
		}

	    for (rowId in rowsToDelete) {
	       rowIDs.push(rowId);
	    }

		// if all rows should be deleted, set a flag to remove the WHOLE table
		var deleteTable = false;
		if (rowIDs.length == this.numRows) {
			deleteTable = true;
		}

		// delete the whole table
		if (deleteTable) {
			var that = this;
			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});
		} else {

			rowIDs.sort(function(a,b){return a - b;});

			// check which cell should be focused after the deletion
			var focusRowId = rowIDs[0];
			if (focusRowId > (this.numRows - rowIDs.length)) {
				focusRowId --;
			}

			// get all rows
			var rows = this.getRows();

			//splits all cells on the rows to be deleted
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = rows[ rowId ];
				for (var i = 0; i < row.cells.length; i++) {
					Utils.splitCell( row.cells[i], function () {
						return table.newActiveCell().obj;
					});
				}
			});

			//decreases rowspans of cells that span the row to be deleted
			//and removes the row
			var grid = Utils.makeGrid( rows );
			jQuery.each( rowIDs, function ( unused, rowId ) {
				var row = grid[ rowId ];
				for ( var j = 0; j < row.length; ) {
					var cellInfo = row[ j ];
					var rowspan = Utils.rowspan( cellInfo.cell );
					if ( 1 < rowspan ) {
						jQuery( cellInfo.cell ).attr( 'rowspan', rowspan - 1);
					}
					j += cellInfo.colspan;
				}
				jQuery( rows[ rowId ] ).remove();
			});

			// reduce the attribute storing the number of rows in the table
			this.numRows -= rowIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[ focusRowId +1 ] );
				lastCell.focus();
			}, 5);

			// finally unselect the marked cells
			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the selected columns. If no columns are selected, delete the column, where the
	 * cursor is positioned. If all columns of the table should be deleted, the whole
	 * table is deleted from the dom and removed from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteColumns = function() {
		var
			colIDs = [],
			cellToDelete = [],
			// get all rows to iterate
		    rows = this.getRows(),
			that = this,
			changeColspan = [],
			cells,
			cellInfo;

		var grid = Utils.makeGrid(rows);
		var selectColWidth = 1; //width of the select-row column

		// if all columns should be deleted, remove the WHOLE table
		// delete the whole table
		if ( this.selection.selectedColumnIdxs.length == grid[0].length - selectColWidth ) {

			Dialog.confirm({
				title : i18n.t('Table'),
				text : i18n.t('deletetable.confirm'),
				yes : function () {
					that.deleteTable();
				}
			});

		} else {

			colIDs.sort(function(a,b) {return a - b;} );

			//TODO there is a bug that that occurs if a column is
			//selected and deleted, and then a column with a greater
			//x-index is selected and deleted.

			//sorted so we delete from right to left to minimize interfernce of deleted rows
			var gridColumns = this.selection.selectedColumnIdxs.sort(function(a,b){ return b - a; });
			for (var i = 0; i < gridColumns.length; i++) {
				var gridColumn = gridColumns[i];
				for (var j = 0; j < rows.length; j++) {
					var cellInfo = grid[j][gridColumn];
					if ( ! cellInfo ) {
						//TODO this case occurred because of a bug somewhere which should be fixed
						continue;
					}
					if ( 0 === cellInfo.spannedX ) {
						if (1 < cellInfo.colspan) {
							var nCell = this.newActiveCell().obj;
							jQuery( cellInfo.cell ).after(nCell);
							nCell.attr('rowspan', cellInfo.rowspan);
							nCell.attr('colspan', cellInfo.colspan - 1);
						}
						jQuery( cellInfo.cell ).remove();
					} else {
						jQuery( cellInfo.cell ).attr('colspan', cellInfo.colspan - 1);
					}
					//ensures that always 0 === cellInfo.spannedY
					j += cellInfo.rowspan - 1;
				}
				//rebuild the grid to reflect the table structure change
				grid = Utils.makeGrid(rows);
			}

			// reduce the attribute storing the number of rows in the table
			this.numCols -= colIDs.length;

			// IE needs a timeout to work properly
			window.setTimeout( function() {
				var lastCell = jQuery( rows[1].cells[1] );
				lastCell.focus();
			}, 5);

			this.selection.unselectCells();
		}
	};

	/**
	 * Deletes the table from the dom and remove it from the tableRegistry.
	 *
	 * @return void
	 */
	Table.prototype.deleteTable = function() {
		var deleteIndex = -1;
		for (var i = 0; i < this.tablePlugin.TableRegistry.length; i++){
			if (this.tablePlugin.TableRegistry[i].obj.attr('id') == this.obj.attr('id')) {
				deleteIndex = i;
				break;
			}
		}
		if (deleteIndex >= 0) {
			// before deleting the table, deactivate it
			this.deactivate();

			this.selection.selectionType = undefined;
			this.tablePlugin.TableRegistry.splice(i, 1);

			// we will set the cursor right before the removed table
			var newRange = Aloha.Selection.rangeObject;
			// TODO set the correct range here (cursor shall be right before the removed table)
			newRange.endContainer = this.obj.get(0).parentNode;
			newRange.startContainer = newRange.endContainer;

			newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(this.obj.get(0));
			newRange.startOffset = newRange.endOffset;

			newRange.clearCaches();

			this.obj.remove();
			this.parentEditable.obj.focus();
			// select the new range
			newRange.correctRange();
			newRange.select();
		}
	};

	/**
	 * @param {string} position
	 *            could be 'after' or 'before'. defines the position where the new
	 *            rows should be inserted
	 */
	function rowIndexFromSelection( position, selection ) {

		var newRowIndex = -1;

		// get the index where the new rows should be inserted
		var cellOfInterest = null;
		if ( 'before' === position ) {
			cellOfInterest = selection.selectedCells[ 0 ];
		} else if ( 'after' === position ) {
			var offset = selection.selectedCells.length - 1;
			cellOfInterest = selection.selectedCells[ offset ];
		}

		if (cellOfInterest && cellOfInterest.nodeType == 1) {
			newRowIndex = cellOfInterest.parentNode.rowIndex;
		}

		return newRowIndex;
	}

	/**
	 * Wrapper function for this.addRow to add a row before the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowBeforeSelection = function(highlightNewRows) {
		var newRowIndex = rowIndexFromSelection( 'before', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex );
		}
	};

	/**
	 * Wrapper function for this.addRow to add a row after the active row
	 *
	 * @see Table.prototype.addRow
	 */
	Table.prototype.addRowAfterSelection = function() {
		var newRowIndex = rowIndexFromSelection( 'after', this.selection );
		if ( -1 !== newRowIndex ) {
			this.addRow( newRowIndex + 1 );
		}
	};

	/**
	 * Adds a new row to the table.
	 *
	 * @param {int} rowIndex
	 *        the index at which the new row shall be inserted
	 */
	Table.prototype.addRow = function(newRowIndex) {

		var that = this;
		var rowsToInsert = 1;

		var numCols = this.countVirtualCols();
		var $rows = this.obj.children().children('tr');
		for (var j = 0; j < rowsToInsert; j++) {
			var insertionRow = jQuery('<tr>');

			// create the first column, the "select row" column
			var selectionColumn = jQuery('<td>');
			selectionColumn.addClass(this.get('classSelectionColumn'));
			this.attachRowSelectionEventsToCell(selectionColumn);
			insertionRow.append(selectionColumn);

			var grid = Utils.makeGrid($rows);
			var selectColOffset = 1;
			if ( newRowIndex >= grid.length ) {
				for (var i = selectColOffset; i < grid[0].length; i++) {
					insertionRow.append(this.newActiveCell().obj);
				}
			} else {
				for (var i = selectColOffset; i < grid[newRowIndex].length; ) {
					var cellInfo = grid[newRowIndex][i];
					if (Utils.containsDomCell(cellInfo)) {
						var colspan = cellInfo.colspan;
						while (colspan--) {
							insertionRow.append(this.newActiveCell().obj);
						}
					} else {
						jQuery( cellInfo.cell ).attr('rowspan', cellInfo.rowspan + 1);
					}
					i += cellInfo.colspan;
				}
			}

			if ( newRowIndex >= $rows.length ) {
				$rows.eq( $rows.length - 1 ).after( insertionRow );
			} else {
				$rows.eq( newRowIndex ).before( insertionRow );
			}
		}

		this.numRows += rowsToInsert;
	};

	/**
	 * Wrapper method to add columns on the right side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsRight = function () {
		this.addColumns('right');
	};

	/**
	 * Wrapper method to add columns on the left side
	 *
	 * @see Table.addColumns
	 * @return void
	 */
	Table.prototype.addColumnsLeft = function() {
		this.addColumns('left');
	};

	/**
	 * Inserts new columns into the table. Either on the right or left side. If
	 * columns are selected, the amount of selected columns will be inserted on the
	 * 'right' or 'left' side. If no cells are selected, 1 new column will be
	 * inserted before/after the column of the last active cell.
	 * As well all column-selection events must be bound to the firsts row-cell.
	 *
	 * @param position
	 *            could be 'left' or 'right'. defines the position where the new
	 *            columns should be inserted
	 * @return void
	 */
	Table.prototype.addColumns = function( position ) {
		var
			that = this,
			emptyCell = jQuery( '<td>' ),
		    rows = this.getRows(),
			cell,
			currentColIdx,
			columnsToSelect = [],
			selectedColumnIdxs = this.selection.selectedColumnIdxs;

		if ( 0 === selectedColumnIdxs.length ) {
			return;
		}

		selectedColumnIdxs.sort( function ( a, b ) { return a - b; } );

		// refuse to insert a column unless a consecutive range has been selected
		if ( ! Utils.isConsecutive( selectedColumnIdxs ) ) {
			Dialog.alert( {
				title : i18n.t( 'Table' ),
				text  : i18n.t( 'table.addColumns.nonConsecutive' )
			});
			return;
		}

		if ( 'left' === position ) {
			currentColIdx = selectedColumnIdxs[ 0 ];
			// inserting a row before the selected column indicies moves
			// all selected columns one to the right
			for ( var i = 0; i < this.selection.selectedColumnIdxs.length; i++ ) {
				this.selection.selectedColumnIdxs[ i ] += 1;
			}
		} else {//"right" == position
			currentColIdx = selectedColumnIdxs[ selectedColumnIdxs.length - 1 ];
		}

		var grid = Utils.makeGrid( rows );

		for ( var i = 0; i < rows.length; i++ ) {
			// prepare the cell to be inserted
			cell = emptyCell.clone();
			cell.html( '\u00a0' );

			// on first row correct the position of the selected columns
			if ( i == 0 ) {
				// this is the first row, so make a column-selection cell
				this.attachColumnSelectEventsToCell( cell );
			} else {
				// activate the cell for this table
				cellObj = this.newActiveCell( cell.get(0) );
				cell = cellObj.obj;
			}

			var leftCell = Utils.leftDomCell( grid, i, currentColIdx );
			if ( null == leftCell ) {
				jQuery( rows[i] ).prepend( cell );
			} else {
				if ( 'left' === position && Utils.containsDomCell( grid[ i ][ currentColIdx ] ) ) {
					jQuery( leftCell ).before( cell );
				} else {//right
					jQuery( leftCell ).after( cell );
				}
			}

			this.numCols++;
		}
	};

	/**
	 * Helper method to set the focus-attribute of the table to true
	 *
	 * @return void
	 */
	Table.prototype.focus = function() {
		if (!this.hasFocus) {
			if (!this.parentEditable.isActive) {
				this.parentEditable.obj.focus();
			}

			// @iefix
			this.tablePlugin.setFocusedTable(this);

			// select first cell
			// TODO put cursor in first cell without selecting
			//var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0);
			//jQuery(firstCell).get(0).focus();

		}

		// TODO workaround - fix this. the selection is updated later on by the browser
		// using setTimeout here is hideous, but a simple execution-time call will fail
	// DEACTIVATED by Haymo prevents selecting rows
	//	setTimeout('Aloha.Selection.updateSelection(false, true)', 50);

	};

	/**
	 * Helper method to set the focus-attribute of the table to false
	 *
	 * @return void
	 */
	Table.prototype.focusOut = function() {
		if (this.hasFocus) {
			this.tablePlugin.setFocusedTable(undefined);
			this.selection.selectionType = undefined;
		}
	};

	/**
	 * Undoes the cursor-selection after cells have been selected.  This
	 * is done to be more consistent in the UI - there should either be
	 * a cursor-selection or a cell-selection, but not both.
	 */
	Table.prototype._removeCursorSelection = function() {
		// We can't remove the selection on IE because whenever a
		// row/column is selected, and then another row/column is
		// selected, the browser windows scrolls to the top of the page
		// (som kind of browser bug).

		// This is no problem for IE because IE removes the
		// cursor-selection by itself and shows a frame around the
		// table, with resize handles (the frame seems useless).

		// On other browsers, we can't remove the selection because the
		// floating menu will disappear when one selects a rows/column
		// and types a key (that's the same effect as when one clicks
		// outside the editable).

		//TODO: currently, removing the cursor selection can't be
		//     reliably implemented.
		//if ( ! jQuery.browser.msie ) {
		//    Aloha.getSelection().removeAllRanges();
		//}

		// The following is a workaround for the above because we can't
		// leave the cursor-selection outside of the table, since
		// otherwise the floating menu scope will be incorrect when one
		// CTRL-clicks on the rows or columns.

		var selection = Aloha.getSelection();

		if ( !selection ||
				!selection._nativeSelection ||
					selection._nativeSelection._ranges.length == 0 ) {
			return;
		}

		var range = selection.getRangeAt( 0 );
		if ( null == range.startContainer ) {
			return;
		}

		// if the selection is  already in the table, do nothing
		if ( 0 !== jQuery( range.startContainer ).closest('table').length ) {
			return;
		}

		// if no cells are selected, do nothing
		if ( 0 === this.selection.selectedCells.length ) {
			return;
		}

		// set the foces to the first selected cell
		var container = TableCell.getContainer( this.selection.selectedCells[ 0 ] );
		jQuery( container ).focus();
	}

	/**
	 * Marks all cells of the specified column as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectColumns = function ( columns ) {
		var columnsToSelect;

		if (columns) {
			columnsToSelect = columns;
		} else {
			columnsToSelect = this.columnsToSelect;
		}

		this.selection.selectColumns(columnsToSelect);
		this.tablePlugin._columnheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.columnConfig,
				this.tablePlugin.columnMSItems,	this.tablePlugin.columnMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();

		Scopes.setScope(this.tablePlugin.name + '.column');
	};

	/**
	 * Marks all cells of the specified row as marked (adds a special class)
	 *
	 * @return void
	 */
	Table.prototype.selectRows = function () {

		this.selection.selectRows(this.rowsToSelect);
		this.tablePlugin._rowheaderButton.setState(this.selection.isHeader());

		// ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI =======

		setActiveStyle(this.selection.selectedCells, this.tablePlugin.rowConfig,
				this.tablePlugin.rowMSItems, this.tablePlugin.rowMSButton);

		// ====== END UI specific code - should be handled by UI =======

		// blur all editables within the table
		this.obj.find('div.aloha-ui-table-cell-editable').blur();

		Utils.selectAnchorContents(this.selection.selectedCells);

		this.selection.notifyCellsSelected();
		this._removeCursorSelection();

		Scopes.setScope(this.tablePlugin.name + '.row');
	};

	/**
	 * Deactivation of a Aloha-table. Clean up ... remove the wrapping div and the
	 * selection-helper divs
	 *
	 * @return void
	 */
	Table.prototype.deactivate = function() {
		// unblockify the table wrapper
		var parent = this.obj.parent();
		if (parent.mahaloBlock) {
			parent.mahaloBlock();
		}

		this.obj.removeClass(this.get('className'));
		if (jQuery.trim(this.obj.attr('class')) == '') {
			this.obj.removeAttr('class');
		}
		this.obj.removeAttr('contenteditable');
	//	this.obj.removeAttr('id');

		// unwrap the selectionLeft-div if available
		if (this.obj.parents('.' + this.get('classTableWrapper')).length){
			this.obj.unwrap();
		}

		// remove the selection row
		this.obj.find('tr.' + this.get('classSelectionRow') + ':first').remove();
		// remove the selection column (first column left)
		var that = this;
		jQuery.each(this.obj.context.rows, function(){
			jQuery(this).children('td.' + that.get('classSelectionColumn')).remove();
		});

		// remove the "selection class" from all td and th in the table
		this.obj.find('td, th').removeClass(this.get('classCellSelected'));
		this.obj.find('td, th').removeClass('aloha-table-cell_active');

		// remove cursor-styles
		this.obj.find('td, th').css('cursor', '');

		this.obj.unbind();
		this.obj.children('tbody').unbind();

		// wrap the inner html of the contentEditable div to its outer html
		for (var i = 0; i < this.cells.length; i++) {
			var Cell = this.cells[i];
			Cell.deactivate();
		}

		// remove editable span in caption (if any)
		this.obj.find('caption div').each(function() {
			jQuery(this).contents().unwrap();
		});

		// better unset ;-) otherwise activate() may think you're activated.
		this.isActive = false;
	};

	/**
	 * Attach the event for column resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachColumnResize = function(cell) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var rows = cell.closest( 'tbody' ).children( 'tr' );
		var cellRow = cell.closest( 'tr' );
		var gridId = Utils.cellIndexToGridColumn(rows,
			rows.index(cellRow),
			cellRow.children().index(cell)
		);

		var resizeColumns = function(pixelsMoved) {
			var expandToWidth, reduceToWidth;

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] );

				// skip the select & cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					if (!reduceToWidth) {
						reduceToWidth = currentCell.width() - pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, reduceToWidth );

				} else if (gridCi === gridId - 1) {
					if (!expandToWidth) {
						expandToWidth = currentCell.width() + pixelsMoved;
					}

					Utils.resizeCellWidth( currentCell, expandToWidth );

				}

				return true;
			});
		};

		cell.bind('mousedown.resize', function() {

			// create a guide
			var guide = jQuery( '<div></div>' );
			var $cell = jQuery(cell);
			var width = $cell.outerWidth() - $cell.innerWidth();
			var height = $cell.closest( 'tbody' ).innerHeight();
			guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': $cell.closest( 'tbody' ).offset().top,
				'left': $cell.offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery('body').append(guide);

			Utils.getCellResizeBoundaries(gridId, rows, function(maxPageX, minPageX) {

				// unset the selection type
				that.selection.resizeMode = true;

				// move the guide while dragging
				jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
					// limit the maximum resize
					if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						guide.css( 'left', e.pageX );
					}
				});

				// do the actual resizing after drag stops
				jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
					var pixelsMoved = 0;

					if ( e.pageX < minPageX ) {
						pixelsMoved = minPageX - cell.offset().left;
					} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
						pixelsMoved = e.pageX - cell.offset().left;
					} else if ( e.pageX > maxPageX ) {
						pixelsMoved = maxPageX - cell.offset().left;
					}

					if ( pixelsMoved !== 0 ) {
						resizeColumns( pixelsMoved );
					}

					jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
					jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );

					// unset the selection resize mode
					that.selection.resizeMode = false;

					guide.remove();
				});

			});

		});

	};

	/**
	 * Attach the event handler for row resize for the given cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.attachRowResize = function(cell, lastRow) {

		var that = this;

		//unbind any exisiting resize event handlers
		that.detachRowColResize( cell );

		var resizeRows = function(pixelsMoved) {
			var expandingRow;

			if (lastRow) {
				expandingRow = cell.closest( 'tr' );
			} else {
				expandingRow = cell.closest( 'tr' ).prev( 'tr' );
			}

			var currentRowHeight = expandingRow.height();
			var expandToHeight = currentRowHeight + pixelsMoved;

			// correct if the height is a minus value
			if ( expandToHeight < 0 ) {
				expandToHeight = 1;
			}

			expandingRow.css( 'height', expandToHeight );
		};

		cell.bind( 'mousedown.resize', function(){

			// create a guide
			var guide = jQuery( '<div></div>' );

			var guideTop = function() {
				if (lastRow) {
					return cell.offset().top + cell.outerHeight();
				} else {
					return cell.offset().top;
				}
			};

			var width = cell.closest( 'tbody' ).innerWidth();
			var height = cell.outerHeight() - cell.innerHeight();

			guide.css({
				'width': (width < 1) ? 1 : width,
				'height': (height < 1) ? 1: height,
				'top': guideTop(),
				'left': cell.closest( 'tbody' ).offset().left,
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery( 'body' ).append( guide );

			// set the minimum resize
			var minHeight = function() {
				if (lastRow) {
					return cell.closest('tr').offset().top;
				} else {
					return cell.closest('tr').prev( 'tr' ).offset().top;
				}
			};

			// set the selection resize mode
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_row_resize', function(e) {
				if ( e.pageY > minHeight() ) {
					guide.css( 'top', e.pageY );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_row_resize', function(e) {

				var pixelsMoved = 0;

				if (lastRow) {
					pixelsMoved = e.pageY - ( cell.offset().top + cell.outerHeight() );
				} else {
					pixelsMoved = e.pageY - cell.offset().top;
				}

				resizeRows( pixelsMoved );

				jQuery( 'body' ).unbind( 'mousemove.dnd_row_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_row_resize' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Attach the table width resize event.
	 * @param {DOMElement} table
	 *
	 * @return void
	 */
	Table.prototype.attachTableResizeWidth = function(table) {

		var that = this;
		var tableContainer = table.closest('.aloha-table-wrapper');
		var trSelector = "tr:not(.aloha-table-selectcolumn)";
		var lastColumn = table.find(trSelector + " th:last-child, " +
			                        trSelector + " td:last-child");
		var lastCell;

		jQuery.each( lastColumn, function() {
			// don't use colspanned cell as the base cell
			if ( !jQuery( this ).attr('colspan') || jQuery( this ).attr('colspan') < 2 ) {
				lastCell = jQuery( this );
				return false;
			}
		});

		// change the cursor
		lastColumn.css( 'cursor', 'col-resize' );

		var resizeColumns = function(pixelsMoved) {
			var rows = table.find( 'tr' );
			var lastCellRow = lastCell.closest( 'tr' );
			var gridId = Utils.cellIndexToGridColumn(
				                   rows,
			                      rows.index( lastCellRow ),
			                      lastCellRow.children().index( lastCell )
			                   );
			var expandToWidth = pixelsMoved - Utils.getCellBorder(lastCell) - Utils.getCellPadding(lastCell);

			Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) {
				var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] )

				// skip the select cells and cells with colspans
				if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) {
					return true;
				}

				if (gridCi === gridId ) {
					Utils.resizeCellWidth( currentCell, expandToWidth );
				} else {
					Utils.resizeCellWidth( currentCell, currentCell.width() );
				}

				return true;
			});
		};

		lastColumn.bind( 'mousedown.resize', function() {

			// create a guide
			var guide = jQuery( '<div></div>' );

			var height = table.children( 'tbody' ).innerHeight();
			var width = lastCell.outerWidth() - lastCell.innerWidth();

			guide.css({
				'height': (height < 1) ? 1 : height,
				'width': (width < 1) ? 1 : width,
				'top': table.find('tbody').offset().top,
				'left': table.offset().left + table.outerWidth(),
				'position': 'absolute',
				'background-color': '#80B5F2'
			});
			jQuery('body').append( guide );

			// set the maximum and minimum resize
			var maxPageX = tableContainer.offset().left + tableContainer.width();
			var minPageX = lastCell.offset().left + ( lastCell.innerWidth() - lastCell.width() ) + Utils.getMinColWidth( lastCell );

			// unset the selection type
			that.selection.resizeMode = true;

			// move the guide while dragging
			jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) {
				// limit the maximum resize
				if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					guide.css( 'left', e.pageX );
				}
			});

			// do the actual resizing after drag stops
			jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) {
				var pixelsMoved = 0;

				if ( e.pageX <= minPageX ) {
				 	pixelsMoved = minPageX - lastCell.offset().left;
				} else if ( e.pageX > minPageX && e.pageX < maxPageX ) {
					pixelsMoved = e.pageX - lastCell.offset().left;
				} else if ( e.pageX > maxPageX ) {
				  pixelsMoved = maxPageX - lastCell.offset().left;
				}

				// set the table width
				resizeColumns( pixelsMoved );

				// unbind the events and reset the cursor
				jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' );
				jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' );
				lastColumn.unbind( 'mousedown.resize' );
				lastColumn.css( 'cursor', 'default' );

				// unset the selection resize mode
				that.selection.resizeMode = false;

				guide.remove();
			});

		});

	};

	/**
	 * Detach any column/row resize event handlers attached to the cell.
	 * @param {DOMElement} tableCell
	 *
	 * @return void
	 */
	Table.prototype.detachRowColResize = function(cell) {
		return cell.unbind('mousedown.resize');
	};

	/**
	 * toString-method for Table object
	 *
	 * @return void
	 */
	Table.prototype.toString = function() {
		return 'Table';
	};

	Table.prototype.newCell = function(domElement) {
		return new TableCell(domElement, this);
	};

	Table.prototype.newActiveCell = function(domElement) {
		var cell = new TableCell(domElement, this);
		cell.activate();
		return cell;
	};

	/**
	 * @return the rows of the table as an array of DOM nodes
	 */
	Table.prototype.getRows = function () {
		//W3C DOM property .rows supported by all modern browsers
		var rows = this.obj.get( 0 ).rows;
		//converts the HTMLCollection to a real array
		return jQuery.makeArray( rows );
	};

	return Table;
});
