/*===============================
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"


SInt32		local_BLUserDataSearchProc( const Ptr inArrayDataP, UInt32 inSearchData, UInt32 inRefCon );
SInt32		local_BLUserDataSortProc( const Ptr inData1P, const Ptr inData2P, UInt32 inRefCon );


typedef struct
{
	SInt32	key;
	SInt32	data;
} BLUserDataRec, *BLUserDataPtr, **BLUserDataHdl;



//===============================
// The user data array handle is not allocated until a datum needs to be set via this routine.
BL_PROC OSErr BLSetUserData( SInt32 inKey, SInt32 inData, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		Handle			dataH;
		BLUserDataPtr	dataP;
		UInt16			nEntries;
		Boolean			needAdd = false;
		
		dataH = (*inList)->userData;
		nEntries = (*inList)->nUserEntries;
		if (!nEntries || !dataH)
		{
			// If there's a nil handle, make sure nEntries is 0.
			nEntries = 0;
			dataH = _VANew( 0, sizeof(BLUserDataRec), false );
			if (!dataH) err = memFullErr;
			else
			{
				(*inList)->userData = dataH;
				needAdd = true;
			}
		}
		else
		{
			UInt32	nthItem;
			
			nthItem = _VASearch( dataH, nil, nEntries, sizeof(BLUserDataRec),
								&local_BLUserDataSearchProc, 0L, (UInt32)inKey, nil );
			if (nthItem)
			{
				dataP = *(BLUserDataPtr*)dataH;
				dataP += nthItem - 1;
				dataP->data = inData;
			}
			else needAdd = true;
		}
		// If the key was not there or if we had to allocate the data array.
		if (needAdd)
		{
			err = _VAAdd( 1, nEntries + 1, nEntries, sizeof(BLUserDataRec), dataH, false );
			if (!err)
			{
				dataP = *(BLUserDataPtr*)dataH;
				dataP += nEntries;
				dataP->key = inKey;
				dataP->data = inData;
				_VASort( dataH, nil, nEntries + 1, sizeof(BLUserDataRec),
						&local_BLUserDataSortProc, 0 );
				(*inList)->nUserEntries = nEntries + 1;
			}
		}
	} // if valid list
	return err;
}


//===============================
BL_PROC OSErr BLGetUserData( SInt32 inKey, SInt32* outData, BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		UInt16			nEntries;
		UInt32			nthItem = 0;
		
		nEntries = (*inList)->nUserEntries;
		// There may not be any entries at all.
		if (nEntries)
		{
			BLUserDataPtr	dataP;
			
			dataP = *(BLUserDataHdl)(*inList)->userData;
			nthItem = _VASearch( nil, (Ptr)dataP, nEntries, sizeof(BLUserDataRec),
								&local_BLUserDataSearchProc, 0, (UInt32)inKey, nil );
			if (nthItem)
			{
				dataP += nthItem - 1;
				*outData = dataP->data;
			}
		}
		if (!nthItem) err = userDataItemNotFound;
	}
	if (err) *outData = 0L;
	return err;
}


//===============================
// Remove a key-value pair from the user data array.
// If it is the only item to be removed, dump the entire array to save memory.
BL_PROC OSErr BLRemoveUserData( SInt32 inKey, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		Handle		dataH;
		UInt16		nEntries;
		
		dataH = (*inList)->userData;
		nEntries = (*inList)->nUserEntries;
		if (dataH && nEntries)
		{
			UInt32	nthItem;
			
			nthItem = _VASearch( dataH, nil, nEntries, sizeof(BLUserDataRec),
								&local_BLUserDataSearchProc, 0L, (UInt32)inKey, nil );
			if (nthItem)
			{
				// If there is only one item in the array, dump the array.
				if (nEntries == 1)
				{
					// It bloody well better item #1.
					_AssertStr( nthItem == 1, "Internal error: out-of-bounds search result." );
					DisposeHandle( dataH );
					(*inList)->userData = nil;
					(*inList)->nUserEntries = 0;
				}
				// Otherwise shorten the array by removing the item.
				else _VARemove( 1, nthItem, nEntries, sizeof(BLUserDataRec), dataH );
			}
			else err = userDataItemNotFound;
		}
	} // if valid list
	return err;
}


//===============================
SInt32 local_BLUserDataSearchProc( const Ptr inArrayDataP, UInt32 inSearchData, UInt32 inRefCon )
{
	#pragma unused (inRefCon)
	SInt32	key;
	
	key = ((BLUserDataPtr)inArrayDataP)->key;
	if (inSearchData == key) return blEqual;
	if (inSearchData < key) return blSearchDataIsLess;
	return blSearchDataIsGreater;
}

//===============================
SInt32 local_BLUserDataSortProc( const Ptr inData1P, const Ptr inData2P, UInt32 inRefCon )
{
	#pragma unused (inRefCon)
	SInt32	key1, key2;
	
	key1 = ((BLUserDataPtr)inData1P)->key;
	key2 = ((BLUserDataPtr)inData2P)->key;
	if (key1 == key2) return blEqual;
	else if (key1 > key2) return blFirstIsGreater;
	return blFirstIsLess;
}


#pragma mark -
//===============================
// Returns zero if the content type was unavailable.
// Moses 3/30/2001 removed check for call from handler.
// Only validate cell or title if not 'table' type.
BL_PROC BLContentType BLGetCellContentType( BLCell inCell, BlugsRef inList )
{
	OSErr			err;
	BLContentType	content = 0;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		BLTitleBarHdl	theBar;
		UInt16			nthTitle;
		
		if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList))
		{
			// Validate title if not a table-type title bar, or the item is top left.
			if (nthTitle) err = _BLValidateTitle( true, nthTitle, theBar, inList );
			if (!err) content = _BLGetTitleContentType( nthTitle, theBar );
		}
		else
		{
			if (!bl_Table(inList)) err = _BLValidateCell( inCell, inList );
			else err = _BLValidateColumn( inCell.col, inList );
			if (!err) content = _BLGetCellContentType( inCell, inList );
		}
	}
	return content;
}

//===============================
// Deinitialize by setting content type to zero.
BL_PROC OSErr BLSetCellContentType( BLCell inCell, BLContentType inContent, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList);
	err = _BLValidateList( inList );
	if (!err)
	{
		BLTitleBarHdl	theBar;
		UInt16			nthTitle;
		
		if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList ))
		{
			// Validate title if not a table-type title bar, or the item is top left.
			if (nthTitle) err = _BLValidateTitle( true, nthTitle, theBar, inList );
			if (!err) _BLSetTitleContentType( true, nthTitle, inContent, theBar, inList );
		}
		else
		{
			// Make sure entire cell is valid for spreadsheets
			if (!bl_Table(inList)) err = _BLValidateCell( inCell, inList );
			// For tables, make sure the column is valid. Ignore row.
			else err = _BLValidateColumn( inCell.col, inList );
			if (!err) _BLSetCellContentType( inCell, inContent, inList );
			_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
		}
	}
	return err;
}

//===============================
// 12/15/2000 Moses
//	outData can be nil if the app just wants the data size.
//		This is consistent with the whole Mac OS, so it's not a horrid change. But it will
//		trip somebody up, I'm sure. Have to put warnings in big bold letters in the docs
//		and 1.0b3 readme files.
BL_PROC OSErr BLGetCellData( BLCell inCell, OSType inDataFlavor, UInt32* ioDataSize,
							void* outData, BlugsRef inList )
{
	OSErr	err;
	
	//_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCellOrTitle( inCell, inList );
	if (!err)
	{
		BLHandlerParamBlock	pb;
		BLTitleBarHdl		theBar;
		UInt16				nthTitle;
		
		pb.ioDataBuffer = outData;
		pb.ioDataSize = *ioDataSize;
		pb.ioDataFlavor = inDataFlavor;
		if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList ))
			_BLCallHandlerForTitle( blCellGetDataMsg, nthTitle, &pb, theBar, inList );
		else _BLCallHandlerForCell( blCellGetDataMsg, inCell, &pb, inList );
		*ioDataSize = pb.ioDataSize;
	}
	if (err) *ioDataSize = 0; // Do we want to do this?
	return err;
}

//===============================
BL_PROC OSErr BLSetCellData( BLCell inCell, OSType inDataFlavor, UInt32 inDataSize, void* inData,
							BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCellOrTitle( inCell, inList );
	if (!err)
	{
		if (inData)
		{
			BLHandlerParamBlock	pb;
			BLTitleBarHdl		theBar;
			UInt16				nthTitle;
			Boolean				isTitle;
			
			pb.ioDataBuffer = inData;
			pb.ioDataSize = inDataSize;
			pb.ioDataFlavor = inDataFlavor;
			pb.inIndent = _BLIndent( inCell, inList );
			isTitle = _BLCellToTitle( inCell, &nthTitle, &theBar, inList );
			if (isTitle)
			{
				(void)_BLTitleRect( nthTitle, theBar, &pb.inRect, inList );
				_BLCallHandlerForTitle( blCellSetDataMsg, nthTitle, &pb, theBar, inList );
			}
			else
			{
				(void)_BLCellRect( inCell, &pb.inRect, inList );
				_BLCallHandlerForCell( blCellSetDataMsg, inCell, &pb, inList );
			}
			if (isTitle) _BLUpdateTitle( nthTitle, theBar, inList );
			else _BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
		}
		else
		{
			err = paramErr;
			_AssertStr(nil,"Error: nil data pointer.");
		}
	}
	return err;
}

//===============================
BL_PROC void BLClearCell( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateCellOrTitle( inCell, inList );
		if (!err)
		{
			BLHandlerParamBlock	pb;
			BLTitleBarHdl		theBar;
			UInt16				nthTitle;
			
			if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList ))
			{
				_BLCallHandlerForTitle( blCellClearDataMsg, nthTitle, &pb, theBar, inList );
				_BLUpdateTitle( nthTitle, theBar, inList );
			}
			else
			{
				_BLCallHandlerForCell( blCellClearDataMsg, inCell, &pb, inList );
				_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
			}
		}
	}
}

/*===============================
BL_PROC UInt32 BLCountCellFlavors( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	UInt32	flavorCount = 0;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCellOrTitle( inCell, inList );
	if (!err) flavorCount = _BLCountCellFlavors( inCell, inList );
	return flavorCount;
}

//===============================
BL_PROC OSErr BLGetIndFlavorInfo( BLCell inCell, UInt16 inIndex, OSType* outDataFlavor,
									UInt32* outSize, BlugsRef inList )
{
	OSErr		err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) err = _BLValidateCellOrTitle( inCell, inList );
	if (!err)
	{
		BLHandlerParamBlock	pb;
		BLTitleBarHdl		theBar;
		UInt16				nthTitle;
		
		pb.inIndex = inIndex;
		if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList ))
			_BLCallHandlerForTitle( blGetNthExportFlavorInfoMsg, nthTitle, &pb, theBar, inList );
		else _BLCallHandlerForCell( blGetNthExportFlavorInfoMsg, inCell, &pb, inList );
		*outSize = pb.ioDataSize;
		*outDataFlavor = pb.ioDataFlavor;
	}
	return err;
}

//===============================
UInt32 _BLCountCellFlavors( BLCell inCell, BlugsRef inList )
{
	BLHandlerParamBlock	pb;
	BLTitleBarHdl		theBar;
	UInt16				nthTitle;
	UInt32				flavorCount;
	
	if (_BLCellToTitle( inCell, &nthTitle, &theBar, inList ))
		flavorCount = _BLCallHandlerForTitle( blCountExportFlavorsMsg, nthTitle, &pb, theBar, inList );
	else flavorCount = _BLCallHandlerForCell( blCountExportFlavorsMsg, inCell, &pb, inList );
	return flavorCount;
}*/

//===============================
BLContentType _BLGetCellContentType( BLCell inCell, BlugsRef inList )
{
	BLContentType	content;
	
	if (bl_Table(inList)) content = _BLGetColumnContentType( inCell.col, inList );
	else content = ((BLIndexPtr)(bl_CellIndexAddr(inCell,inList)))->content;
	return content;
}

//===============================
void _BLSetCellContentType( BLCell inCell, BLContentType inContent, BlugsRef inList )
{
	BLHandlerParamBlock	pb;
	BLContentType		oldContent;
	
	if (bl_Table(inList))
	{
		oldContent = _BLGetColumnContentType( inCell.col, inList );
		if (oldContent != inContent)
		{
			UInt16	nRows;
		
			nRows = bl_NRows(inList);
			// Deinitialize cell in each row
			if (oldContent)
			{
				for (inCell.row = 1; inCell.row <= nRows; inCell.row++)
				{
					_BLCallHandlerForCell( blCellDeinitMsg, inCell, &pb, inList );
				}
			}
			_BLSetColumnContentType( inCell.col, inContent, inList );
			if (inContent)
			{
				// Initialize cell in each row
				for (inCell.row = 1; inCell.row <= nRows; inCell.row++)
				{
					_BLCallHandlerForCell( blCellInitMsg, inCell, &pb, inList );
					_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
				}
			}
		}
	}
	else
	{
		BLIndexPtr	indexP;
		
		indexP = bl_CellIndexAddr(inCell,inList);
		oldContent = indexP->content;
		if (oldContent != inContent)
		{
			_BLCallHandlerForCell( blCellDeinitMsg, inCell, &pb, inList );
			indexP->content = inContent;
			if (inContent) _BLCallHandlerForCell( blCellInitMsg, inCell, &pb, inList );
			_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
		}
	}
}

//===============================
void _BLSetTitleContentType( Boolean inUpdate, UInt16 inTitle, BLContentType inContent,
							BLTitleBarHdl inBar, BlugsRef inList )
{
	BLHandlerParamBlock	pb;
	SInt8				state;
	BLContentType		oldContent;
	Boolean				needUpdate = true;
	
	if (!inTitle)
	{
		oldContent = (*inBar)->topLeftContent;
		if (oldContent == inContent) needUpdate = false;
		else
		{
			if (oldContent) _BLCallHandlerForTitle( blCellDeinitMsg, inTitle, &pb, inBar, inList );
			(*inBar)->topLeftContent = inContent;
			_BLCallHandlerForTitle( blCellInitMsg, inTitle, &pb, inBar, inList );
		}
	}
	else
	{
		BLTitleArrayHdl		titles;
		BLTitlePtr			title;
		
		titles = (*inBar)->titles;
		title = &((**titles)[inTitle-1]);
		oldContent = title->contentType;
		if (oldContent == inContent) needUpdate = false;
		else
		{
			state = HGetState( (Handle)titles );
			HLock( (Handle)titles );
			if (oldContent) _BLCallHandlerForTitle( blCellDeinitMsg, inTitle, &pb, inBar, inList );
			title->contentType = inContent;
			_BLCallHandlerForTitle( blCellInitMsg, inTitle, &pb, inBar, inList );
			HSetState( (Handle)titles, state );
		}
	}
	if (inUpdate && needUpdate) _BLUpdateTitle( inTitle, inBar, inList );
}