/***********************************************************************
 *
 Copyright © 1995 - 1998, 3Com Corporation or its subsidiaries ("3Com").  
 All rights reserved.
   
 This software may be copied and used solely for developing products for 
 the Palm Computing platform and for archival and backup purposes.  Except 
 for the foregoing, no part of this software may be reproduced or transmitted 
 in any form or by any means or used to make any derivative work (such as 
 translation, transformation or adaptation) without express written consent 
 from 3Com.

 3Com reserves the right to revise this software and to make changes in content 
 from time to time without obligation on the part of 3Com to provide notification 
 of such revision or changes.  
 3COM MAKES NO REPRESENTATIONS OR WARRANTIES THAT THE SOFTWARE IS FREE OF ERRORS 
 OR THAT THE SOFTWARE IS SUITABLE FOR YOUR USE.  THE SOFTWARE IS PROVIDED ON AN 
 "AS IS" BASIS.  3COM MAKES NO WARRANTIES, TERMS OR CONDITIONS, EXPRESS OR IMPLIED, 
 EITHER IN FACT OR BY OPERATION OF LAW, STATUTORY OR OTHERWISE, INCLUDING WARRANTIES, 
 TERMS, OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 
 SATISFACTORY QUALITY.

 TO THE FULL EXTENT ALLOWED BY LAW, 3COM ALSO EXCLUDES FOR ITSELF AND ITS SUPPLIERS 
 ANY LIABILITY, WHETHER BASED IN CONTRACT OR TORT (INCLUDING NEGLIGENCE), FOR 
 DIRECT, INCIDENTAL, CONSEQUENTIAL, INDIRECT, SPECIAL, OR PUNITIVE DAMAGES OF 
 ANY KIND, OR FOR LOSS OF REVENUE OR PROFITS, LOSS OF BUSINESS, LOSS OF INFORMATION 
 OR DATA, OR OTHER FINANCIAL LOSS ARISING OUT OF OR IN CONNECTION WITH THIS SOFTWARE, 
 EVEN IF 3COM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

 3Com, HotSync, Palm Computing, and Graffiti are registered trademarks, and 
 Palm III and Palm OS are trademarks of 3Com Corporation or its subsidiaries.

 IF THIS SOFTWARE IS PROVIDED ON A COMPACT DISK, THE OTHER SOFTWARE AND 
 DOCUMENTATION ON THE COMPACT DISK ARE SUBJECT TO THE LICENSE AGREEMENT 
 ACCOMPANYING THE COMPACT DISK.
 
 *****************************************************************************
 *
 * PROJECT:  Touchdown
 * FILE:     ExpenseDB.c
 * AUTHOR:	 Art Lamb: January 3, 1995
 *
 * DECLARER: Expense
 *
 * DESCRIPTION:
 *	  This is the Expense application's datebase management routines.
 *
 **********************************************************************/

#include "Pilot.h"
#include "FeatureMgr.h"
#include "Expense.h"


/************************************************************
 * DB initialization date
 *************************************************************/
/*
 ExpenseAppInfoType DefaultExpenseApplicationInfo = {
 	// Renamed categories
 	0x0006,
 	
 	// Category labels
	"Unfiled",
	"New York",
	"Paris",
	"",
	"",	"",	"",	"",
	"",	"",	"",	"",
	"",	"",	"",	"",
	
	// Category Uniq IDs
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 
	
	// Last Uniq ID
	15,

	// Reserved
	0, 0,

	// Sort order
	sortByDate,
	};
*/

/************************************************************
 *
 *  FUNCTION: DateTypeCmp
 *
 *  DESCRIPTION: Compare two dates
 *
 *  PARAMETERS: 
 *
 *  RETURNS: 
 *
 *  CREATED: 1/20/95 
 *
 *  BY: Roger Flores
 *
 *************************************************************/
static Int DateTypeCmp(DateType d1, DateType d2)
{
	Int result;
	
	result = d1.year - d2.year;
	if (result != 0)
		{
		if ((*(int *) &d1) == -1)
			return 1;
		if ((*(int *) &d2) == -1)
			return -1;
		return result;
		}
	
	result = d1.month - d2.month;
	if (result != 0)
		return result;
	
	result = d1.day - d2.day;

	return result;

}


/************************************************************
 *
 *  FUNCTION: ExpenseAppInfoInit
 *
 *  DESCRIPTION: Create an app info chunk if missing.  Set
 *		the strings to a default.
 *
 *  PARAMETERS: database pointer
 *
 *  RETURNS: 0 if successful, errorcode if not
 *
 *  CREATED: 1/20/95 
 *
 *  BY: Roger Flores
 *
 *************************************************************/
Err	ExpenseAppInfoInit (DmOpenRef dbP)
{
	UInt cardNo;
	VoidHand h;
	LocalID dbID;
	LocalID appInfoID;
	ExpenseAppInfoPtr appInfoP;
	ExpenseAppInfoPtr	nilP = 0;
	
	if (DmOpenDatabaseInfo(dbP, &dbID, NULL, NULL, &cardNo, NULL))
		return dmErrInvalidParam;

	if (DmDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, 
		 NULL, &appInfoID, NULL, NULL, NULL))
		return dmErrInvalidParam;
	
	if (appInfoID == NULL)
		{
		h = DmNewHandle(dbP, sizeof (ExpenseAppInfoType));
		if (! h) return dmErrMemError;

		appInfoID = MemHandleToLocalID (h);
		DmSetDatabaseInfo(cardNo, dbID, NULL, NULL, NULL, NULL, NULL, NULL, 
			NULL, &appInfoID, NULL, NULL, NULL);
		}
	
	appInfoP = MemLocalIDToLockedPtr(appInfoID, cardNo);

	// Clear the app info block.
	DmSet (appInfoP, 0, sizeof (ExpenseAppInfoType), 0);

	// Initialize the categories.
	CategoryInitialize ((AppInfoPtr) appInfoP, expenseLocalizedAppInfoStr);

	// Initialize the sort order.
	DmSet (appInfoP, (ULong)&nilP->sortOrder, sizeof(appInfoP->sortOrder), 
		sortByDate);

	MemPtrUnlock (appInfoP);

	return 0;
}


/************************************************************
 *
 *  FUNCTION: ExpenseGetAppInfo
 *
 *  DESCRIPTION: Get the app info chunk 
 *
 *  PARAMETERS: database pointer
 *
 *  RETURNS: handle to the expense application info block (ExpenseAppInfoType)
 *
 *  CREATED: 5/12/95 
 *
 *  BY: Art Lamb
 *
 *************************************************************/
VoidHand ExpenseGetAppInfo (DmOpenRef dbP)
{
	Err error;
	UInt cardNo;
	LocalID dbID;
	LocalID appInfoID;
	
	error = DmOpenDatabaseInfo (dbP, &dbID, NULL, NULL, &cardNo, NULL);
	ErrFatalDisplayIf (error,  "Get getting expense app info block");

	error = DmDatabaseInfo (cardNo, dbID, NULL, NULL, NULL, NULL, NULL, 
			NULL, NULL, &appInfoID, NULL, NULL, NULL);
	ErrFatalDisplayIf (error,  "Get getting expense app info block");

	return ((VoidHand) MemLocalIDToGlobal (appInfoID, cardNo));
}


/************************************************************
 *
 *  FUNCTION: ExpenseCompareRecords
 *
 *  DESCRIPTION: Compare two records.
 *
 *  PARAMETERS: database record 1
 *				database record 2
 *
 *  RETURNS: -n if record one is less (n != 0)
 *			  n if record two is less
 *
 *  CREATED: 1/23/95 
 *
 *  BY: Roger Flores
 *
 *	COMMENTS:	Compare the two records key by key until
 *	there is a difference.  Return -n if r1 is less or n if r2
 *	is less.  A zero is never returned because if two records
 *	seem identical then their unique IDs are compared!
 *
 * This function accepts record data chunk pointers to avoid
 * requiring that the record be within the database.  This is
 * important when adding records to a database.  This prevents
 * determining if a record is a deleted record (which are kept
 * at the end of the database and should be considered "greater").
 * The caller should test for deleted records before calling this
 * function!
 *
 *************************************************************/
#define maxExpenseNameLength	25

static Int ExpenseCompareRecords (ExpensePackedRecordPtr r1, ExpensePackedRecordPtr r2, 
	Int sortOrder, SortRecordInfoPtr info1, SortRecordInfoPtr info2,
	VoidHand appInfoH)
	{
	Err err;
	Int result;
	BytePtr expensesP;
	VoidHand expensesH;


	// Sort by date.
	if (sortOrder == sortByDate)
		{
		result = DateTypeCmp(r1->date, r2->date);
		}

	// Sort by expense.
	else
		{
		if (r1->type == noExpenseType)
			return (-1);
		
		if (r1->type == noExpenseType)
			return (1);

		err = FtrGet (sysFileCExpense, expFeatureNum, (DWordPtr) &expensesH);
		if (err)
			return (r1->type - r2->type);
		
		expensesP = MemHandleLock (expensesH);
		result = (Int) expensesP[r1->type] - (Int) expensesP[r2->type];
		MemPtrUnlock (expensesP);
		}
		
	return result;
	}


/************************************************************
 *
 *  FUNCTION: ExpenseGetSortOrder
 *
 *  DESCRIPTION: This routine get the sort order value from the 
 *               to do application info block.
 *
 *  PARAMETERS: database pointer
 *
 *  RETURNS:    true if the to do record are sorted by priority, 
 *              false if the records are sorted by due date.
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/21/96	Initial Revision
 *
 *************************************************************/
Byte ExpenseGetSortOrder (DmOpenRef dbP)
{
	Byte sortOrder;
	ExpenseAppInfoPtr appInfoP;
			
	appInfoP = MemHandleLock (ExpenseGetAppInfo (dbP));
	sortOrder = appInfoP->sortOrder;
	MemPtrUnlock (appInfoP);	

	return (sortOrder);
}


/************************************************************
 *
 *  FUNCTION: ExpenseFindSortPosition
 *
 *  DESCRIPTION: Return where a record is or should be
 *		Useful to find or find where to insert a record.
 *
 *  PARAMETERS: database record (not deleted!)
 *
 *  RETURNS: the size in bytes
 *
 *  CREATED: 1/11/95 
 *
 *  BY: Roger Flores
 *
 *************************************************************/
static UInt ExpenseFindSortPosition (DmOpenRef dbP, ExpensePackedRecord *newRecord)
{
	Int sortOrder;
	
	sortOrder = ExpenseGetSortOrder (dbP);

	return (DmFindSortPosition (dbP, newRecord, NULL, 
		(DmComparF *)ExpenseCompareRecords, sortOrder));
}




/************************************************************
 *
 *  FUNCTION:    ExpenseChangeSortOrder
 *
 *  DESCRIPTION: Change the Expense Database's sort order
 *
 *  PARAMETERS:  database pointer
 *				     TRUE if sort by company
 *
 *  RETURNS:     nothing
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/21/96	Initial Revision
 *
 *************************************************************/
Err ExpenseChangeSortOrder(DmOpenRef dbP, Boolean sortOrder)
{
	ExpenseAppInfoPtr appInfoP;
	ExpenseAppInfoPtr	nilP = 0;


	appInfoP = MemHandleLock (ExpenseGetAppInfo (dbP));

	if (appInfoP->sortOrder != sortOrder)
		{
		DmWrite(appInfoP, (ULong)&nilP->sortOrder, &sortOrder, sizeof(appInfoP->sortOrder));
		
		DmInsertionSort(dbP, (DmComparF *) &ExpenseCompareRecords, (Int) sortOrder);
		}
		
	MemPtrUnlock (appInfoP);	

	return 0;
}



/************************************************************
 *
 *  FUNCTION: ExpenseSort
 *
 *  DESCRIPTION: Sort the appointment database.
 *
 *  PARAMETERS: database record
 *
 *  RETURNS: nothing
 *
 *  CREATED: 10/17/95 
 *
 *  BY: Art Lamb
 *
 *************************************************************/
void ExpenseSort (DmOpenRef dbP)
{
	Int sortOrder;
	
	sortOrder = ExpenseGetSortOrder (dbP);

	DmInsertionSort(dbP, (DmComparF *) &ExpenseCompareRecords, sortOrder);
}


/************************************************************
 *
 *  FUNCTION: 	  ExpensetPackedSize
 *
 *  DESCRIPTION: Returns the packed size of an ExpensePackedRecordType 
 *
 *  PARAMETERS:  database record
 *
 *  RETURNS:     the size in bytes
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/22/96	Initial Revision
 *
 *************************************************************/
static UInt ExpensePackedSize (ExpenseRecordPtr r)
{
	UInt size;

	
	size = sizeof (ExpensePackedRecord);
	
	if (r->amount)
		size += StrLen (r->amount) + 1;
	else
		size++;

	if (r->vendor)
		size += StrLen (r->vendor) + 1;
	else
		size++;

	if (r->city)
		size += StrLen (r->city) + 1;
	else
		size++;

	if (r->attendees)
		size += StrLen (r->attendees) + 1;
	else
		size++;

	if (r->note)
		size += StrLen (r->note) + 1;
	else
		size++;

	return size;
}


/************************************************************
 *
 *  FUNCTION: ExpenseUnpack
 *
 *  DESCRIPTION: Fills in the ExpensePackedRecord structure
 *
 *  PARAMETERS: database record
 *
 *  RETURNS: the record unpacked
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/21/96	Initial Revision
 *
 *************************************************************/
static void ExpenseUnpack (ExpensePackedRecordPtr src, ExpenseRecordPtr dest)
{
	CharPtr p;

	
	dest->date = src->date;
	dest->type = src->type;
	dest->paymentType = src->paymentType;
	dest->currency = src->currency;


	p = (CharPtr)src + sizeof(ExpensePackedRecord);
	
	// Get the amount.
	dest->amount = p;
	p += StrLen(p) + 1;

	// Get the vendor
	dest->vendor = p;
	p += StrLen(p) + 1;

	// Get the city
	dest->city = p;
	p += StrLen(p) + 1;

	// Get the attendees
	dest->attendees = p;
	p += StrLen(p) + 1;

	// Get the note
	dest->note = p;
}


/************************************************************
 *
 *  FUNCTION:    ExpensePack
 *
 *  DESCRIPTION: Pack an ExpenseRecordPtr
 *
 *  PARAMETERS:  database record
 *
 *  RETURNS:     a pack ExpensePackedRecord
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/22/96	Initial Revision
 *
 *************************************************************/
static void ExpensePack (ExpenseRecordPtr src, ExpensePackedRecordPtr dest)
{
	Char						zero = 0;
	ULong						offset;
	ExpensePackedRecordPtr	nilP = 0;
	
	
	DmWrite (dest, (ULong)&nilP->date, &src->date, sizeof(nilP->date));
	DmWrite (dest, (ULong)&nilP->type, &src->type, sizeof(nilP->type));
	DmWrite (dest, (ULong)&nilP->paymentType, &src->paymentType, sizeof(nilP->paymentType));
	DmWrite (dest, (ULong)&nilP->currency, &src->currency, sizeof(nilP->currency));

	offset = sizeof (ExpensePackedRecord);


	// Add the amount.
	if (src->amount)
		{
		DmStrCopy (dest, offset, src->amount);
		offset += StrLen (src->amount) + 1;
		}
	else
		{
		DmWrite (dest, offset, &zero, 1);
		offset++;
		}
	

	// Add the vendor.
	if (src->vendor)
		{
		DmStrCopy (dest, offset, src->vendor);
		offset += StrLen (src->vendor) + 1;
		}
	else
		{
		DmWrite (dest, offset, &zero, 1);
		offset++;
		}
	

	// Add the city.
	if (src->amount)
		{
		DmStrCopy (dest, offset, src->city);
		offset += StrLen (src->city) + 1;
		}
	else
		{
		DmWrite (dest, offset, &zero, 1);
		offset++;
		}
	

	// Add the attendees.
	if (src->attendees)
		{
		DmStrCopy (dest, offset, src->attendees);
		offset += StrLen (src->attendees) + 1;
		}
	else
		{
		DmWrite (dest, offset, &zero, 1);
		offset++;
		}
	

	// Add the note.
	if (src->note)
		{
		DmStrCopy (dest, offset, src->note);
		}
	else
		{
		DmWrite (dest, offset, &zero, 1);
		}
}


/************************************************************
 *
 *  FUNCTION: ExpenseGetRecord
 *
 *  DESCRIPTION: Get a record from a Appointment Database
 *
 *  PARAMETERS: dbP     - database pointer
 *				    index   - database index
 *				    r       - database record
 *              handleP - returned: handled of record
 *              
 *
 *  RETURNS: 	errorcode, 0 if successful
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/21/96	Initial Revision
 *
 *************************************************************/
Err ExpenseGetRecord (DmOpenRef dbP, UInt index, ExpenseRecordPtr r, 
	VoidHand * handleP)
{
	VoidHand handle;
	ExpensePackedRecordPtr src;
	

	handle = DmQueryRecord (dbP, index);
	ErrFatalDisplayIf(DmGetLastErr(), "Error Querying record");
	
	src = MemHandleLock (handle);

	if (DmGetLastErr())
		{
		*handleP = 0;
		return DmGetLastErr();
		}
	
	ExpenseUnpack (src, r);
	
	*handleP = handle;
	return 0;
}


/************************************************************
 *
 *  FUNCTION: ExpenseNewRecord
 *
 *  DESCRIPTION: Create a new record in sorted position
 *
 *  PARAMETERS: database pointer
 *				database record
 *
 *  RETURNS: ##0 if successful, errorcode if not
 *
 *  CREATED: 1/15/96
 *
 *  BY: Art Lamb
 *
 *************************************************************/
Err ExpenseNewRecord (DmOpenRef dbP, ExpenseRecordPtr item, UInt *index)
{
	Err 						err;
	Int						size;
	VoidHand 				recordH;
	UInt 						newIndex;
	ExpensePackedRecordPtr	recordP;
	

	// Compute the size of the new expense record.
	size = ExpensePackedSize (item);
		
	//  Allocate a chunk in the database for the new record.
	recordH = (Handle)DmNewHandle(dbP, (ULong) size);
	if (recordH == NULL)
		return dmErrMemError;

	// Pack the the data into the new record.
	recordP = MemHandleLock (recordH);
	ExpensePack (item, recordP);
	
		
	// Determine the sort position of the new record.
	newIndex = ExpenseFindSortPosition (dbP, recordP);

	MemPtrUnlock (recordP);

	// Insert the record.
	err = DmAttachRecord(dbP, &newIndex, recordH, 0);
	if (err) 
		MemHandleFree(recordH);
	else
		*index = newIndex;

	return err;
}


/************************************************************
 *
 *  FUNCTION:    ExpenseChangeRecordField
 *
 *  DESCRIPTION: Replace a field in a record with the passed value.
 *
 *  PARAMETERS:  scr    - packed database record
 *               size   - size of packed database record
 *               data   - data to add to the record
 *               fieldP - position to write to
 *
 *  RETURNS:     nothing
 *
 *  REVISION HISTORY:
 *			Name	Date		Description
 *			----	----		-----------
 *			art	8/22/96	Initial Revision
 *
 *************************************************************/
static void ExpenseChangeRecordField (ExpensePackedRecordPtr src, 
	ULong size, VoidPtr data, CharPtr fieldP)
{
	ULong		offset;
	ULong		bytes;
	CharPtr 	ptr;

	// Move the rest of the record.
	offset = (fieldP - (CharPtr)src) + StrLen (data) + 1;
	ptr = fieldP + StrLen (fieldP) + 1;
	bytes = size - (ULong)(ptr - (CharPtr)src);
	if (bytes)
		DmWrite (src, offset, ptr, bytes);
			
	// Write the new vendor field.
	offset = (fieldP - (CharPtr)src);
	DmStrCopy (src, offset, data);
}


/************************************************************
 *
 *  FUNCTION: ExpenseChangeRecord
 *
 *  DESCRIPTION: Change a record in the Expense Database
 *
 *  PARAMETERS: database pointer
 *					 database index
 *					 database record
 *					 changed fields
 *
 *  RETURNS: ##0 if successful, errorcode if not
 *
 *  CREATED: 1/14/95 
 *
 *  BY: Roger Flores
 *
 *	COMMENTS:	Records are not stored with extra padding - they
 *	are always resized to their exact storage space.  This avoids
 *	a database compression issue.  The code works as follows:
 *	
 *
 *************************************************************/
Err ExpenseChangeRecord(DmOpenRef dbP, UInt *index, 
	ExpenseRecordFieldType changedField, VoidPtr data)
{
	Err 							err;
	UInt 							curSize;
	UInt 							newSize;
	UInt 							newIndex;
	VoidHand 					recordH;
	ExpenseRecordType			record;
	ExpensePackedRecord	temp;
	ExpensePackedRecordPtr 	src;
	ExpensePackedRecordPtr 	nilP = 0;
	
	// Get the record which we are going to change
	recordH = DmQueryRecord(dbP, *index);
	src = MemHandleLock (recordH);
	

	// If the  record is being changes such that its sort position will 
	// change,  move the record to its new sort position.
	if (changedField == expenseDate || changedField == expenseType)
		{
		if (changedField == expenseDate)
			{
			temp.type = src->type;
			temp.date = *((DatePtr)data);
			}
		else
			{
			temp.type = *((BytePtr)data);
			temp.date = src->date;
			}
		newIndex = ExpenseFindSortPosition (dbP, &temp);
		DmMoveRecord (dbP, *index, newIndex);
		if (newIndex > *index) newIndex--;
		*index = newIndex;
		}


	if (changedField == expenseDate)
		{ 
		DmWrite (src, (ULong)&nilP->date, data, sizeof(src->date));
		goto exit;
		}

	if (changedField == expenseType)
		{
		DmWrite (src, (ULong)&nilP->type, data, sizeof(src->type));
		goto exit;
		}
			
	if (changedField == expensePaymentType)
		{
		DmWrite (src, (ULong)&nilP->paymentType, data, sizeof(src->paymentType));
		goto exit;
		}
			
	if (changedField == expenseCurrency)
		{
		DmWrite (src, (ULong)&nilP->currency, data, sizeof(src->currency));
		goto exit;
		}
			

	// Calculate the size of the changed record
	curSize = MemPtrSize (src);
	ExpenseUnpack (src, &record);
	newSize = curSize;
	
	if (changedField == expenseAmount)
		newSize += StrLen (data) - StrLen (record.amount);

	else if (changedField == expenseVendor)
		newSize += StrLen (data) - StrLen (record.vendor);

	else if (changedField == expenseCity)
		newSize += StrLen (data) - StrLen (record.city);

	else if (changedField == expenseAttendees)
		newSize += StrLen (data) - StrLen (record.attendees);

	else if (changedField == expenseNote)
		newSize += StrLen (data) - StrLen (record.note);


	// If the new record is longer, expand the record.
	if (newSize > curSize)
		{
		MemPtrUnlock (src);
		err = MemHandleResize (recordH, newSize);
		if (err) return (err);

		src = MemHandleLock (recordH);
		ExpenseUnpack (src, &record);
		}


	// Change the amount field.
	if (changedField == expenseAmount)
		ExpenseChangeRecordField (src, curSize, data, record.amount);

	// Change the vendor field.
	if (changedField == expenseVendor)
		ExpenseChangeRecordField (src, curSize, data, record.vendor);

	// Change the city field.
	else if (changedField == expenseCity)
		ExpenseChangeRecordField (src, curSize, data, record.city);

	// Change the attendees field.
	else if (changedField == expenseAttendees)
		ExpenseChangeRecordField (src, curSize, data, record.attendees);

	// Change the note field
	else if (changedField == expenseNote)
		ExpenseChangeRecordField (src, curSize, data, record.note);


	// If the new record is shorter, shrink the record.
	if (newSize < curSize)
		MemPtrResize (src, newSize);


exit:
	MemPtrUnlock (src);

	return 0;
}





