/*===============================
Copyright © 1998-2002 Brian Hall

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*===============================*/
#include "BlugsPrivate.h"

typedef struct
{
	UInt16	parent;
	UInt16	row;
} BLReinsertArrayEntry;

typedef struct
{
	BlugsRef						list;
	UInt16					sortCol;
	Boolean					smallAtTop;
} BLStringSortParamBlock, *BLStringSortParamPtr;

typedef struct
{
	BlugsRef				list;
	UInt32			dataSize;
	UInt16			sortCol;			
} BLStringSearchParamBlock, *BLStringSearchParamPtr;

static void		local_BLSortDisclosure( Boolean inSmallAtTop, UInt16 inSortColumn, BlugsRef inList );
static void		local_BLSortNoDisclosure( Boolean inSmallAtTop, UInt16 inSortColumn, BlugsRef inList );
static void		local_BLSortRange( UInt16 inSortArray[], UInt16 inCount, UInt16 inSortColumn,
									Boolean inSmallAtTop, BlugsRef inList );
static SInt32	local_BLStringSortProc( const Ptr inData1P, const Ptr inData2P, UInt32 inRefCon );
static void		local_BLRearrange( UInt16 inSortArray[], BlugsRef inList );
static OSErr	local_BLRearrangeIndex( UInt16 inSortArray[], BlugsRef inList );
static void		local_BLReinsert( UInt16 inSortArray[], UInt16 inSortItems,
									BLReinsertArrayEntry inReinsertArray[], UInt16 inReinsertItems,
									UInt16 inMasterArray[], UInt16 inStartHere );
static UInt16*	local_BLMakeSortArrays( UInt16 inMasterArray[], UInt16 inRangeStart,
										UInt16 inRangeCount, UInt16 inThisLevelCount,
										UInt16 inLevel, BlugsRef inList, UInt16 inReinsertCount,
										BLReinsertArrayEntry* inReinsertArray );
static SInt32	local_BLStringSearchProc( const Ptr inData, UInt32 inSearchData, UInt32 inRefCon );



//===============================
BL_PROC OSErr BLGetSortState( BLSortState* outState, UInt16* outColumn, BlugsRef inList )
{
	OSErr		err;
	BLSortState	state = blSortStateUnsorted;
	UInt16		column = 0;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		if (bl_Sortable(inList))
		{
			column = BLGetColumnFromUID( &(*inList)->primarySortColumn, inList );
			if (column) state = (*inList)->sortState;
		}
	}
	if (outState) *outState = state;
	if (outColumn) *outColumn = column;
	return err;
}

//===============================
BL_PROC OSErr BLSort( BLSortState inState, UInt16 inColumn, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		//bl_Sortable is checked in _BLSort; no need to do it here.
		//if (bl_Sortable(inList))
		//{
			if (inColumn) err = _BLValidateColumn( inColumn, inList );
			if (!err)
			{
				BLGetColumnUID( inColumn, &(*inList)->primarySortColumn, inList );
				_BLSort( inState, inList );
			}
		//}
	}
	return err;
}

//===============================
// Derives the basic sort button state, based on the sort status of the list
//	and whether or not it's active. Pressed status will be determined by the routine that
//	calls here.
BLSortState _BLSortButtonState( BlugsRef inList )
{
	BLSortState	state = 0;
	
	if (!bl_Active(inList) || !bl_NRows(inList) || !bl_NCols(inList))
		bl_Set(state,blSortButtonInactive);
	state |= (*inList)->sortState;
	return state;
}

//===============================
void _BLSort( BLSortState inState, BlugsRef inList )
{
	_BLValidateList( inList );
	if (bl_Sortable(inList))
	{
		UInt16			sortColumn;
		BLTitleBarHdl	hBar;
		
		sortColumn = BLGetColumnFromUID( &(*inList)->primarySortColumn, inList );
		if (bl_Test(inState,blSortStateNoChange)) inState = (*inList)->sortState;
		hBar = bl_HTitle(inList);
		// If the list is sorted, we sort.
		if (bl_Test(inState,blSortStateSorted))
		{
			Boolean			smallAtTop;
			
			smallAtTop = !bl_Test(inState,blSortStateLargeToSmall);
			if (!sortColumn) inState = blSortStateUnsorted;
			else
			{
				UInt16	nRows;
				
				nRows = bl_NRows(inList);
				if (bl_NRows(inList))
				{
					if (bl_NCols(inList))
					{
						// Lock everything down!
						// Hey! No need to unlock; both the data array and index will be reallocated
						//HLock( (Handle)(bl_RowData(inList)) );
						//HLock( bl_Index(inList) );
						if (bl_Disclosure(inList)) local_BLSortDisclosure( smallAtTop, sortColumn, inList );
						else local_BLSortNoDisclosure( smallAtTop, sortColumn, inList );
						_AssertStr(nRows = bl_NRows(inList),"Internal error: sorting changed number of rows.");
					} // end if nCols
				} // end if nRows
			} // end if sort column
		} // sorted
		// Sync h title bar selection to sort column.
		if (hBar) BLGetColumnUID( sortColumn, &(*hBar)->whichSelected, inList );
		(*inList)->sortState = inState;
		_BLUpdateWorld( blUpdateBlit | blUpdateCorners, nil, inList );
	} // end if sortable
	//_AssertStr(nRows = bl_NRows(inList),"Error: sorting changed number of rows.");
}

//===============================
static void local_BLSortDisclosure( Boolean inSmallAtTop, UInt16 inSortColumn, BlugsRef inList )
{
	UInt16	*masterA, nRows;
	
	nRows = bl_NRows(inList);
	masterA = (UInt16*)NewPtr( nRows * sizeof(UInt16) );
	if (masterA)
	{
		UInt16	deepestLevel, i;
		OSErr	err = noErr;
		
		// Number the master array from 1 to nRows.
		for (i = 0; i < nRows; i++) masterA[i] = i + 1;
		deepestLevel = _BLDeepestDisclosureLevel( inList );
		// Do once for each disclosure level (level variable decrements at bottom of loop)
		// Abort if we run into memory problems.
		for (i = 0; i <= deepestLevel && err == noErr; i++)
		{
			UInt16		startHere = 1, thisLevelCount, thisLevel, *sortA;
			
			thisLevel = deepestLevel - i;
			while (startHere < nRows)
			{
				Boolean					foundOne;
				UInt16					rangeStart, rangeCount, reinsertCount;
				BLReinsertArrayEntry	*reinsertA;
				
				// Find a range of rows at the current disclosure level
				foundOne = _BLFindDisclosureRange( startHere, thisLevel, &rangeStart, &rangeCount,
													&thisLevelCount, inList );
				if (!foundOne) break;
				reinsertCount = rangeCount - thisLevelCount;
				if (reinsertCount)
				{
					reinsertA = (BLReinsertArrayEntry*)NewPtr( reinsertCount * sizeof(BLReinsertArrayEntry) );
					if (!reinsertA) err = memFullErr;
				}
				else reinsertA = nil;
				sortA = local_BLMakeSortArrays( masterA, rangeStart, rangeCount, thisLevelCount,
												thisLevel, inList, reinsertCount, reinsertA );
				if (!sortA)
				{
					if (reinsertA) DisposePtr( (Ptr)reinsertA );
					err = memFullErr;
					break;
				}
				local_BLSortRange( sortA, thisLevelCount, inSortColumn, inSmallAtTop, inList );
				// local_BLReinsert disposes of the arrays
				local_BLReinsert( sortA, thisLevelCount, reinsertA, reinsertCount, masterA, rangeStart );
				startHere = rangeStart + rangeCount;
			}
		}
		if (!err) local_BLRearrange( masterA, inList );
		DisposePtr( (Ptr)masterA );
	}
}

//===============================
// There is one content handler that will be called on to determine if a > b
// Sort array is initially 1,2,3... and shows final arrangement:
//	if sortArray[0] = 3 it means row 1 ends up as the third row.
static void local_BLSortNoDisclosure( Boolean inSmallAtTop, UInt16 inSortColumn, BlugsRef inList )
{
	UInt16	nRows, i, *sortArray;
	
	nRows = bl_NRows(inList);
	sortArray = (UInt16*)NewPtr( nRows * sizeof(UInt16) );
	if (sortArray)
	{
		// Number each position in the sort array
		for (i = 0; i < nRows; i++) sortArray[i] = i + 1;
		local_BLSortRange( sortArray, nRows, inSortColumn, inSmallAtTop, inList );
		local_BLRearrange( sortArray, inList );
		DisposePtr( (Ptr)sortArray );
	}
}

//===============================
static void local_BLSortRange( UInt16 inSortArray[], UInt16 inCount, UInt16 inSortColumn,
								Boolean inSmallAtTop, BlugsRef inList )
{
	BLStringSortParamBlock	pb;

	pb.list = inList;
	pb.sortCol = inSortColumn;
	pb.smallAtTop = inSmallAtTop;
	_VASort( nil, (Ptr)inSortArray, inCount, sizeof(UInt16), &local_BLStringSortProc, (UInt32)&pb );
}

//===============================
// The data elements contain pointers to row numbers, refCon is param ptr.
static SInt32 local_BLStringSortProc( const Ptr inData1P, const Ptr inData2P, UInt32 inRefCon )
{
	BLStringSortParamPtr	pp;
	BLCell					cell1, cell2;
	BLHandlerParamBlock		pb;
	SInt32					response;
	Boolean					smallAtTop, row1IsTitleRow = false, row2IsTitleRow = false;
	BlugsRef						list;
	UInt16					sortCol;
	Handle					indexH;
	
	pp = (BLStringSortParamPtr)inRefCon;
	cell1.row = *(UInt16*)inData1P;
	cell2.row = *(UInt16*)inData2P;
	_AssertStr( cell1.row && cell2.row, "Internal error: trying to sort row #0" );
	list = pp->list;
	smallAtTop = pp->smallAtTop;
	sortCol = pp->sortCol;
	cell1.col = cell2.col = sortCol;
	indexH = bl_Index(list);
	// We only consider it a title row if column > 2.
	// Otherwise it can be handled normally.
	if (cell1.col > 1) row1IsTitleRow = bl_Test(_BLGetRowFlags( cell1.row, list ),blRowIsTitleRow);
	if (cell2.col > 1) row2IsTitleRow = bl_Test(_BLGetRowFlags( cell2.row, list ),blRowIsTitleRow);
	// If only one is title row, it sorts before.
	// If both are title rows, they are considered equal.
	if (row1IsTitleRow || row2IsTitleRow)
	{
		if (row1IsTitleRow && !row2IsTitleRow) response = blCellSortFirstIsLess;
		else if (row2IsTitleRow && !row1IsTitleRow) response = blCellSortFirstIsGreater;
		else response = blCellSortEqual;
	}
	else
	{
		pb.inStorage2 = bl_CellIndexAddrFromIH(cell2,list,indexH)->data;
		response = _BLCallHandlerForCell( blCellSortMsg, cell1, &pb, list );
	}
	if (response == blCellSortEqual)
	{
		BLSecondarySortColumnProcPtr	secondaryProc;
		
		secondaryProc = (*list)->callbacks.secondaryProc;
		if (secondaryProc)
		{
			UInt16		secondaryCol;
			
			secondaryCol = (*secondaryProc)( sortCol, list );
			if (secondaryCol != sortCol && secondaryCol != 0)
			{
				UInt32					features2;
				OSErr					err;
				
				cell1.col = cell2.col = secondaryCol;
				err = _BLGetCellHandlerFeatures( cell1, &features2, list );
				if (!err)
				{
					if (bl_Test(features2,blWantsSort))
					{
						// If there's a title row (still) secondary sorting can only help if
						//	this column is #1.
						if ((row1IsTitleRow || row2IsTitleRow) && secondaryCol > 1)
							response = blCellSortFirstIsLess;
						else
						{
							pb.inStorage2 = bl_CellIndexAddrFromIH(cell2,list,indexH)->data;
							_BLCallHandlerForCell( blCellSortMsg, cell1, &pb, list );
						}
					}
				}
			}
		}
	}
	// Swap ordering judgement if sorting in reverse.
	if (!smallAtTop)
	{
		if (response == blCellSortFirstIsGreater) response = blCellSortFirstIsLess;
		else response = blCellSortFirstIsGreater;
	}
	return response;
}


//===============================
// Rearranges index entries and row data to reflect new ordering
static void local_BLRearrange( UInt16 inSortArray[], BlugsRef inList )
{
	OSErr		err;
	
	err = local_BLRearrangeIndex( inSortArray, inList );
	if (!err) _BLRearrangeRowData( inSortArray, inList );
}

//===============================
// Rearranges index entries to reflect new ordering
static OSErr local_BLRearrangeIndex( UInt16 inSortArray[], BlugsRef inList )
{
	UInt16		nRows, nCols;
	Handle		newIndex;
	Size		copySize;
	OSErr		err = noErr;
	
	nRows = bl_NRows(inList);
	nCols = bl_NCols(inList);
	copySize = nCols * sizeof(BLIndexRec);
	newIndex = NewHandle( nRows * copySize );
	if (!newIndex) err = memFullErr;
	else
	{
		Handle		oldIndex;
		BLIndexPtr	base, source, dest;
		UInt16		i;
		
		oldIndex = bl_Index(inList);
		base = (BLIndexPtr)*oldIndex;
		dest = (BLIndexPtr)*newIndex;
		for (i = 0; i < nRows; i++, dest += nCols)
		{
			source = base + ((inSortArray[i] - 1) * nCols);
			BlockMoveData( (Ptr)source, (Ptr)dest, copySize );
		}
		bl_Index(inList) = newIndex;
		DisposeHandle( oldIndex );
	}
	return err;
}

//===============================
// Puts the sorted rows back into the master array, keeping relative ordering
//	of children of sorted rows intact.
// If there was nothing to reinsert, the reinsert array may be nil.
static void local_BLReinsert( UInt16 inSortArray[], UInt16 inSortItems,
								BLReinsertArrayEntry inReinsertArray[], UInt16 inReinsertItems,
								UInt16 inMasterArray[], UInt16 inStartHere )
{
	UInt16	sortIndex = 0, total, i, startHereIndex;
	
	// startHere is a row number; we need to use it as a zero-based array index
	startHereIndex = inStartHere - 1;
	total = inReinsertItems + inSortItems;
	for (i = 0; i < total; i++)
	{
		UInt16	row, reinsertIndex;
		
		row = inSortArray[sortIndex];
		inMasterArray[startHereIndex + i] = row;
		// If any child rows left to insert, insert them
		// Insert all the reinsert array items whose hi 16 bits are equal to the sort row
		//	just inserted.
		for (reinsertIndex = 0; reinsertIndex < inReinsertItems; reinsertIndex++)
		{
			// For each reinsert array item, if it's associated with the sorted row
			//	then put it in the master array.
			if (inReinsertArray[reinsertIndex].parent == row)
			{
				i++;
				inMasterArray[startHereIndex + i] = inReinsertArray[reinsertIndex].row;
			}
		}
		sortIndex++;
	}
	DisposePtr( (Ptr)inSortArray );
	if (inReinsertArray) DisposePtr( (Ptr)inReinsertArray );
}

//===============================
// Allocates an array of row numbers for the desired disclosure level, and an
//	array of row numbers for rows at a deeper disclosure level, that have already been sorted.
static UInt16* local_BLMakeSortArrays( UInt16 inMasterArray[], UInt16 inRangeStart,
									UInt16 inRangeCount, UInt16 inThisLevelCount,
									UInt16 inLevel, BlugsRef inList, UInt16 inReinsertCount,
									BLReinsertArrayEntry* inReinsertArray )
{
#if !__DEBLUG__
	#pragma unused (inReinsertCount)
#endif
	UInt16*		mySortArray;
	
	mySortArray = (UInt16*)NewPtr( inThisLevelCount * sizeof(UInt16) );
	if (mySortArray)
	{
		//BLRowDataPtr	dataP;
		UInt16			reinsertIndex = 0, lastSortItem = 0, sortIndex = 0, i;
		
		//dataP = bl_NthRowDataAddr(inRangeStart,bl_RowData(inList));
		for (i = 0; i < inRangeCount; i++)
		{
			if (_BLGetRowDisclosureLevel( inRangeStart + i, inList ) == inLevel)
			{
				lastSortItem = inMasterArray[inRangeStart + i - 1];
				mySortArray[sortIndex] = lastSortItem;
				sortIndex++;
			}
			else
			{
				if (inReinsertArray)
				{
					_AssertStr( reinsertIndex + 1 <= inReinsertCount, "Internal error: extra row for reinsertion." );
					inReinsertArray[reinsertIndex].parent = lastSortItem;
					inReinsertArray[reinsertIndex].row = inMasterArray[inRangeStart + i - 1];
					reinsertIndex++;
				}
			}
			//dataP++;
		}
	}
	return mySortArray;
}


#pragma mark -
//===============================
// Can return:
//	errMessageNotSupported (-30580)	Handler doesn't support text search.
//	kNoClientTableErr (-9097)		List is not a table.
//	handlerNotFoundErr (-1856)		No handler for table column.
//	inputOutOfBounds (-190)			Column does not exist.
//	nilHandleErr (-109)				Bad list.
//	paramErr (-50)					Nil data or 0 length data.
//	noErr (0)						Search data was matched exactly.
//	blSearchResultNextCell (1000)	Next cell returned in inexact match.
//	blSearchResultPrevCell (1001)	Previous cell returned in inexact match.
BL_PROC OSErr BLSearch( UInt16 inColumn, void* inData, UInt32 inDataSize, BLCell* outCell, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateColumn( inColumn, inList );
		if (!err)
		{
			if (!inData)
			{
				_AssertStr(false,"Error: nil data pointer." );
				err = paramErr;
			}
			else
			{
				if (!inDataSize)
				{
					_AssertStr(false,"Error: zero-length search data." );
					err = paramErr;
				}
				else
				{
					if (!bl_Table(inList))
					{
						_AssertStr(false,"Error: connot search a spreadsheet (yet)." );
						err = kNoClientTableErr;
					}
					else
					{
						BLContentType	content;
						BLCell			cell;
						
						cell.row = 1;
						cell.col = inColumn;
						content = _BLGetCellContentType( cell, inList );
						if (content)
						{
							UInt32	features;
							
							// Make sure the content handler supports text sort/search.
							err = _BLGetHandlerFeatures( content, &features );
							if (!err)
							{
								if (!bl_Test(blWantsSort,features))
								{
									_AssertStr(false,"Error: content handler doesn't support text search." );
									err = errMessageNotSupported;
								}
								else err = _BLSearch( inColumn, inData, inDataSize, outCell, inList );
							}
						}
					}
				}
			}
		}
	}
	return err;
}

//===============================
// Only call this routine with a table (BLKey checks this).
// outCell is unchanged if search fails.
OSErr _BLSearch( UInt16 inColumn, void* inData, UInt32 inDataSize, BLCell* outCell, BlugsRef inList )
{
	UInt16			nDisclosedRows;
	Handle			sortArrayH = nil;
	OSErr			err;
	
	sortArrayH = _BLGetDisclosedRows( &nDisclosedRows, inList );
	if (sortArrayH)
	{
		BLSecondarySortColumnProcPtr	saveSecondaryProc;
		BLStringSearchParamBlock		pb;
		UInt16							*sortArrayP;
		UInt32							next, nthItem;
		
		HLock( sortArrayH );
		// I don't think we want to use secondary sorting when we do this.
		// So we save and restore the secondaryProc; but temporarily set it to nil.
		// This should speed things up a little, too.
		saveSecondaryProc = (*inList)->callbacks.secondaryProc;
		(*inList)->callbacks.secondaryProc = nil;
		sortArrayP = (UInt16*)*sortArrayH;
		local_BLSortRange( sortArrayP, nDisclosedRows, inColumn, true, inList );
		pb.list = inList;
		pb.sortCol = inColumn;
		pb.dataSize = inDataSize;
		nthItem = _VASearch( nil, (Ptr)sortArrayP, nDisclosedRows, sizeof(UInt16),
							&local_BLStringSearchProc, (UInt32)&pb, (UInt32)inData, &next );
		if (nthItem)
		{
			outCell->row = sortArrayP[nthItem-1];
			err = blSearchResultExactMatch;
		}
		else
		{
			if (next)
			{
				outCell->row = sortArrayP[next-1];
				err = blSearchResultNextCell;
			}
			else
			{
				outCell->row = sortArrayP[nDisclosedRows-1];
				err = blSearchResultPrevCell;
			}
		}
		// Change the return cell only if search was in some way successful.
		outCell->col = inColumn;
		(*inList)->callbacks.secondaryProc = saveSecondaryProc;
		DisposeHandle( sortArrayH );
	}
	else err = memFullErr;
	return err;
}


//===============================
// data points to a row number, search data is the string ptr, refCon is our parameter pointer
static SInt32 local_BLStringSearchProc( const Ptr inData, UInt32 inSearchData, UInt32 inRefCon )
{
	BLHandlerParamBlock		pb;
	BLStringSearchParamPtr	pp;
	BLCell					cell;
	BlugsRef						list;
	
	pp = (BLStringSearchParamPtr)inRefCon;
	list = pp->list;
	pb.ioDataBuffer = (void*)inSearchData;
	pb.ioDataSize = pp->dataSize;
	cell.col = pp->sortCol;
	cell.row = *(UInt16*)inData;
	// If this is a title row and we are searching col > 1, simply return blSearchDataIsLess.
	if (cell.col > 1) if (bl_Test(_BLGetRowFlags( cell.row, list ),blRowIsTitleRow)) return blSearchDataIsLess;
	return _BLCallHandlerForCell( blCellSearchMsg, cell, &pb, list );
}
