/*===============================
|	DateTimeHandler.c
|
|	Blugs 1.1 handler for date/time text display
|	www.blugs.com
|
|	Copyright © 1998-2001 Brian Hall
|	You may use these routines in your software without restriction.
|
|===============================*/
#include "DateTimeHandler.h"
#include "BlugsAppearanceExtras.h"

#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#elif TARGET_RT_MAC_MACHO
#include <Carbon/Carbon.h>
#else
#include <TextUtils.h>
#include <ToolUtils.h>
#include <FixMath.h>
#include <Fonts.h>
#include <Math64.h>
#endif

#define _Test(field,features)	(((field) & (features)) == (features))
#define _Set(field,feature)		((field) |= (feature))


static void			local_DTGetRegion( Rect cellRect, UInt16 indent, RgnHandle rgn,
										DateTimeHdl dateTime );
static void			local_DTTextRect( Rect inCellRect, UInt16 inIndent, Rect* outTextRect, Str255 inString );
static void			local_DTDraw( Rect r, UInt16 indent, DateTimeHdl dateTime );
//static Rect			local_DTStringAndRect( Rect cellRect, UInt16 indent, Str255 string,
//										DateTimeHdl dateTime );
static DateTimeHdl	local_DTAllocateStorage( void );
static void			local_DTSet( OSType flavor, Ptr data, DateTimeHdl dateTime );
static void			local_DTGet( BLHandlerParamPtr param, DateTimeHdl dateTime );
static SInt32		local_DTSort( BLHandlerParamPtr pp, DateTimeHdl dtH1 );
static SInt32		local_DTSearch( BLHandlerParamPtr pp, DateTimeHdl dateTime );
static void			local_DTString( DateTimeHdl time, Str255 string );
static void			local_DTSafeAppendString( const Str255 source, Str255 dest, UInt8 maxBytes );
static void			local_DTSafeAppendChar( UInt8 theChar, Str255 dest, UInt8 maxBytes );



pascal UInt32 DateTimeHandler(		BLMessage			message,
									BLHandlerParamPtr	param		)
{
	UInt32			answer = 0;
	DateTimeHdl		dataH;
	
	if (param) dataH = (DateTimeHdl)param->ioStorage;
	switch (message)
	{
		case blHandlerInitMsg:
		answer = blWantsSort | blWantsRegion;
		break;
		
		case blCellDrawMsg:
		local_DTDraw( param->inRect, param->inIndent, dataH );
		break;
		
		case blCellInitMsg:
		param->ioStorage = (Handle)local_DTAllocateStorage();
		break;
		
		case blCellSetDataMsg:
		local_DTSet( param->ioDataFlavor, param->ioDataBuffer, dataH );
		break;
		
		case blCellGetDataMsg:
		local_DTGet( param, dataH );
		break;
		
		case blCellDeinitMsg:
		if (dataH) DisposeHandle( (Handle)dataH );
		break;
		
		case blCellRegionMsg:
		local_DTGetRegion( param->inRect, param->inIndent, param->inRegion, dataH );
		break;
		
		case blCellSortMsg:
		answer = local_DTSort( param, dataH );
		break;
		
		case blCellSearchMsg:
		answer = local_DTSearch( param, dataH );
		break;
		
		default:
		break;
	}
	return answer;
}

//===============================
static void local_DTGetRegion( Rect cellRect, UInt16 indent, RgnHandle rgn, DateTimeHdl dateTime )
{
	Rect		textRect;
	Str255		string = "\p";
	
	local_DTString( dateTime, string );
	local_DTTextRect( cellRect, indent, &textRect, string );
	RectRgn( rgn, &textRect );
}

//===============================
static void local_DTTextRect( Rect inCellRect, UInt16 inIndent, Rect* outTextRect, Str255 ioString )
{
	OSErr		err;
	Boolean		truncated;
	Rect		textRect;
	Point		bounds;
	SInt16		baseline;
	
	inCellRect.left += inIndent;
	textRect = inCellRect;
	err = BLTruncateString( ioString, kThemeCurrentPortFont, inCellRect.right - inCellRect.left,
							((BLEnvironment() & blHasAqua) == blHasAqua) ? truncMiddle : truncEnd,
							&truncated );
	err = BLGetTextDimensions( &ioString[1], ioString[0], kThemeCurrentPortFont,
								kThemeStateActive, false, &bounds, &baseline );
	textRect.bottom = textRect.top + bounds.v;
	textRect.right = textRect.left + bounds.h;
	BLAlignRectInRect( blAlignVCenter, false, &textRect, &inCellRect );
	*outTextRect = textRect;
}

//===============================
static void local_DTDraw( Rect cellRect, UInt16 indent, DateTimeHdl dateTime )
{
	Str255		string = "\p";
	
	local_DTString( dateTime, string );
	BLDrawStringVerticallyCentered( string, &cellRect, indent );
}

//===============================
// Pass in cell rect, it becomes text rect
/*static Rect local_DTStringAndRect( Rect cellRect, UInt16 indent, Str255 string,
									DateTimeHdl dateTime )
{
	FontInfo	fontInfo;
	Rect		r;
	
	local_DTString( dateTime, string );
	r.left = cellRect.left + indent;
	r.right = cellRect.right;
	TruncString( r.right - r.left, string, truncEnd );
	r.right = r.left + StringWidth( string );
	if (r.right > cellRect.right) r.right = cellRect.right;
	GetFontInfo( &fontInfo );
	r.bottom = cellRect.bottom - ((cellRect.bottom - cellRect.top - fontInfo.ascent) >> 1);
	r.top = r.bottom - fontInfo.ascent;
	if (r.bottom > cellRect.bottom) r.bottom = cellRect.bottom;
	// Make sure the rectangle has some width, so we can hit-test with a zero-length string
	if (r.right <= r.left + 4) r.right = r.left + 4;
	return r;
}*/


//===============================
static DateTimeHdl local_DTAllocateStorage( void )
{
	DateTimeHdl		dateTime;
	
	dateTime = (DateTimeHdl)NewHandleClear( sizeof(DateTimeHandlerRec) );
	return dateTime;
}

//===============================
static void local_DTSet( OSType flavor, Ptr data, DateTimeHdl dateTime )
{
	if (dateTime)
	{
		switch (flavor)
		{
			case dtSecondsFlavor:
			(*dateTime)->dateTimeWord = *(UInt32*)data;
			break;
			
			case dtFlagsFlavor:
			(*dateTime)->flags = *(UInt32*)data;
			break;
			
			case dtLongDateTimeFlavor:
			(*dateTime)->longDateTimeWord = ((LongDateCvt*)data)->hl.lHigh;
			(*dateTime)->dateTimeWord = ((LongDateCvt*)data)->hl.lLow;
			_Set((*dateTime)->flags,dt64BitRecord);
			break;
			
			case dtIntlHandleFlavor:
			(*dateTime)->intlHandle = *(Handle*)data;
			_Set((*dateTime)->flags,dtUseIntlHandle);
			break;
		}
	}
}

//===============================
static void local_DTGet( BLHandlerParamPtr param, DateTimeHdl dateTime )
{
	UInt32	bytes = 0;
	void*	src;
	
	if (dateTime)
	{
		switch (param->ioDataFlavor)
		{
			case dtSecondsFlavor:
			{
				src = &(*dateTime)->dateTimeWord;
				bytes = 4;
			}
			break;
			
			case dtLongDateTimeFlavor:
			{
				LongDateCvt	cvt;
				
				cvt.hl.lHigh = (_Test((*dateTime)->flags,dt64BitRecord)) ?
								(*dateTime)->longDateTimeWord : 0;
				cvt.hl.lLow = (*dateTime)->dateTimeWord;
				src = &cvt;
				bytes = sizeof(LongDateCvt);
			}
			break;
			
			case dtTextFlavor:
			{
				Str255	string;
				
				local_DTString( dateTime, string );
				bytes = (param->ioDataSize < string[0]) ? param->ioDataSize : string[0];
				src = &string[1];
			}
			break;
		}
	}
	if (bytes && param->ioDataBuffer) BlockMoveData( src, param->ioDataBuffer, bytes );
	param->ioDataSize = bytes;
}

//===============================
static SInt32 local_DTSort( BLHandlerParamPtr pp, DateTimeHdl dtH1 )
{
	SInt16		result;
	SInt32		longResult = 0;
	DateTimeHdl	dtH2;
	
	dtH2 = (DateTimeHdl)(pp->inStorage2);
	if (dtH1 && dtH2)
	{
		UInt32	secs1, secs2;
		
		secs1 = (*dtH1)->dateTimeWord;
		secs2 = (*dtH2)->dateTimeWord;
		if (secs1 > secs2) result = blCellSortFirstIsGreater;
		else
		{
			if (secs1 < secs2) result = blCellSortFirstIsLess;
			else result = blCellSortEqual;
		}
		longResult = result;
	}
	return longResult;
}

//===============================
static SInt32 local_DTSearch( BLHandlerParamPtr pp, DateTimeHdl dateTime )
{
	SInt32		longResult = 0;
	
	if (dateTime && pp->ioDataBuffer)
	{
		Str255		dtString;
		SInt16		result;
		
		local_DTString( dateTime, dtString );
		result = CompareText( &dtString[1], pp->ioDataBuffer, dtString[0], pp->ioDataSize, nil );
		longResult = result;
	}
	return longResult;
}

//===============================
static void local_DTString( DateTimeHdl time, Str255 string )
{
	UInt32				time32, flags;
	SInt64				time64;
	Boolean				do64, longFlag = shortDate;
	Str255				dateString = "\p", timeString = "\p";
	Handle				intl;
	
	flags = (*time)->flags;
	do64 = _Test((*time)->flags,dt64BitRecord);
	intl = (_Test(flags,dtUseIntlHandle)) ? (*time)->intlHandle : nil;
	if (do64) time64 = WideToSInt64( (*time)->longDateTimeWord );
	else time32 = (*time)->dateTimeWord;
	if (_Test(flags,dtShowDate))
	{
		if (_Test(flags,dtAbbrevDate)) longFlag = abbrevDate;
		else longFlag = longDate;
		if (!do64) DateString( time32, longFlag, dateString, intl );
		else LongDateString( &time64, longFlag, dateString, intl );
	}
	if (_Test(flags,dtShowTime))
	{
		Boolean	wantSeconds = _Test(flags,dtWantSeconds);
		
		if (!do64) TimeString( time32, wantSeconds, timeString, intl );
		else LongTimeString( &time64, wantSeconds, timeString, intl );
	}
	
	if (_Test(flags,dtDateFirst))
	{
		local_DTSafeAppendString( dateString, string, 255 );
		if (timeString[0])
		{
			local_DTSafeAppendChar( 0x20, string, 255 );
			local_DTSafeAppendString( timeString, string, 255 );
		}
	}
	else
	{
		local_DTSafeAppendString( timeString, string, 255 );
		if (dateString[0])
		{
			local_DTSafeAppendChar( 0x20, string, 255 );
			local_DTSafeAppendString( dateString, string, 255 );
		}
	}
}

//===================================
static void local_DTSafeAppendString( const Str255 source, Str255 dest, UInt8 maxBytes )
{
	UInt8		bytesToCopy = source[0];
	
	if ((source[0] + dest[0]) > maxBytes) bytesToCopy = maxBytes - dest[0];
	BlockMove( &(source[1]), &(dest[dest[0]+1]), bytesToCopy );
	dest[0] += bytesToCopy;
}

//===================================
static void local_DTSafeAppendChar( UInt8 theChar, Str255 dest, UInt8 maxBytes )
{
	if ( dest[0] >= maxBytes ) return;
	dest[ dest[0] + 1] = theChar;
	dest[0]++;
}