/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
 *
 * == BEGIN LICENSE ==
 *
 * Licensed under the terms of any of the following licenses at your
 * choice:
 *
 *  - GNU General Public License Version 2 or later (the "GPL")
 *    http://www.gnu.org/licenses/gpl.html
 *
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 *
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 *
 * == END LICENSE ==
 *
 * Manage table operations.
 */

var FCKTableHandler = new Object() ;

FCKTableHandler.InsertRow = function( insertBefore )
{
	// Get the row where the selection is placed in.
	var oRow = FCKSelection.MoveToAncestorNode( 'TR' ) ;
	if ( !oRow ) return ;

	// Create a clone of the row.
	var oNewRow = oRow.cloneNode( true ) ;

	// Insert the new row (copy) before of it.
	oRow.parentNode.insertBefore( oNewRow, oRow ) ;

	// Clean one of the rows to produce the illusion of inserting an empty row before or after.
	FCKTableHandler.ClearRow( insertBefore ? oNewRow : oRow ) ;
}

FCKTableHandler.DeleteRows = function( row )
{
	// If no row has been passed as a parameter,
	// then get the row where the selection is placed in.
	if ( !row )
		row = FCKSelection.MoveToAncestorNode( 'TR' ) ;
	if ( !row ) return ;

	// Get the row's table.
	var oTable = FCKTools.GetElementAscensor( row, 'TABLE' ) ;

	// If just one row is available then delete the entire table.
	if ( oTable.rows.length == 1 )
	{
		FCKTableHandler.DeleteTable( oTable ) ;
		return ;
	}

	// Delete the row.
	row.parentNode.removeChild( row ) ;
}

FCKTableHandler.DeleteTable = function( table )
{
	// If no table has been passed as a parameer,
	// then get the table where the selection is placed in.
	if ( !table )
	{
		table = FCKSelection.GetSelectedElement() ;
		if ( !table || table.tagName != 'TABLE' )
			table = FCKSelection.MoveToAncestorNode( 'TABLE' ) ;
	}
	if ( !table ) return ;

	// Delete the table.
	FCKSelection.SelectNode( table ) ;
	FCKSelection.Collapse();
	table.parentNode.removeChild( table ) ;
}

FCKTableHandler.InsertColumn = function( insertBefore )
{
	// Get the cell where the selection is placed in.
	var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ;

	if ( !oCell ) 
	{
		// oCell not fouund could be due to multiple selection ranges on tables in Gecko.
		// We have another function to find the table cell for this special case.
		if ( ! FCKBrowserInfo.IsIE )
		{
			var node = this._GetSelectedCellFromGecko( true ) ;
			if ( node )
				oCell = node ;
			else
				return null ;
		}
		else
			return null ;
	}

	// Get the cell's table.
	var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;

	var iIndex = oCell.cellIndex ;

	// Loop throw all rows available in the table.
	for ( var i = 0 ; i < oTable.rows.length ; i++ )
	{
		// Get the row.
		var oRow = oTable.rows[i] ;

		// If the row doens't have enought cells, ignore it.
		if ( oRow.cells.length < ( iIndex + 1 ) )
			continue ;

		oCell = oRow.cells[iIndex].cloneNode(false) ;

		if ( FCKBrowserInfo.IsGecko )
			oCell.innerHTML = GECKO_BOGUS ;

		// Get back the currently selected cell.
		var oBaseCell = oRow.cells[iIndex] ;

		if ( insertBefore )
			oRow.insertBefore( oCell, oBaseCell ) ;
		else if ( oBaseCell.nextSibling )
			oRow.insertBefore( oCell, oBaseCell.nextSibling ) ;
		else
			oRow.appendChild( oCell ) ;
	}
}

FCKTableHandler.DeleteColumns = function()
{
	// Get the cell where the selection is placed in.
	var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ;

	if ( !oCell ) return ;

	// Get the cell's table.
	var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;

	// Get the cell index.
	var iIndex = oCell.cellIndex ;

	// Loop throw all rows (from down to up, because it's possible that some
	// rows will be deleted).
	for ( var i = oTable.rows.length - 1 ; i >= 0 ; i-- )
	{
		// Get the row.
		var oRow = oTable.rows[i] ;

		// If the cell to be removed is the first one and the row has just one cell.
		if ( iIndex == 0 && oRow.cells.length == 1 )
		{
			// Remove the entire row.
			FCKTableHandler.DeleteRows( oRow ) ;
			continue ;
		}

		// If the cell to be removed exists the delete it.
		if ( oRow.cells[iIndex] )
			oRow.removeChild( oRow.cells[iIndex] ) ;
	}
}

FCKTableHandler.InsertCell = function( cell, insertBefore )
{
	// Get the cell where the selection is placed in.
	var oCell = cell ? cell : FCKSelection.MoveToAncestorNode( 'TD' ) ;
	if ( !oCell ) 
	{
		// oCell not fouund could be due to multiple selection ranges on tables in Gecko.
		// We have another function to find the table cell for this special case.
		if ( ! FCKBrowserInfo.IsIE )
		{
			var node = this._GetSelectedCellFromGecko( true ) ;
			if ( node )
				oCell = node ;
			else
				return null ;
		}
		else
			return null ;
	}

	// Create the new cell element to be added.
	var oNewCell = FCK.EditorDocument.createElement( 'TD' ) ;
	if ( FCKBrowserInfo.IsGecko )
		oNewCell.innerHTML = GECKO_BOGUS ;
//	oNewCell.innerHTML = "&nbsp;" ;

	if ( !insertBefore && oCell.cellIndex == oCell.parentNode.cells.length - 1 )
		oCell.parentNode.appendChild( oNewCell ) ;
	else
		oCell.parentNode.insertBefore( oNewCell, insertBefore ? oCell : oCell.nextSibling ) ;

	return oNewCell ;
}

FCKTableHandler.DeleteCell = function( cell )
{
	// If this is the last cell in the row.
	if ( cell.parentNode.cells.length == 1 )
	{
		// Delete the entire row.
		FCKTableHandler.DeleteRows( FCKTools.GetElementAscensor( cell, 'TR' ) ) ;
		return ;
	}

	// Delete the cell from the row.
	cell.parentNode.removeChild( cell ) ;
}

FCKTableHandler.DeleteCells = function()
{
	var aCells = FCKTableHandler.GetSelectedCells() ;

	for ( var i = aCells.length - 1 ; i >= 0  ; i-- )
	{
		FCKTableHandler.DeleteCell( aCells[i] ) ;
	}
}

FCKTableHandler.MergeCells = function()
{
	// Get all selected cells.
	var aCells = FCKTableHandler.GetSelectedCells() ;

	// At least 2 cells must be selected.
	if ( aCells.length < 2 )
		return ;

	// The merge can occour only if the selected cells are from the same row.
	if ( aCells[0].parentNode != aCells[aCells.length-1].parentNode )
		return ;

	// Calculate the new colSpan for the first cell.
	var iColSpan = isNaN( aCells[0].colSpan ) ? 1 : aCells[0].colSpan ;

	var sHtml = '' ;
	var oCellsContents = FCK.EditorDocument.createDocumentFragment() ;

	for ( var i = aCells.length - 1 ; i >= 0 ; i-- )
	{
		var eCell = aCells[i] ;

		// Move its contents to the document fragment.
		for ( var c = eCell.childNodes.length - 1 ; c >= 0 ; c-- )
		{
			var eChild = eCell.removeChild( eCell.childNodes[c] ) ;

			if ( ( eChild.hasAttribute && eChild.hasAttribute('_moz_editor_bogus_node') ) || ( eChild.getAttribute && eChild.getAttribute( 'type', 2 ) == '_moz' ) )
				continue ;

				oCellsContents.insertBefore( eChild, oCellsContents.firstChild ) ;
		}

		if ( i > 0 )
		{
			// Accumulate the colspan of the cell.
			iColSpan += isNaN( eCell.colSpan ) ? 1 : eCell.colSpan ;

			// Delete the cell.
			FCKTableHandler.DeleteCell( eCell ) ;
		}
	}

	// Set the innerHTML of the remaining cell (the first one).
	aCells[0].colSpan = iColSpan ;

	if ( FCKBrowserInfo.IsGecko && oCellsContents.childNodes.length == 0 )
		aCells[0].innerHTML = GECKO_BOGUS ;
	else
		aCells[0].appendChild( oCellsContents ) ;
}

FCKTableHandler.SplitCell = function()
{
	// Check that just one cell is selected, otherwise return.
	var aCells = FCKTableHandler.GetSelectedCells() ;
	if ( aCells.length != 1 )
		return ;

	var aMap = this._CreateTableMap( aCells[0].parentNode.parentNode ) ;
	var iCellIndex = FCKTableHandler._GetCellIndexSpan( aMap, aCells[0].parentNode.rowIndex , aCells[0] ) ;

	var aCollCells = this._GetCollumnCells( aMap, iCellIndex ) ;

	for ( var i = 0 ; i < aCollCells.length ; i++ )
	{
		if ( aCollCells[i] == aCells[0] )
		{
			var oNewCell = this.InsertCell( aCells[0], false ) ;
			if ( !isNaN( aCells[0].rowSpan ) && aCells[0].rowSpan > 1 )
				oNewCell.rowSpan = aCells[0].rowSpan ;
		}
		else
		{
			if ( isNaN( aCollCells[i].colSpan ) )
				aCollCells[i].colSpan = 2 ;
			else
				aCollCells[i].colSpan += 1 ;
		}
	}
}

// Get the cell index from a TableMap.
FCKTableHandler._GetCellIndexSpan = function( tableMap, rowIndex, cell )
{
	if ( tableMap.length < rowIndex + 1 )
		return null ;

	var oRow = tableMap[ rowIndex ] ;

	for ( var c = 0 ; c < oRow.length ; c++ )
	{
		if ( oRow[c] == cell )
			return c ;
	}

	return null ;
}

// Get the cells available in a collumn of a TableMap.
FCKTableHandler._GetCollumnCells = function( tableMap, collumnIndex )
{
	var aCollCells = new Array() ;

	for ( var r = 0 ; r < tableMap.length ; r++ )
	{
		var oCell = tableMap[r][collumnIndex] ;
		if ( oCell && ( aCollCells.length == 0 || aCollCells[ aCollCells.length - 1 ] != oCell ) )
			aCollCells[ aCollCells.length ] = oCell ;
	}

	return aCollCells ;
}

// This function is quite hard to explain. It creates a matrix representing all cells in a table.
// The difference here is that the "spanned" cells (colSpan and rowSpan) are duplicated on the matrix
// cells that are "spanned". For example, a row with 3 cells where the second cell has colSpan=2 and rowSpan=3
// will produce a bi-dimensional matrix with the following values (representing the cells):
//		Cell1, Cell2, Cell2, Cell 3
//		Cell4, Cell2, Cell2, Cell 5
FCKTableHandler._CreateTableMap = function( table )
{
	var aRows = table.rows ;

	// Row and Collumn counters.
	var r = -1 ;

	var aMap = new Array() ;

	for ( var i = 0 ; i < aRows.length ; i++ )
	{
		r++ ;
		if ( !aMap[r] )
			aMap[r] = new Array() ;

		var c = -1 ;

		for ( var j = 0 ; j < aRows[i].cells.length ; j++ )
		{
			var oCell = aRows[i].cells[j] ;

			c++ ;
			while ( aMap[r][c] )
				c++ ;

			var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ;
			var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ;

			for ( var rs = 0 ; rs < iRowSpan ; rs++ )
			{
				if ( !aMap[r + rs] )
					aMap[r + rs] = new Array() ;

				for ( var cs = 0 ; cs < iColSpan ; cs++ )
				{
					aMap[r + rs][c + cs] = aRows[i].cells[j] ;
				}
			}

			c += iColSpan - 1 ;
		}
	}
	return aMap ;
}

FCKTableHandler.ClearRow = function( tr )
{
	// Get the array of row's cells.
	var aCells = tr.cells ;

	// Replace the contents of each cell with "nothing".
	for ( var i = 0 ; i < aCells.length ; i++ )
	{
		if ( FCKBrowserInfo.IsGecko )
			aCells[i].innerHTML = GECKO_BOGUS ;
		else
			aCells[i].innerHTML = '' ;
	}
}

FCKTableHandler._GetSelectedCellFromGecko = function( getLastCell )
{
	var sel = FCK.EditorWindow.getSelection() ;
	if ( sel.rangeCount < 1 )
		return null ;
	var range = getLastCell ? sel.getRangeAt( sel.rangeCount - 1 ) : sel.getRangeAt( 0 ) ;
	var node = getLastCell ? range.endContainer : range.startContainer ;

	// Gecko returns a <TR> as startContainer and endContainer when cells are selected.
	// We need to find back the final <TD|TH> that's being selected.
	if ( node.nodeType == 1 && node.tagName.toLowerCase() == 'tr' )
	{
		for ( var i = range.startOffset ; i <= range.endOffset ; i++ )
		{
			var candidate = node.childNodes[i] ;
			var tag = candidate.tagName.toLowerCase() ;
			if ( candidate.nodeType == 1 && ( tag == 'td' || tag == 'th' ) )
			{
				node = node.childNodes[i] ;
				break ;
			}
		}
	}
	// For safety, we have to be ready for the 'usual' case as well.
	else
	{
		while ( node && node.tagName.toLowerCase() != 'td' && node.tagName.toLowerCase() != 'th' )
			node = node.parentNode ;
	}

	// Have we found the <TD|TH> node we wanted, finally? If yes, return the found node. If not, abort.
	if ( node.tagName.toLowerCase() != 'td' && node.tagName.toLowerCase() != 'th' )
		return null ;
	else
		return node ;
}
