#ifndef _T_JPackedTableData
#define _T_JPackedTableData

/******************************************************************************
 JPackedTableData.tmpl

	Template for storing values and structs in a table.
	The data is grouped by column into a single JRunArray.

	*** Since we use a JRunArray, this class cannot be use to store objects.
		Use JObjTableData instead.

	BASE CLASS = JTableData

	Copyright © 1997 by John Lindal & Glenn Bach. All rights reserved.

 ******************************************************************************/

#include <JPackedTableData.h>
#include <JTable.h>
#include <JMinMax.h>
#include <jAssert.h>

/******************************************************************************
 Constructor

 ******************************************************************************/

template <class T>
JPackedTableData<T>::JPackedTableData
	(
	const T& defValue
	)
	:
	JTableData(),
	itsDefValue(defValue)
{
	itsData = new JRunArray< T >;
	assert( itsData != NULL );
}

/******************************************************************************
 Copy constructor

 ******************************************************************************/

template <class T>
JPackedTableData<T>::JPackedTableData
	(
	const JPackedTableData<T>& source
	)
	:
	JTableData(source),
	itsDefValue(source.itsDefValue)
{
	itsData = new JRunArray< T >(*(source.itsData));
	assert( itsData != NULL );
}

/******************************************************************************
 Destructor

 ******************************************************************************/

template <class T>
JPackedTableData<T>::~JPackedTableData()
{
	delete itsData;
}

/******************************************************************************
 GetDefaultValue

 ******************************************************************************/

template <class T>
const T
JPackedTableData<T>::GetDefaultValue()
	const
{
	return itsDefValue;
}

/******************************************************************************
 SetDefaultValue

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetDefaultValue
	(
	const T& data
	)
{
	itsDefValue = data;
}

/******************************************************************************
 GetData

 ******************************************************************************/

template <class T>
const JRunArray< T >&
JPackedTableData<T>::GetData()
	const
{
	return *itsData;
}

/******************************************************************************
 GetElement

 ******************************************************************************/

template <class T>
const T
JPackedTableData<T>::GetElement
	(
	const JIndex row,
	const JIndex col
	)
	const
{
	assert( RowIndexValid(row) && ColIndexValid(col) );
	return itsData->GetElement(RCToI(row,col));
}

template <class T>
const T
JPackedTableData<T>::GetElement
	(
	const JPoint& cell
	)
	const
{
	assert( RowIndexValid(cell.y) && ColIndexValid(cell.x) );
	return itsData->GetElement(RCToI(cell));
}

/******************************************************************************
 SetElement

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetElement
	(
	const JIndex	row,
	const JIndex	col,
	const T&		data
	)
{
	assert( RowIndexValid(row) && ColIndexValid(col) );
	const JIndex index = RCToI(row,col);
	if (data != itsData->GetElement(index))
		{
		itsData->SetElement(index, data);
		Broadcast(JTableData::RectChanged(row,col));
		}
}

template <class T>
void
JPackedTableData<T>::SetElement
	(
	const JPoint&	cell,
	const T&		data
	)
{
	assert( RowIndexValid(cell.y) && ColIndexValid(cell.x) );
	const JIndex index = RCToI(cell);
	if (data != itsData->GetElement(index))
		{
		itsData->SetElement(index, data);
		Broadcast(JTableData::RectChanged(cell));
		}
}

/******************************************************************************
 GetRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::GetRow
	(
	const JIndex	index,
	JOrderedSet<T>*	rowData
	)
	const
{
	assert( RowIndexValid(index) );

	rowData->RemoveAll();

	const JSize colCount = GetColCount();
	for (JIndex i=1; i<=colCount; i++)
		{
		rowData->InsertElementAtIndex(i, GetElement(index,i));
		}
}

/******************************************************************************
 SetRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetRow
	(
	const JIndex			index,
	const JOrderedSet<T>&	rowData
	)
{
	const JSize colCount = GetColCount();
	assert( RowIndexValid(index) );
	assert( rowData.GetElementCount() == colCount );

	for (JIndex i=1; i<=colCount; i++)
		{
		itsData->SetElement(RCToI(index,i), rowData.GetElement(i));
		}

	Broadcast(JTableData::RectChanged(JRect(index, 1, index+1, colCount+1)));
}

template <class T>
void
JPackedTableData<T>::SetRow
	(
	const JIndex	index,
	const T&		data
	)
{
	SetPartialRow(index, 1, GetColCount(), data);
}

/******************************************************************************
 SetPartialRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetPartialRow
	(
	const JIndex	rowIndex,
	const JIndex	firstColIndex,
	const JIndex	lastColIndex,
	const T&		data
	)
{
	assert( RowIndexValid(rowIndex) &&
			ColIndexValid(firstColIndex) && ColIndexValid(lastColIndex) &&
			firstColIndex <= lastColIndex );

	for (JIndex i=firstColIndex; i<=lastColIndex; i++)
		{
		itsData->SetElement(RCToI(rowIndex,i), data);
		}

	Broadcast(JTableData::RectChanged(
		JRect(rowIndex, firstColIndex, rowIndex+1, lastColIndex+1)));
}

/******************************************************************************
 GetCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::GetCol
	(
	const JIndex	index,
	JOrderedSet<T>*	colData
	)
	const
{
	assert( ColIndexValid(index) );

	colData->RemoveAll();

	const JSize rowCount = GetRowCount();
	for (JIndex i=1; i<=rowCount; i++)
		{
		colData->InsertElementAtIndex(i, GetElement(i,index));
		}
}

/******************************************************************************
 SetCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetCol
	(
	const JIndex			index,
	const JOrderedSet<T>&	colData
	)
{
	const JSize rowCount = GetRowCount();
	assert( ColIndexValid(index) );
	assert( colData.GetElementCount() == rowCount );

	for (JIndex i=1; i<=rowCount; i++)
		{
		itsData->SetElement(RCToI(i,index), colData.GetElement(i));
		}

	Broadcast(JTableData::RectChanged(JRect(1, index, rowCount+1, index+1)));
}

template <class T>
void
JPackedTableData<T>::SetCol
	(
	const JIndex	index,
	const T&		data
	)
{
	SetPartialCol(index, 1, GetRowCount(), data);
}

/******************************************************************************
 SetPartialCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetPartialCol
	(
	const JIndex	colIndex,
	const JIndex	firstRowIndex,
	const JIndex	lastRowIndex,
	const T&		data
	)
{
	SetRect(JRect(firstRowIndex, colIndex, lastRowIndex+1, colIndex+1), data);
}

/******************************************************************************
 SetRect

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::SetRect
	(
	const JRect&	rect,
	const T&		data
	)
{
	SetRect(rect.left, rect.top, rect.width(), rect.height(), data);
}

template <class T>
void
JPackedTableData<T>::SetRect
	(
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h,
	const T&			data
	)
{
	assert( w > 0 && h > 0 );

	const JIndex x2 = x + w - 1;
	const JIndex y2 = y + h - 1;

	assert( RowIndexValid(y)  && ColIndexValid(x) &&
			RowIndexValid(y2) && ColIndexValid(x2) );

	for (JIndex j=x; j<=x2; j++)
		{
		itsData->SetNextElements(RCToI(y,j), h, data);
		}

	Broadcast(JTableData::RectChanged(JRect(y,x,y2+1,x2+1)));
}

/******************************************************************************
 ApplyToRect

	This ought to be done one -run- at a time, and should be implemented
	in JRunArray.

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::ApplyToRect
	(
	const JRect& rect,
	T (*f)(const T&)
	)
{
	ApplyToRect(rect.left, rect.top, rect.width(), rect.height(), f);
}

template <class T>
void
JPackedTableData<T>::ApplyToRect
	(
	const JCoordinate x,
	const JCoordinate y,
	const JCoordinate w,
	const JCoordinate h,
	T (*f)(const T&)
	)
{
	assert( w > 0 && h > 0 );

	const JIndex x2 = x + w - 1;
	const JIndex y2 = y + h - 1;

	assert( RowIndexValid(y)  && ColIndexValid(x) &&
			RowIndexValid(y2) && ColIndexValid(x2) );

	JIndex prevDataIndex = RCToI(y,x);
	JIndex runIndex, firstInRun;
	JBoolean ok = itsData->FindRun(prevDataIndex, &runIndex, &firstInRun);
	assert( ok );

	for (JIndex j=x; j<=x2; j++)
		{
		for (JIndex i=y; i<=y2; i++)
			{
			const JIndex currDataIndex = RCToI(i,j);
			ok = itsData->FindRun(prevDataIndex, currDataIndex, &runIndex, &firstInRun);
			assert( ok );
			itsData->SetNextElements(currDataIndex, 1,
									 f(itsData->GetRunDataRef(runIndex)),
									 &runIndex, &firstInRun);
			prevDataIndex = currDataIndex;
			}
		}

	Broadcast(JTableData::RectChanged(JRect(y,x,y2+1,x2+1)));
}

/******************************************************************************
 InsertRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::InsertRow
	(
	const JIndex			index,
	const JOrderedSet<T>*	initData
	)
{
	assert( initData == NULL || initData->GetElementCount() == GetColCount() );

	const JIndex trueIndex = JMin(index, GetRowCount()+1);

	const JSize colCount = GetColCount();
	if (initData != NULL)
		{
		for (JIndex i=colCount; i>=1; i--)
			{
			itsData->InsertElementAtIndex(RCToI(trueIndex, i) , initData->GetElement(i));
			}
		}
	else
		{
		for (JIndex i=colCount; i>=1; i--)
			{
			itsData->InsertElementAtIndex(RCToI(trueIndex, i), itsDefValue);
			}
		}

	RowAdded();
	Broadcast(JTableData::RowInserted(trueIndex));
}

/******************************************************************************
 Row manipulations

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::PrependRow
	(
	const JOrderedSet<T>* initData
	)
{
	InsertRow(1, initData);
}

template <class T>
void
JPackedTableData<T>::AppendRow
	(
	const JOrderedSet<T>* initData
	)
{
	InsertRow(GetRowCount()+1, initData);
}

/******************************************************************************
 DuplicateRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::DuplicateRow
	(
	const JIndex origIndex,
	const JIndex newIndex
	)
{
	assert( RowIndexValid(origIndex) );

	const JIndex trueIndex = JMin(newIndex, GetRowCount()+1);

	const JSize colCount = GetColCount();
	for (JIndex i=colCount; i>=1; i--)
		{
		const T element = GetElement(origIndex, i);
		itsData->InsertElementAtIndex(RCToI(trueIndex,i), element);
		}

	RowAdded();
	Broadcast(JTableData::RowDuplicated(origIndex, trueIndex));
}

/******************************************************************************
 RemoveRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::RemoveRow
	(
	const JIndex index
	)
{
	assert( RowIndexValid(index) );

	const JSize colCount = GetColCount();
	for (JIndex i=colCount; i>=1; i--)
		{
		itsData->RemoveElement(RCToI(index,i));
		}

	RowDeleted();
	Broadcast(JTableData::RowRemoved(index));
}

/******************************************************************************
 RemoveAllRows

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::RemoveAllRows()
{
	itsData->RemoveAll();
	SetRowCount(0);
	Broadcast(JTableData::AllRowsRemoved());
}

/******************************************************************************
 MoveRow

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::MoveRow
	(
	const JIndex origIndex,
	const JIndex newIndex
	)
{
	assert( RowIndexValid(origIndex) && RowIndexValid(newIndex) );

	if (origIndex != newIndex)
		{
		const JSize colCount = GetColCount();
		for (JIndex i=1; i<=colCount; i++)
			{
			itsData->MoveElementToIndex(RCToI(origIndex, i), RCToI(newIndex, i));
			}

		Broadcast(JTableData::RowMoved(origIndex, newIndex));
		}
}

/******************************************************************************
 InsertCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::InsertCol
	(
	const JIndex			index,
	const JOrderedSet<T>*	initData
	)
{
	assert( initData == NULL || initData->GetElementCount() == GetRowCount() );

	const JIndex trueIndex = JMin(index, GetColCount()+1);

	const JSize rowCount = GetRowCount();
	if (initData != NULL)
		{
		const JIndex startIndex = RCToI(1,index);
		for (JIndex i=1; i<=rowCount; i++)
			{
			itsData->InsertElementAtIndex(startIndex + i-1, initData->GetElement(i));
			}
		}
	else if (rowCount > 0)
		{
		itsData->InsertElementsAtIndex(RCToI(1,index), itsDefValue, rowCount);
		}

	ColAdded();
	Broadcast(JTableData::ColInserted(trueIndex));
}

/******************************************************************************
 Column manipulations

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::PrependCol
	(
	const JOrderedSet<T>* initData
	)
{
	InsertCol(1, initData);
}

template <class T>
void
JPackedTableData<T>::AppendCol
	(
	const JOrderedSet<T>* initData
	)
{
	InsertCol(GetColCount()+1, initData);
}

/******************************************************************************
 DuplicateCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::DuplicateCol
	(
	const JIndex origIndex,
	const JIndex newIndex
	)
{
	assert( ColIndexValid(origIndex) );

	const JIndex trueIndex = JMin(newIndex, GetColCount()+1);

	const JIndex rowCount = GetRowCount();
	if (rowCount > 0)
		{
		const JIndex toIndex = RCToI(1,trueIndex);
		itsData->InsertElementsAtIndex(toIndex, itsDefValue, rowCount);
		ColAdded();

		JIndex fromIndex = origIndex;
		if (fromIndex >= trueIndex)
			{
			fromIndex++;
			}
		fromIndex = RCToI(1,fromIndex);

		for (JIndex i=1; i<=rowCount; i++)
			{
			itsData->SetElement(toIndex + i-1, itsData->GetElement(fromIndex + i-1));
			}
		}
	else
		{
		ColAdded();
		}

	Broadcast(JTableData::ColDuplicated(origIndex, trueIndex));
}

/******************************************************************************
 RemoveCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::RemoveCol
	(
	const JIndex index
	)
{
	assert( ColIndexValid(index) );

	const JIndex j = RCToI(1, index);

	const JIndex rowCount = GetRowCount();
	for (JIndex i=1; i<=rowCount; i++)
		{
		itsData->RemoveElement(j);
		}

	ColDeleted();
	Broadcast(JTableData::ColRemoved(index));
}

/******************************************************************************
 RemoveAllCols

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::RemoveAllCols()
{
	itsData->RemoveAll();
	SetColCount(0);
	Broadcast(JTableData::AllColsRemoved());
}

/******************************************************************************
 MoveCol

 ******************************************************************************/

template <class T>
void
JPackedTableData<T>::MoveCol
	(
	const JIndex origIndex,
	const JIndex newIndex
	)
{
	assert( ColIndexValid(origIndex) && ColIndexValid(newIndex) );

	if (origIndex == newIndex)
		{
		return;
		}

	const JIndex rowCount = GetRowCount();
	if (rowCount > 0)
		{
		const JIndex fromIndex = RCToI(1,origIndex);
		const JIndex toIndex   = RCToI(1,newIndex);

		if (origIndex < newIndex)
			{
			for (JIndex i=rowCount; i>=1; i--)
				{
				itsData->MoveElementToIndex(fromIndex + i-1, toIndex + i-1);
				}
			}
		else	// origIndex > newIndex
			{
			for (JIndex i=1; i<=rowCount; i++)
				{
				itsData->MoveElementToIndex(fromIndex + i-1, toIndex + i-1);
				}
			}
		}

	Broadcast(JTableData::ColMoved(origIndex, newIndex));
}

/******************************************************************************
 SetAllElements

 ******************************************************************************/

template <class T>
void	
JPackedTableData<T>::SetAllElements
	(
	const T& data
	)
{
	const JIndex count = itsData->GetElementCount();
	T value;
	if (count > 0 &&
		(!AllElementsEqual(&value) || value != data))
		{
		itsData->RemoveAll();
		itsData->AppendElements(data, count);
		Broadcast(JTableData::RectChanged(JRect(1, 1, GetRowCount()+1, GetColCount()+1)));
		}
}

/******************************************************************************
 ClearAllElements

	Sets all elements to the default value.

 ******************************************************************************/

template <class T>
void	
JPackedTableData<T>::ClearAllElements()
{
	SetAllElements(itsDefValue);
}

/******************************************************************************
 AllElementsEqual

	If all the elements are equal, this function returns kTrue and sets
	data to the single value.

	If there are no elements, it returns kFalse, mainly because the
	second version of the function doesn't have any data to return.

 ******************************************************************************/

template <class T>
JBoolean	
JPackedTableData<T>::AllElementsEqual()
	const
{
	return JConvertToBoolean( itsData->GetRunCount() == 1 );
}

template <class T>
JBoolean	
JPackedTableData<T>::AllElementsEqual
	(
	T* data
	)
	const
{
	if (itsData->GetRunCount() == 1)
		{
		*data = itsData->GetRunDataRef(1);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 CountElementsWithValue

 ******************************************************************************/

template <class T>
JSize	
JPackedTableData<T>::CountElementsWithValue
	(
	const T& data
	)
	const
{
	const JSize runCount = itsData->GetRunCount();
	JSize valueCount     = 0;

	for (JIndex i=1; i<=runCount; i++)
		{
		if (itsData->GetRunDataRef(i) == data)
			{
			valueCount += itsData->GetRunLength(i);
			}
		}

	return valueCount;
}

/******************************************************************************
 RCToI

	Convert row and column values to an index into itsData.

 ******************************************************************************/

template <class T>
JIndex 
JPackedTableData<T>::RCToI
	(
	const JIndex row, 
	const JIndex col
	) 
	const
{
	return GetRowCount() * (col - 1) + row;
}

template <class T>
JIndex 
JPackedTableData<T>::RCToI
	(
	const JPoint& cell
	) 
	const
{
	return GetRowCount() * (cell.x - 1) + cell.y;
}

#endif

// Instantiate the template for the specified type.

#if defined JTemplateType && ! defined JOnlyWantTemplateDefinition
	#define JTemplateName JPackedTableData
	#include <instantiate_template.h>
	#undef JTemplateName
#endif
