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

#pragma mark ¥¥¥Module Data¥¥¥

#define blHashTableSize		101	// A big ole prime number. Too big, probably.


typedef struct BLContentHandlerRec		BLContentHandlerRec;
typedef BLContentHandlerRec*			BLContentHandlerPtr;
typedef BLContentHandlerRec**			BLContentHandlerHdl;


struct BLContentHandlerRec
{
	BLContentHandlerHdl			next;	// Other handlers whose content hashes to same location
	BLContentHandlerProcPtr		proc;
	UInt32						features;
	BLContentType				content;
	UInt16						variationCode;
};

typedef BLContentHandlerRec				BLContentHandlerArray[1];
typedef BLContentHandlerArray**			BLContentHandlerArrayHdl;



static BLContentHandlerPtr
				local_BLGetHandler( BLContentType inContent );
static void		local_BLSetFontStyle( UInt16 inColumn, ControlFontStyleRec* outSaveFontStyle,
										BlugsRef inList );
static void		local_BLRestoreFontStyle( ControlFontStyleRec* inSaveFontStyle );



#pragma mark -
//===============================
// Handler numbers over 101 are extremely unlikely.
// Therefore we give those lower privileged status; the entries in the array itself
//	are reserved for entries content types under 102, for fastest access.
// Anything higher than 101 is automatically chained, even if there is no array entry.
BL_PROC OSErr BLRegisterContentHandler( BLContentType inContent, UInt16 inVariationCode,
										BLContentHandlerProcPtr inProc )
{
	OSErr	err = noErr;
	
	_AssertStr( inProc, "Error: nil handler proc." );
	_AssertStr( inContent, "Error: content type 0 is not valid." );
	if (!inContent || !inProc) err = paramErr;
	else
	{
		err = BLEnter( 0 );
		if (!err)
		{
			BLContentHandlerArrayHdl	array;
			BLContentHandlerPtr			handler;
			
			array = (BLContentHandlerArrayHdl)_blGlobals.handlerTable;
			// Fail if registering a type that is already registered.
			handler = local_BLGetHandler( inContent );
			if (handler && handler->proc) err = paramErr;
			else
			{
				UInt8						tableIndex;
				BLContentHandlerHdl			newHandler = nil;
				BLHandlerParamBlock			pb;
				
				HLock( (Handle)array );
				if (inContent < blHashTableSize) tableIndex = inContent;
				else tableIndex = inContent % blHashTableSize;
				// The base handler record.
				handler = &((**array)[tableIndex]);
				if (inContent >= blHashTableSize)
				{
					// Allocate a handle to one handler record, so we can chain handler records with
					//	content types higher than blHashTableSize to the hash table.
					newHandler = (BLContentHandlerHdl)NewHandle( sizeof(BLContentHandlerRec) );
					if (!newHandler) err = memFullErr;
					else
					{
						// Go until the 'next' element is unallocated.
						while (handler->next) handler = *(handler->next);
						handler->next = newHandler;
						handler = *newHandler;
						HLock( (Handle)newHandler );
					}
				}
				if (!err)
				{
					// handler pointer is either in the array or the new handler (handle)
					handler->next = nil;
					handler->proc = inProc;
					handler->content = inContent;
					handler->variationCode = inVariationCode;
					// Initialize handler and get its features
					pb.ioStorage = nil;
					pb.inVariationCode = inVariationCode;
					handler->features = (*inProc)( blHandlerInitMsg, &pb );
					if (newHandler) HUnlock( (Handle)newHandler );
				}
				HUnlock( (Handle)array );
			} // inContent is not registered yet.
		} // Valid handler array.
	} // Content type and proc are valid.
	return err;
}


#pragma mark -
//===============================
// Allocates a hash table for holding handler records.
Handle _BLAllocateHandlerTable( void )
{
	Handle	table;
	
	// Under Carbon, use NewHandleClear which will (most likely) use BlockZero,
	//	which is faster than _BLZeroMemory.
#if __BL_USE_CARBON_API__
	table = NewHandleClear( sizeof(BLContentHandlerRec) * blHashTableSize );
#else
	table = NewHandle( sizeof(BLContentHandlerRec) * blHashTableSize );
	if (table) _BLZeroMemory( *table, sizeof(BLContentHandlerRec) * blHashTableSize );
#endif

	return table;
}


//===============================
OSErr _BLGetHandlerFeatures( BLContentType inContent, UInt32* outFeatures )
{
	BLContentHandlerPtr		handler;
	OSErr					err;
	
	handler = local_BLGetHandler( inContent );
	if (!handler) err = handlerNotFoundErr; // Drag Mgr error code.
	else
	{
		*outFeatures = handler->features;
		err = noErr;
	}
	return err;
}

//===============================
OSErr _BLGetCellHandlerFeatures( BLCell inCell, UInt32* outFeatures, BlugsRef inList )
{
	BLContentType	content;
	OSErr			err = noErr;
	
	content = _BLGetCellContentType( inCell, inList );
	if (content)
	{
		BLContentHandlerPtr		handler;
		
		handler = local_BLGetHandler( content );
		if (handler) *outFeatures = handler->features;
		else err = handlerNotFoundErr;
	}
	else err = paramErr;
	return err;
}

//===============================
OSErr _BLCheckValidContentType( BLContentType inContent )
{
	OSErr		err = noErr;
	
	if (!inContent) err = handlerNotFoundErr;
	else
	{
		BLContentHandlerPtr		handler;
		
		handler = local_BLGetHandler( inContent );
		if (!handler) err = handlerNotFoundErr;
		else if (!handler->proc) err = handlerNotFoundErr;
	}
	return err;
}

//===============================
UInt32 _BLCallHandlerForCell( BLMessage inMessage, BLCell inCell, BLHandlerParamPtr ioParam,
								BlugsRef inList )
{
	UInt32				answer = 0;
	BLContentType		content;
	
	content = _BLGetCellContentType( inCell, inList );
	if (content)
	{
		BLContentHandlerPtr		handler;
		
		handler = local_BLGetHandler( content );
		if (handler)
		{
			BLContentHandlerProcPtr	proc;
			
			proc = handler->proc;
			_AssertStr( proc != nil, "Internal error: nil handler proc." );
			if (proc)
			{
				BLIndexPtr			indexP;
				Handle				indexH, storage;
				ControlFontStyleRec	saveFontStyle;
				
				// Check if the message is one that makes saving and setting up the
				//	font style necessary, or at least advisable.
				if (inMessage >= blCellInitMsg)
					local_BLSetFontStyle( inCell.col, &saveFontStyle, inList );
				indexH = bl_Index(inList);
				indexP = bl_CellIndexAddrFromIH(inCell,inList,indexH);
				storage = indexP->data;
				ioParam->inPart = blCellRegionPart;
				ioParam->ioStorage = storage;
				ioParam->inCell = inCell;
				ioParam->inVariationCode = handler->variationCode;
				ioParam->inList = inList;
				ioParam->ioCommand = 0;
				//bl_Set((*inList)->privateFlags,blSuppressIdle);
				_SetHandlerFlag(inList);
				answer = (*proc)( inMessage, ioParam );
				//bl_Clear((*inList)->privateFlags,blSuppressIdle);
				_ClearHandlerFlag(inList);
				indexP = bl_CellIndexAddrFromIH(inCell,inList,indexH);
				if (inMessage == blCellDeinitMsg) indexP->data = nil;
				else
				{
					// If handler changed storage, put it in index.
					if (ioParam->ioStorage != storage) indexP->data = ioParam->ioStorage;
				}
				if (inMessage >= blCellInitMsg) local_BLRestoreFontStyle( &saveFontStyle );
			}
		}
	}
	return answer;
}

//===============================
// inTitle can be zero if it is a widget.
UInt32 _BLCallHandlerForTitle( BLMessage inMessage, UInt16 inTitle, BLHandlerParamPtr ioParam,
								BLTitleBarHdl inBar, BlugsRef inList )
{
	UInt32				answer = 0;
	BLContentType		content;
	
	content = _BLGetTitleContentType( inTitle, inBar );
	if (content)
	{
		BLContentHandlerPtr		handler;
		
		handler = local_BLGetHandler( content );
		_AssertStr(handler != nil, "Internal error: could not get handler.");
		if (handler)
		{
			BLContentHandlerProcPtr	proc;
			Handle					storage;
			
			proc = handler->proc;
			_AssertStr( proc != nil, "Internal error: nil handler proc." );
			if (proc)
			{
				BLCell				cell = {0,0};
				ControlFontStyleRec	saveFontStyle;
				
				storage = (inTitle) ? (bl_NthTitleAddr(inTitle,inBar))->data :
										(*inBar)->topLeftStorage;
				if (bl_BarVert(inBar))
				{
					cell.row = inTitle;
					ioParam->inPart = blVTitleBarTitlePart;
				}
				else
				{
					cell.col = inTitle;
					ioParam->inPart = blHTitleBarTitlePart;
				}
				if (!inTitle) ioParam->inPart = blTopLeftPart;
				if (inMessage >= blCellInitMsg)
					local_BLSetFontStyle( cell.col, &saveFontStyle, inList );
				ioParam->inList = inList;
				ioParam->inCell = cell;
				ioParam->inVariationCode = handler->variationCode;
				ioParam->ioStorage = storage;
				ioParam->ioCommand = 0;
				_SetHandlerFlag(inList);
				answer = (*proc)( inMessage, ioParam );
				_ClearHandlerFlag(inList);
				if (inTitle)
				{
					BLTitlePtr	title;
					
					title = bl_NthTitleAddr(inTitle,inBar);
					if (inMessage == blCellDeinitMsg) title->data = nil;
					else if (ioParam->ioStorage != storage) title->data = ioParam->ioStorage;
				}
				else
				{
					if (inMessage == blCellDeinitMsg) (*inBar)->topLeftStorage = nil;
					else if (ioParam->ioStorage != storage) (*inBar)->topLeftStorage = ioParam->ioStorage;
				}
				if (inMessage >= blCellInitMsg) local_BLRestoreFontStyle( &saveFontStyle );
			}
		} // end if handler
	} // end if content
	return answer;
}

//===============================
UInt32 _BLCallHandlerForWidget( BLMessage inMessage, Boolean inVertical, UInt16 inWidget,
								BLHandlerParamPtr ioParam, BlugsRef inList )
{
	UInt32				answer = 0;
	BLContentType		content;
	
	content = _BLGetWidgetContentType( inVertical, inWidget, inList );
	if (content)
	{
		BLContentHandlerPtr		handler;
		
		handler = local_BLGetHandler( content );
		_AssertStr(handler != nil, "Internal error: could not get handler.");
		if (handler)
		{
			BLContentHandlerProcPtr	proc;
			Handle					storage;
			
			proc = handler->proc;
			_AssertStr( proc != nil, "Internal error: nil handler proc." );
			if (proc)
			{
				BLCell		cell = {0,0};
				
				if (inMessage >= blCellInitMsg)
				{
					TextFont( 1 );
					TextSize( 9 );
				}
				storage = _BLGetWidgetStorage( inVertical, inWidget, inList );
				if (inVertical) cell.row = inWidget;
				else cell.col = inWidget;
				ioParam->inPart = blWidgetPart;
				ioParam->inList = inList;
				ioParam->inCell = cell;
				ioParam->inVariationCode = handler->variationCode;
				ioParam->ioStorage = storage;
				ioParam->ioCommand = 0;
				_SetHandlerFlag(inList);
				answer = (*proc)( inMessage, ioParam );
				_ClearHandlerFlag(inList);
				if (inMessage == blCellDeinitMsg) storage = nil;
				else if (ioParam->ioStorage != storage) storage = ioParam->ioStorage;
				_BLSetWidgetStorage( inVertical, inWidget, storage, inList );
			}
		} // end if handler
	} // end if content
	return answer;
}


//===============================
/*void _BLDeinitializeHandlers( void )
{
	BLContentHandlerPtr			handler;
	BLContentHandlerArrayHdl	array;
	UInt8						i;
	BLHandlerParamBlock			pb;
	
	_BLZeroMemory( (Ptr)&pb, sizeof(BLHandlerParamBlock) );
	(Handle)array = _blGlobals.handlerTable;
	HLock( (Handle)array );
	handler = &((**array)[0]);
	for (i = 0; i < blHashTableSize; i++, handler++)
	{
		BLContentHandlerProcPtr	proc;
		BLContentHandlerHdl		chain = nil;
		
		if (handler->content)
		{
			proc = handler->proc;
			pb.inVariationCode = handler->variationCode;
			// Deinitialize handler
			if (proc) (*proc)( blHandlerDeinitMsg, &pb );
		}
		// Deinitialize handler that may be chained to the array
		chain = handler->next;
		while (chain)
		{
			BLContentHandlerHdl	next;
			
			proc = (*chain)->proc;
			next = (*chain)->next;
			pb.inVariationCode = handler->variationCode;
			if (proc) (*proc)( blHandlerDeinitMsg, &pb );
			DisposeHandle( (Handle)chain );
			chain = next;
		}
	}
	DisposeHandle( (Handle)array );
}*/

#pragma mark -
//===============================
static BLContentHandlerPtr local_BLGetHandler( BLContentType inContent )
{
	BLContentHandlerPtr		handler;
	
	if (!inContent) handler = nil;
	else
	{
		BLContentHandlerArrayHdl	array;
		
		array = (BLContentHandlerArrayHdl)_blGlobals.handlerTable;
		if (inContent < blHashTableSize) handler = &((**array)[inContent]);
		else
		{
			UInt8		tableIndex;
			
			tableIndex = inContent % blHashTableSize;
			// The base handler record.
			handler = &((**array)[tableIndex]);
			// Search thru the chain in the unlikely event the content type is greater than 101
			// Go until the 'next' element is unallocated.
			// Stop when the content type is found.
			while (handler->next)
			{
				handler = *(handler->next);
				if (handler->content == inContent) return handler;
			}
			handler = nil;
		}
	}
	return handler;
}

//===============================
#if !__BL_USE_CARBON_API__
	#define GetPortTextFont(p)		(p->txFont)
	#define GetPortTextFace(p)		(p->txFace)
	#define GetPortTextMode(p)		(p->txMode)
	#define GetPortTextSize(p)		(p->txSize)
#endif
//===============================
static void local_BLSetFontStyle( UInt16 inColumn, ControlFontStyleRec* outSaveFontStyle,
									BlugsRef inList )
{
	ControlFontStyleRec	fontStyle;
	GWorldPtr			world;
	GDHandle			dev;
	
	GetGWorld( &world, &dev );
	if (inColumn) _BLGetColumnFontStyle( inColumn, &fontStyle, inList );
	else BlockMoveData( &(*(bl_VTitle(inList)))->fontStyle, &fontStyle, sizeof(ControlFontStyleRec) );
	outSaveFontStyle->flags = fontStyle.flags;
	// Font
	if (!bl_Test(fontStyle.flags,kControlUseFontMask)) fontStyle.font = 1;
	outSaveFontStyle->font = GetPortTextFont( world );
	TextFont( fontStyle.font );
	// Face
	if (!bl_Test(fontStyle.flags,kControlUseFaceMask)) fontStyle.style = 0;
	outSaveFontStyle->style = GetPortTextFace( world );
	TextFace( fontStyle.style );
	// Size
	if (!bl_Test(fontStyle.flags,kControlUseSizeMask)) fontStyle.size = 10;
	outSaveFontStyle->size = GetPortTextSize( world );
	TextSize( fontStyle.size );
	// Color; we don't use a default because that is frequently determined beforehand
	//	by drawing routines.
	if (bl_Test(fontStyle.flags,kControlUseForeColorMask))
	{
		GetForeColor( &outSaveFontStyle->foreColor );
		RGBForeColor( &fontStyle.foreColor );
	}
	// Mode. May need a default for it.
	if (bl_Test(fontStyle.flags,kControlUseModeMask))
	{
		outSaveFontStyle->mode = GetPortTextMode( world );
		TextMode( fontStyle.mode );
	}
}

//===============================
static void local_BLRestoreFontStyle( ControlFontStyleRec* inSaveFontStyle )
{
	TextFont( inSaveFontStyle->font );
	TextFace( inSaveFontStyle->style );
	TextSize( inSaveFontStyle->size );
	if (bl_Test(inSaveFontStyle->flags,kControlUseForeColorMask)) RGBForeColor( &inSaveFontStyle->foreColor );
	if (bl_Test(inSaveFontStyle->flags,kControlUseModeMask)) TextMode( inSaveFontStyle->mode );
}