/*******************************************************************************

					CONDAT (UK)

********************************************************************************                                                                              

 This software product is the property of Condat (UK) Ltd and may not be
 disclosed to any third party without the express permission of the owner.                                 
                                                                              
********************************************************************************

 $Project name:	                                                      
 $Project code:	                                                           
 $Module:		
 $File:		    ATBPb.c
 $Revision:		                                                      
                                                                              
 $Author:		Condat(UK)                                                         
 $Date:		                                                          
                                                                               
********************************************************************************
                                                                              
 Description:
    
                        
********************************************************************************

 $History: ATBPb.c

    Jan 18, 2004 REF: CRR MMI-SPR-25332 xnkulkar
    Description: Moving entries from Sim Card to phone memory fails. 
    Solution: When entries are moved, corresponding updations are done in cache as well.
	
	   
 $End

*******************************************************************************/

#if defined (NEW_FRAME)

#include "typedefs.h"
#include "vsi.h"
#include "pei.h"
#include "custom.h"
#include "gsm.h"

#else

#include "STDDEFS.H"
#include "custom.h"
#include "gsm.h"
#include "vsi.h"

#endif

#include <stdio.h>
#include <string.h>

#include "mfw_mfw.h"
#include "mfw_sys.h"

#include "cus_aci.h"
#include "prim.h"
#include "pcm.h"

#include "ATBPb.h"
#include "ATBPb_i.h"

/* Global data for phonebook */

static T_PB_DATA *phonebook[PB_PHONEBOOKS_MAX] = {0};


/*******************************************************************************

 $Function:    	ATB_pb_GetPhonebook
 
 $Description:	Checks to see which file system is selected

 $Returns:		PB_OK					Action completed OK.
				PB_FILEWRITEFAIL		File write encountered an error

 $Arguments:	phonebook_id	The phonebook identifier
 				current_type	Place to store type of phonebook selected.
 
*******************************************************************************/

PB_RET	ATB_pb_GetPhonebook(SHORT phonebook_id, T_PB_TYPE *current_type)
{
	PB_RET 		result;
	T_PB_DATA	*data	= ATB_hnd_GetPbData(phonebook_id);

	tracefunction("ATB_pb_GetPhonebook");

	if (data)
	{
		*current_type = data->current_type;
		return PB_OK;
	}
		
	result = FS_pb_GetPhonebook(phonebook_id, current_type);

	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_SetPhonebook
 
 $Description:	Select file system

 $Returns:		PB_OK					Action completed OK.
				PB_FILEREADFAIL			File read encountered an error

 $Arguments:	phonebook_id	The phonebook identifier
 				current_type	Type of phonebook selected.
 
*******************************************************************************/

PB_RET	ATB_pb_SetPhonebook(SHORT phonebook_id, T_PB_TYPE current_type)
{
	PB_RET 		result;
	T_PB_DATA	*data	= ATB_hnd_GetPbData(phonebook_id);

	tracefunction("ATB_pb_SetPhonebook");
	
	if (data)
		data->current_type = current_type;
	
	result = FS_pb_SetPhonebook(phonebook_id, current_type);

	return result;
}

		
/*******************************************************************************

 $Function:    	ATB_pb_Initialise
 
 $Description:	Initialise an instance of phonebook.  Dynamically allocates a record
 				cache in RAM.  Creates the necessary file(s) if they do not already exist.
 				Sorts the phonebook by name and by number, creating the appropriate
 				index tables.
				IMPORTANT: if the file reading is non-blocking - i.e. if a request to read
				a file does not return with an answer straight away, but the system
				awaits a callback - then "cache_max" must equal "records_max".  This
				ensures that all records are stored in RAM and can be retrieved without
				waiting for a response.  If the file reading is blocking, then cache_max
				can be smaller than records_max.

				The memory allocated by this operation will not be freed until
				ATB_pb_Exit() is called.

 $Returns:		PB_OK					Action completed OK.
				PB_EXCT					Action currently executing, callback awaited.
										GI_pb_OK will be called if successful,
										GI_pb_Error otherwise.
				PB_BUSY					Failed, phonebook is busy.
				PB_FILEREADFAIL			File read encountered an error
				PB_FILEWRITEFAIL		File write encountered an error
				PB_BOOKALREADYEXISTS 	Tried to initialise a phonebook that already
										exists

 $Arguments:	phonebook_id	The phonebook identifier
				type			Type of phonebook.
				records_max		Indicates the maximum number of entries the
								phonebook can hold.
				cache_max		Indicates the maximum number of records that the
								PB will store concurrently in RAM.
				alpha_max		Maximum size of unicode alpha tag in characters
				number_max		Maximum size of phone number in digits
				ext_max			Maximum size of extended data in bytes
								
*******************************************************************************/

PB_RET	ATB_pb_Initialise(SHORT phonebook_id, T_PB_TYPE type, SHORT records_max, SHORT cache_max,
	SHORT alpha_max, SHORT number_max, SHORT ext_max)
{
	T_PB_DATA *data;
	
	tracefunction("ATB_pb_Initialise()");
	
	/* Check to see if this phonebook has already been allocated */
	
	if (ATB_hnd_GetPbData(phonebook_id)!=NULL)
	{
		trace("* ERROR * - Phonebook already exists");
		GI_pb_Error(phonebook_id, PB_INITIALISE, PB_BOOKALREADYEXISTS);
		return PB_BOOKALREADYEXISTS;
	}

	/* Allocate memory for phonebook data */

	data = (T_PB_DATA *)GI_pb_MemAlloc(sizeof(T_PB_DATA));
	ATB_hnd_SetPbData(phonebook_id, data);
	
	data->records_max = records_max;
	data->records_used = 0;
	data->search_results = 0;

	data->alpha_max = alpha_max;
	data->number_max = number_max;
	data->ext_max = ext_max;
	
	/* Allocate memory for index tables */
	
	data->name_table = (SHORT *)GI_pb_MemAlloc(records_max*sizeof(SHORT));
	memset(data->name_table, 0, records_max*sizeof(SHORT));
	data->number_table = (SHORT *)GI_pb_MemAlloc(records_max*sizeof(SHORT));
	memset(data->number_table, 0, records_max*sizeof(SHORT));
	data->search_table = (SHORT *)GI_pb_MemAlloc(records_max*sizeof(SHORT));
	memset(data->search_table, 0, records_max*sizeof(SHORT));
	data->in_memory = (SHORT *)GI_pb_MemAlloc(records_max*sizeof(SHORT));
	memset(data->in_memory, PB_EMPTY_RECORD, records_max*sizeof(SHORT));

	/* Allocate memory for cache */

	data->cache_max = cache_max;
	data->cache_size = 0;
	data->cache	= (T_PB_RECORD **)GI_pb_MemAlloc(records_max*sizeof(T_PB_RECORD *));
	memset(data->cache, 0, records_max*sizeof(T_PB_RECORD *));
	
	/* Set up command */
	
	data->command_id = PB_INITIALISE;
	data->status	= PB_STATUS_INIT;
	data->param.Initialise.type = type;
	data->param.Initialise.records_max = records_max;
	
	return ATB_status_Initialise(phonebook_id);
}

/* Status function for Initialise */

PB_RET ATB_status_Initialise(SHORT phonebook_id)
{
	T_PB_DATA		*data		= ATB_hnd_GetPbData(phonebook_id);
	T_PB_INITIALISE	*Initialise;
	SHORT				blocking;
	PB_RET			result;

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	Initialise = &data->param.Initialise;
	blocking = TRUE;
	while (blocking)
	{
		switch(data->status)
		{
			case PB_STATUS_INIT:
				trace("ATB_status_Initialise: INIT");
				result = FS_pb_Initialise(phonebook_id, Initialise->type, Initialise->records_max,
					data->alpha_max, data->number_max, data->ext_max);
				
				/* Select next state */
				
				data->status = PB_STATUS_EXEC;
				Initialise->phys_index = 0;
				Initialise->first_record = TRUE;
				Initialise->record = ATB_pb_AllocRec(phonebook_id);
				
				/* Allow exit from function */
				
				if (result!=PB_OK)
					blocking = FALSE;
				break;

			case PB_STATUS_EXEC:
				trace("ATB_status_Initialise: EXEC");

				/* Try to read in index tables from file system.  If we can't find them, the
				 * records will be read in and sorted. */

				if (Initialise->first_record)
				{
					result = FS_pb_ReadTables(phonebook_id, &data->records_used, data->name_table, data->number_table);
					if (result==PB_OK)
					{
						/* Escape to PB_STATUS_COMPLETE */
						result = PB_OK;
						data->status = PB_STATUS_COMPLETE;
						break;
					}
				}
				/* If we've already read a record, and it's an existing record, add it to the
				 * index tables and to the cache */
				 
				else
				{	
					if (Initialise->record->alpha.data[0]!=(USHORT)0xFFFF)
					{
						ATB_mem_UpdateCache(phonebook_id, Initialise->phys_index, Initialise->record);
						ATB_index_AddRec(phonebook_id, INDEX_NAME, Initialise->phys_index, Initialise->record, NULL);
						ATB_index_AddRec(phonebook_id, INDEX_NUMBER, Initialise->phys_index, Initialise->record, NULL);
						data->records_used++;
					}

					Initialise->phys_index++;
				}

				/* Start processing after first record is read */
				
				Initialise->first_record = FALSE;
				
				/* If we haven't just read the last record, read the next one */
				
				if (Initialise->phys_index<data->records_max)
				{
					result = FS_pb_ReadRec(phonebook_id, Initialise->phys_index, Initialise->record);
				}
				else
				{
					/* Set next state as finished */
					result = PB_OK;
					data->status = PB_STATUS_COMPLETE;
				}


				/* Allow exit from function */
				
				if (result!=PB_OK)
					blocking = FALSE;
				break;

			case PB_STATUS_COMPLETE:
				trace("ATB_status_Initialise: COMPLETE");
				blocking = FALSE;

				/* Close file */

				FS_pb_Finished(phonebook_id);
				FS_pb_WriteTables(phonebook_id, data->records_used, data->name_table, data->number_table);
				
				/* Free the allocated record */

				ATB_pb_FreeRec(phonebook_id, Initialise->record);
				
				/* Notify the GI of success */
				GI_pb_OK(phonebook_id, data->command_id, NULL);
				result = PB_OK;
				data->status = PB_STATUS_NONE;
				break;
		}
	}

	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_Exit
 
 $Description:	Frees memory associated with phonebook.  To be called when the
 				phonebook is no longer required, or at shutdown.

 $Returns:		PB_OK		Action completed OK.

 $Arguments:	phonebook_id	The phonebook identifier
 
*******************************************************************************/

PB_RET ATB_pb_Exit(SHORT phonebook_id)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT phys_index;

	trace("ATB_pb_Exit");

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	for (phys_index = 0; phys_index<data->records_max; phys_index++)
	{
		if (data->cache[phys_index]!=NULL)
			ATB_pb_FreeRec(phonebook_id, data->cache[phys_index]);
	}

	GI_pb_MemFree((UBYTE *)data->cache, data->records_max*sizeof(T_PB_RECORD *));
	GI_pb_MemFree((UBYTE *)data->in_memory, data->records_max*sizeof(SHORT));
	GI_pb_MemFree((UBYTE *)data->search_table, data->records_max*sizeof(SHORT));
	GI_pb_MemFree((UBYTE *)data->number_table, data->records_max*sizeof(SHORT));
	GI_pb_MemFree((UBYTE *)data->name_table, data->records_max*sizeof(SHORT));
	
	GI_pb_MemFree((UBYTE *)data, sizeof(T_PB_DATA));

	ATB_hnd_SetPbData(phonebook_id, NULL);	
	return PB_OK;
}


/*******************************************************************************

 $Function:    	ATB_pb_Status
 
 $Description:	Returns PB_OK if the phonebook is ready for a new command,
 				or PB_BUSY if the phonebook is busy.

 $Returns:		PB_OK		All tasks completed, phonebook ready.
 				PB_BUSY		Commands currently executing.

 $Arguments:	phonebook_id	The phonebook identifier
 
*******************************************************************************/

PB_RET	ATB_pb_Status(SHORT phonebook_id)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	PB_RET	result;

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}

	// Jan 18, 2004 REF: CRR MMI-SPR-25332 xnkulkar
    	// Description: Moving entries from Sim Card to phone memory fails. 
    	// Solution: result should be set as PB_OK when PB_STATUS_COMPLETE is true also.
	if ((data->status==PB_STATUS_NONE) ||(data->status==PB_STATUS_COMPLETE))
		result = PB_OK;
	else
		result = PB_BUSY;
	
	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_Info
 
 $Description:	Returns information about the phonebook, which will be returned
 				in the data structure pointed to by phonebook_info.  The caller must
 				allocate the T_PB_INFO structure.

 $Returns:		PB_OK		Action completed OK.

 $Arguments:	phonebook_id	The phonebook identifier
				phonebook_info	Pointer to data structure to contain phonebook
								information (allocated by caller).
 
*******************************************************************************/

PB_RET	ATB_pb_Info(SHORT phonebook_id, T_PB_INFO *phonebook_info)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	phonebook_info->records_used = data->records_used;
	phonebook_info->records_max = data->records_max;
	phonebook_info->records_free = data->records_max - data->records_used;
	phonebook_info->search_results = data->search_results;
	
	return PB_OK;
}


/*******************************************************************************

 $Function:    	ATB_pb_ReadRec
 
 $Description:	Reads a record from the logical position index.  The record information
 				will be returned in the data structure pointed to by record.  The 
				T_PB_RECORD structure must be allocated by the user.

				This function calls the ATB_mem_ReadRec function, with an additional
				check to make sure no other commands are currently executing.

 $Returns:		PB_OK				Action completed OK.
 				PB_BUSY				Failed, phonebook is busy.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index list from which the record is to be read
				log_index		Logical index in the index list of the record to read
				record			Structure in which to store record data
 
*******************************************************************************/

PB_RET	ATB_pb_ReadRec(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index, T_PB_RECORD *record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	PB_RET result;

	tracefunction("ATB_pb_ReadRec");

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	/* Make sure phonebook is not busy */
	
	if (ATB_pb_Status(phonebook_id)==PB_BUSY)
	{
		trace("* ERROR * - Phonebook is busy");
		result = PB_BUSY;
	}
	else
	{
		/* Read the record from cache or from file */
		
		result = ATB_mem_ReadRec(phonebook_id, index_type, log_index, record);
	}

	/* Send success/failure info to the GI */
	
	if (result==PB_OK)
		GI_pb_OK(phonebook_id, PB_READREC, NULL);
	else if (result!=PB_EXCT)
		GI_pb_Error(phonebook_id, PB_READREC, result);

	/* Close any open file */
	
	FS_pb_Finished(phonebook_id);
	
	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_WriteRec
 
 $Description:	Writes a record to the logical position index.  If the index is
 				PB_NEW_RECORD, a new record is added, otherwise the previous
 				record at that position is overwritten. The name and number index lists
 				will be updated with the new entry in the appropriate place in each.
				GI_pb_OK will be called to confirm that the write completed successfully,
				otherwise GI_pb_Error will be called with an error code.
				Since the order of the records may change due to sorting, GI_pb_OK
				will receive a pointer to an integer new_log_index, which will store the
				new position of the record in the chosen index list.  This is allocated
				by and will be destroyed by the phonebook, so must be copied by the
				GI before function return.

 $Returns:		PB_OK				Action completed OK.
				PB_EXCT				Action currently executing, callback awaited.
									GI_pb_OK will be called if successful,
									GI_pb_Error otherwise.
									GI_pb_OK will receive a pointer to the following:
									SHORT new_log_index - The new position of the
 									record in the chosen index list
 				PB_BUSY				Failed, phonebook is busy.
				PB_FILEWRITEFAIL	File write encountered an error
				PB_RECDOESNOTEXIST	(Overwriting only) Tried to access a record that
									does not exist

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index list of the record to write
				log_index		Logical index in the index list of the record to write
				record			Pointer to record data to write to phonebook
								(allocated by caller)
 
*******************************************************************************/

PB_RET ATB_pb_WriteRec(SHORT phonebook_id, T_PB_TYPE index_type, SHORT log_index, T_PB_RECORD *record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT phys_index;

	tracefunction("ATB_pb_WriteRec");

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	/* Make sure phonebook is not busy */
	
	if (ATB_pb_Status(phonebook_id)==PB_BUSY)
	{
		trace("* ERROR * - Phonebook is busy");
		GI_pb_Error(phonebook_id, PB_WRITEREC, PB_BUSY);
		return PB_BUSY;
	}

	/* Are we adding a new record, or overwriting an old one? */
	
	if (log_index==PB_NEW_RECORD)
	{
		/* Is phonebook full? */

		if (data->records_used==data->records_max)
		{
			GI_pb_Error(phonebook_id, PB_WRITEREC, PB_BOOKFULL);
			return PB_BOOKFULL;
		}
		
		/* Find an empty physical record */
		for (phys_index=0; phys_index<data->records_max; phys_index++)
		{
			if (data->in_memory[phys_index]==PB_EMPTY_RECORD)
				break;
		}
	}
	else
	{
		/* Check that record exists */

		if (log_index<0
			|| (log_index>=data->records_used && (index_type==INDEX_NAME || index_type==INDEX_NUMBER))
			|| (index_type==INDEX_SEARCH && log_index>data->search_results)
			|| (index_type==INDEX_PHYSICAL && log_index>data->records_max))
		{
			GI_pb_Error(phonebook_id, PB_WRITEREC, PB_RECDOESNOTEXIST);
			return PB_RECDOESNOTEXIST;
		}
		
		phys_index = ATB_index_GetPhysIndex(phonebook_id, index_type, log_index);
	}

	/* Set up command */
	
	data->command_id 	= PB_WRITEREC;
	data->status		= PB_STATUS_INIT;

	data->param.WriteRec.log_index = log_index;
	data->param.WriteRec.index_type = index_type;
	data->param.WriteRec.phys_index = phys_index;
	data->param.WriteRec.record = record;
	
	return ATB_status_WriteRec(phonebook_id);
}

/* Status function for WriteRec */

PB_RET ATB_status_WriteRec(SHORT phonebook_id)
{
	T_PB_DATA		*data		= ATB_hnd_GetPbData(phonebook_id);
	T_PB_WRITEREC	*WriteRec	= &data->param.WriteRec;
	SHORT				blocking;
	PB_RET			result;
	SHORT				name_log_index;
	SHORT				number_log_index;
	SHORT				*log_index_return;

	blocking = TRUE;
	while (blocking)
	{
		switch(data->status)
		{
			case PB_STATUS_INIT:
				result = FS_pb_WriteRec(phonebook_id, WriteRec->phys_index, WriteRec->record);
				
				/* Select next state */
				
				data->status = PB_STATUS_COMPLETE;
				
				/* Allow exit from function */
				
				if (result!=PB_OK)
					blocking = FALSE;
				break;

			case PB_STATUS_COMPLETE:

				if (WriteRec->log_index!=PB_NEW_RECORD)
				{
					name_log_index = ATB_index_GetLogIndex(phonebook_id, INDEX_NAME, WriteRec->phys_index);
					ATB_index_RemoveRec(phonebook_id, INDEX_NAME, name_log_index);
					number_log_index = ATB_index_GetLogIndex(phonebook_id, INDEX_NUMBER, WriteRec->phys_index);
					ATB_index_RemoveRec(phonebook_id, INDEX_NUMBER, number_log_index);
					data->records_used--;
				}

				/* Indicate that record is new, then add to the cache */
				
				data->in_memory[WriteRec->phys_index] = 0;
				ATB_mem_UpdateCache(phonebook_id, WriteRec->phys_index, WriteRec->record); 

				/* Add the record to the index lists */
				
				ATB_index_AddRec(phonebook_id, INDEX_NAME, WriteRec->phys_index, WriteRec->record, &name_log_index);
				ATB_index_AddRec(phonebook_id, INDEX_NUMBER, WriteRec->phys_index, WriteRec->record, &number_log_index);
				data->records_used++;
				
				blocking = FALSE;

				/* Close any open file */
				
				FS_pb_Finished(phonebook_id);
				FS_pb_WriteTables(phonebook_id, data->records_used, data->name_table, data->number_table);
	
				/* Notify the GI of success */
				log_index_return = NULL;
				if (WriteRec->index_type==INDEX_NAME)
					log_index_return = &name_log_index;
				if (WriteRec->index_type==INDEX_NUMBER)
					log_index_return = &number_log_index;
				
				GI_pb_OK(phonebook_id, data->command_id, log_index_return);
				result = PB_OK;
				data->status = PB_STATUS_NONE;
				break;
		}
	}

	/* Report any errors to the GI */
	
	if (result!=PB_OK && result!=PB_EXCT)
	{
		GI_pb_Error(phonebook_id, PB_WRITEREC, result);	
	}
	
	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_DeleteRec
 
 $Description:	Deletes a record at a logical position index.
				GI_pb_OK will be called to confirm that the delete completed successfully,
				otherwise GI_pb_Error will be called with an error code.

 $Returns:		PB_OK				Action completed OK.
				PB_EXCT				Action currently executing, callback awaited.
									GI_pb_OK will be called if successful,
									GI_pb_Error otherwise.
				PB_BUSY				Failed, phonebook is busy.
				PB_FILEWRITEFAIL	File write encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index list of the record to delete
				log_index		Logical index in the index list of the record to delete

*******************************************************************************/

PB_RET ATB_pb_DeleteRec(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);

	tracefunction("ATB_pb_DeleteRec");
	
	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}

	/* Make sure phonebook is not busy */
	
	if (ATB_pb_Status(phonebook_id)==PB_BUSY)
	{
		trace("* ERROR * - Phonebook is busy");
		GI_pb_Error(phonebook_id, PB_DELETEREC, PB_BUSY);
		return PB_BUSY;
	}
	
	/* Check that record exists */

	if (log_index<0
		|| (log_index>=data->records_used && (index_type==INDEX_NAME || index_type==INDEX_NUMBER))
		|| (index_type==INDEX_SEARCH && log_index>data->search_results)
		|| (index_type==INDEX_PHYSICAL && log_index>data->records_max))
	{
		GI_pb_Error(phonebook_id, PB_DELETEREC, PB_RECDOESNOTEXIST);
		return PB_RECDOESNOTEXIST;
	}
	
	/* Set up the command */
	
	data->command_id = PB_DELETEREC;
	data->status	= PB_STATUS_INIT;

	data->param.DeleteRec.log_index = log_index;
	data->param.DeleteRec.index_type = index_type;
	data->param.DeleteRec.phys_index = ATB_index_GetPhysIndex(phonebook_id, index_type, log_index);
	
	return ATB_status_DeleteRec(phonebook_id);
}

/* Status function for DeleteRec */

PB_RET ATB_status_DeleteRec(SHORT phonebook_id)
{
	T_PB_DATA		*data		= ATB_hnd_GetPbData(phonebook_id);
	T_PB_DELETEREC	*DeleteRec	= &data->param.DeleteRec;
	SHORT				blocking;
	PB_RET			result;
	SHORT				name_log_index;
	SHORT				number_log_index;

	blocking = TRUE;
	while (blocking)
	{
		switch(data->status)
		{
			case PB_STATUS_INIT:
				result = FS_pb_DeleteRec(phonebook_id, DeleteRec->phys_index);
				
				/* Select next state */
				
				data->status = PB_STATUS_COMPLETE;
				
				/* Allow exit from function */
				
				if (result!=PB_OK)
					blocking = FALSE;
				break;

			case PB_STATUS_COMPLETE:
				name_log_index = ATB_index_GetLogIndex(phonebook_id, INDEX_NAME, DeleteRec->phys_index);
				ATB_index_RemoveRec(phonebook_id, INDEX_NAME, name_log_index);
				number_log_index = ATB_index_GetLogIndex(phonebook_id, INDEX_NUMBER, DeleteRec->phys_index);
				ATB_index_RemoveRec(phonebook_id, INDEX_NUMBER, number_log_index);
				data->records_used--;
				data->in_memory[DeleteRec->phys_index] = -1;

				// Jan 18, 2004 REF: CRR MMI-SPR-25332 xnkulkar
    				// Description: Moving entries from Sim Card to phone memory fails. 
    				// Solution: When entries are moved, corresponding updations are done in cache as well.
				if(data->cache[DeleteRec->phys_index]!=NULL)
				{
             				ATB_pb_FreeRec(phonebook_id, data->cache[DeleteRec->phys_index]);
	 				data->cache[DeleteRec->phys_index]=NULL;
	 				data->cache_size--;
				}

				/* Close any open file */
	
				FS_pb_Finished(phonebook_id);
				FS_pb_WriteTables(phonebook_id, data->records_used, data->name_table, data->number_table);
					
				blocking = FALSE;
				
				/* Notify the GI of success */

				GI_pb_OK(phonebook_id, data->command_id, NULL);
				result = PB_OK;
				data->status = PB_STATUS_NONE;
				break;
		}
	}

	/* Report any errors to the GI */
	
	if (result!=PB_OK && result!=PB_EXCT)
	{
		GI_pb_Error(phonebook_id, PB_DELETEREC, result);	
	}
	
	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_Find
 
 $Description:	Find the nearest match to the supplied record.  Only the index_type
 				specified will be considered: if the index_type is INDEX_NAME, then the
 				name only will be matched; if INDEX_NUMBER, then just the number.
				The new_log_index parameter should be a pointer to a user allocated
				integer.  This will return the logical index of the closest match in the
				index list specified.  This index either corresponds to a perfect match,
				or to the record that would appear just after the provided record in the
				list.  For example, if the list consisted of record 0, Alice and record 1,
				Carol, then a search for Bob would return 1.  A search for a record
				that would occur before the first record always returns 0.  A search for
				a record that would occur after the last record returns the index of the
				last record +1.
				The "match" parameter should be a pointer to a user allocated variable
				of type T_PB_MATCH.  This returns information about how closely the
				found entry matches the record.  In the example above, a search for
				Bob would return 1 and MATCH_NONE; a search for Ca would return 1
				and MATCH_START, and a search for Carol would return 1 and
				MATCH_EXACT. Rarely, match may be MATCH_FRAGMENT: this can be
				considered equivalent to MATCH_NONE in this context.

 $Returns:		PB_OK				Action completed OK.
 				PB_BUSY				Failed, phonebook is busy.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist
									(should never happen)

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index_type of the provided record that is to be
								searched for, and the index list that the resulting
								new_log_index pertains to
				record			The record whose index_type is to be matched
				new_log_index	Returns with the logical index of the closest matching
								record
				match			Specifies how good the resulting match was
								(MATCH_NONE, MATCH_START, MATCH_FRAGMENT
								or MATCH_EXACT).
 
*******************************************************************************/


PB_RET ATB_pb_Find (SHORT phonebook_id, T_PB_INDEX index_type, T_PB_RECORD *record, SHORT *new_log_index, T_PB_MATCH *match)
{
	PB_RET result;

	tracefunction("ATB_pb_Find");
	
	result = ATB_index_Find(phonebook_id, index_type, record, new_log_index, match);

	/* Close any open file */
	
	FS_pb_Finished(phonebook_id);
		
	/* Send success/failure info to the GI */
	
	if (result==PB_OK)
		GI_pb_OK(phonebook_id, PB_FIND, NULL);
	else if (result!=PB_EXCT)
		GI_pb_Error(phonebook_id, PB_FIND, result);

	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_ReadRecList
 
 $Description:	Fetches record information of sequential records in an index list, starting
 				at logical record start_log_index and fetching recs_count records in total.
 				The information will be stored in the caller allocated array of records
 				provided.

 $Returns:		PB_OK				Action completed OK.
				PB_BUSY				Failed, phonebook is busy.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index list to use
				start_log_index	Logical index in the index list of the first record to
								read
				recs_count		Total number of sequential records to read. 
				record			Array of entries in which to store the fetched records.
								The array must be recs_count in size. (Caller allocated).
 
*******************************************************************************/

PB_RET ATB_pb_ReadRecList(SHORT phonebook_id, T_PB_INDEX index_type, SHORT start_log_index, SHORT num_recs, T_PB_LIST *list)
{
	PB_RET result;
	SHORT log_index;

	tracefunction("ATB_pb_ReadRecList");
	
	list->records_used = 0;

	/* Make sure phonebook is not busy */
	
	if (ATB_pb_Status(phonebook_id)==PB_BUSY)
	{
		trace("* ERROR * - Phonebook is busy");
			/* Report any errors to the GI */
		GI_pb_Error(phonebook_id, PB_READRECLIST, PB_BUSY);	
		return PB_BUSY;
	}

	/* Read in list of records */
	
	for (log_index = start_log_index; log_index<start_log_index+num_recs && log_index<list->records_max; log_index++)
	{
		result = ATB_mem_ReadRec(phonebook_id, index_type, log_index, &list->record[log_index-start_log_index]);
		if (result!=PB_OK)
			break;
		list->records_used++;
	}

	/* Close any open file */
	
	FS_pb_Finished(phonebook_id);
	
	/* Send success/failure info to the GI */
	
	if (result==PB_OK)
		GI_pb_OK(phonebook_id, PB_READRECLIST, NULL);
	else if (result!=PB_EXCT)
		GI_pb_Error(phonebook_id, PB_READRECLIST, result);
	
	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_Search
 
 $Description:	Searches every record in the index list for a fragment.  The index list
 				must be either INDEX_NAME or INDEX_NUMBER.  If it is INDEX_NAME,
 				then a name fragment will be searched for; if it is INDEX_NUMBER,
 				a number fragment will be searched for.

				The results are stored in an index list.  They may be accessed by
				using the normal read and write functions, using the index type
				INDEX_SEARCH.  The size of the search list is returned in the variable
				recs_count.

 $Returns:		PB_OK				Action completed OK.
				PB_BUSY				Failed, phonebook is busy.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist
				(should never happen)

 $Arguments:	phonebook_id The phonebook identifier
				index_type	The index list to use
				record		Record containing the fragment to search for
				recs_count	Pointer to an int (user allocated) in which will be returned
							the number of search results. 

 
*******************************************************************************/

PB_RET ATB_pb_Search(SHORT phonebook_id, T_PB_INDEX index_type, T_PB_RECORD *record, SHORT *recs_count)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	PB_RET result;
	SHORT count = 0;
	SHORT log_index;
	T_PB_MATCH match;
	T_PB_RECORD *cur_record;

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}

	/* Allocate record for temporary use */
	
	cur_record = ATB_pb_AllocRec(phonebook_id);
		
	/* Make sure phonebook is not busy */
	
	if (ATB_pb_Status(phonebook_id)==PB_BUSY)
	{
		trace("* ERROR * - Phonebook is busy");
		GI_pb_Error(phonebook_id, PB_SEARCH, PB_BUSY);	
		return PB_BUSY;
	}
	
	/* Make sure we're searching a valid index type */

	if (index_type==INDEX_PHYSICAL || index_type==INDEX_SEARCH)
	{
		GI_pb_Error(phonebook_id, PB_SEARCH, PB_INDEXINVALID);	
		return PB_INDEXINVALID;
	}
	
	for (log_index=0; log_index<data->records_used; log_index++)
	{
		result = ATB_mem_ReadRec(phonebook_id, index_type, log_index, cur_record);
		if (result!=PB_OK)
			break;
			
		match = ATB_index_Match(record, cur_record, index_type);
		
		if (match==MATCH_EXACT || match==MATCH_START || match==MATCH_FRAGMENT)
		{
			data->search_table[count] = ATB_index_GetPhysIndex(phonebook_id, index_type, log_index);
			count++;
		}
	}

	data->search_results = count;
	*recs_count = count;

	/* Free allocated record */
	
	ATB_pb_FreeRec(phonebook_id, cur_record);

	/* Close any open file */
	
	FS_pb_Finished(phonebook_id);
	
	/* Send success/failure info to the GI */
	
	if (result==PB_OK)
		GI_pb_OK(phonebook_id, PB_SEARCH, NULL);
	else if (result!=PB_EXCT)
		GI_pb_Error(phonebook_id, PB_SEARCH, result);

	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_ConvIndex
 
 $Description:	Returns the index in table dest_index_type corresponding to the index
 				in table src_index_type.
 
 $Returns:		PB_OK			Action completed OK.

 $Arguments:	phonebook_id	The phonebook identifier.
				index_type	The index table of the original index.
				log_index		The original logical index.
				new_index_type	The index table required.
				new_log_index	Pointer to where the new logical index will be stored
				
*******************************************************************************/

PB_RET ATB_pb_ConvIndex(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index,
	T_PB_INDEX new_index_type, SHORT *new_log_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT phys_index;

	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}
	
	if (index_type==new_index_type)
	{
		*new_log_index = log_index;
	}
	else
	{	
		phys_index = ATB_index_GetPhysIndex(phonebook_id, index_type, log_index);
		*new_log_index = ATB_index_GetLogIndex(phonebook_id, new_index_type, phys_index);
	}
	
	return PB_OK;
}


/*******************************************************************************

 $Function:    	ATB_pb_CharToBCD

 $Description:	Converts an ascii string of digits into BCD form, with 4 bits representing
 				each digit.
 
 $Returns:		None

 $Arguments:	src 	- source string (ascii)
 				dest	- destination string for BCD digits
 
*******************************************************************************/

void ATB_pb_CharToBCD(UBYTE *dest, char *src, int max_len)
{
	UBYTE srcIndex = 0;
	UBYTE destIndex = 0;
	BOOL leftbits = TRUE;														/* Left or right nibble */
	UBYTE digit = 0;
  
	while (digit!=0xF && srcIndex<max_len)
	{
		if (src[srcIndex]==NULL)
			digit = 0xF;														/* 0xF terminates BCD */
		else
			digit = src[srcIndex]-'0';											/* The digit, 0 to 9. */

		if (leftbits)
		{
			dest[destIndex] = digit;
			leftbits = FALSE;
		}
		else
		{
			dest[destIndex] |= digit<<4;										/* *16 shifts right 4 bits */
			leftbits = TRUE;
			destIndex++;
		}
		srcIndex++;
	} 
	
	return;
}


/*******************************************************************************

 $Function:    	ATB_pb_BCDToChar

 $Description:	Converts a BCD string to ascii digits
 
 $Returns:		None

 $Arguments:	src 	- source string (BCD)
 				dest	- destination string for ascii digits
 
*******************************************************************************/

void ATB_pb_BCDToChar(char *dest, UBYTE *src, int max_len)
{
	SHORT numIndex = 0;
	UBYTE digit = 0xF; //dummy

	while (digit!=NULL && numIndex<max_len)
	{
		/*HELLO!*/
		digit = ATB_num_Digit(src, numIndex);

		if (digit==0xF)
			digit = NULL;
		else
			digit+='0';
		dest[numIndex] = digit;

		numIndex++;
	}

	return;
}


/*******************************************************************************

 $Function:    	ATB_pb_AllocRec
 
 $Description:	Allocates memory for a record

 $Returns:		

 $Arguments:	phonebook_id	The phonebook identifier
 
*******************************************************************************/

T_PB_RECORD *ATB_pb_AllocRec(SHORT phonebook_id)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	T_PB_RECORD *record;
	
	record = (T_PB_RECORD *)GI_pb_MemAlloc(sizeof(T_PB_RECORD));
	
	record->alpha.data = (USHORT *)GI_pb_MemAlloc(data->alpha_max*sizeof(USHORT));
	record->number = (UBYTE *)GI_pb_MemAlloc(data->number_max/2);
	if (data->ext_max>0)
	{
		record->ext_data = (UBYTE *)GI_pb_MemAlloc(data->ext_max);
	}
	else
	{
		record->ext_data = NULL;
	}
	record->ton_npi = 0;

	return record;
}


/*******************************************************************************

 $Function:    	ATB_pb_FreeRec
 
 $Description:	Frees memory allocated for a record

 $Returns:		

 $Arguments:	phonebook_id	The phonebook identifier
 				record			The record to destroy
 
*******************************************************************************/

void ATB_pb_FreeRec(SHORT phonebook_id, T_PB_RECORD *record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);

	if (data->ext_max>0 && record->ext_data!=NULL)
	{
		GI_pb_MemFree((UBYTE *)record->ext_data, data->ext_max);
	}
	GI_pb_MemFree((UBYTE *)record->number, data->number_max/2);
	GI_pb_MemFree((UBYTE *)record->alpha.data, data->alpha_max*sizeof(USHORT));

	GI_pb_MemFree((UBYTE *)record, sizeof(T_PB_RECORD));
	
	return;
}


/*******************************************************************************

 $Function:    	ATB_pb_AllocRecList
 
 $Description:	Allocates memory for a list of records

 $Returns:		

 $Arguments:	phonebook_id	The phonebook identifier
 				num_recs		The number of records to allocate
 
*******************************************************************************/

T_PB_LIST *ATB_pb_AllocRecList(SHORT phonebook_id, SHORT num_recs)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	T_PB_LIST *list;
	SHORT rec_index;
	
	list = (T_PB_LIST *)GI_pb_MemAlloc(sizeof(T_PB_LIST));
	list->records_max = num_recs;
	list->records_used = 0;
	list->record = (T_PB_RECORD *)GI_pb_MemAlloc(sizeof(T_PB_RECORD)*num_recs);
	
	for (rec_index=0; rec_index<num_recs; rec_index++)
	{
		list->record[rec_index].alpha.data = (USHORT *)GI_pb_MemAlloc(data->alpha_max*sizeof(USHORT));
		list->record[rec_index].number = (UBYTE *)GI_pb_MemAlloc(data->number_max/2);
		if (data->ext_max>0)
		{
			list->record[rec_index].ext_data = (UBYTE *)GI_pb_MemAlloc(data->ext_max);
		}
		else
		{
			list->record[rec_index].ext_data = NULL;
		}
	}

	return list;
}


/*******************************************************************************

 $Function:    	ATB_pb_FreeRecList
 
 $Description:	Frees memory allocated for a list of records

 $Returns:		

 $Arguments:	phonebook_id	The phonebook identifier
 				record			The records to destroy
 				num_recs		Number of records in the list
 
*******************************************************************************/

void ATB_pb_FreeRecList(SHORT phonebook_id, T_PB_LIST *list)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT rec_index;
	
	for (rec_index=0; rec_index<list->records_max; rec_index++)
	{
		if (data->ext_max>0 && list->record[rec_index].ext_data!=NULL)
		{
			GI_pb_MemFree((UBYTE *)list->record[rec_index].ext_data, data->ext_max);
		}
		GI_pb_MemFree((UBYTE *)list->record[rec_index].number, data->number_max/2);
		GI_pb_MemFree((UBYTE *)list->record[rec_index].alpha.data, data->alpha_max*sizeof(USHORT));
	}

	GI_pb_MemFree((UBYTE *)list->record, sizeof(T_PB_RECORD)*list->records_max);
	GI_pb_MemFree((UBYTE *)list, sizeof(T_PB_LIST));

	return;
}


/*******************************************************************************

 $Function:    	ATB_mem_CopyRec
 
 $Description:	Copies a record from one location to another.  The destination should
 				have memory allocated for its strings.

 $Returns:		

 $Arguments:	dest_record		The destination record
 				src_record		The source record
 
*******************************************************************************/

void	ATB_pb_CopyRec(SHORT phonebook_id, T_PB_RECORD *dest_record, T_PB_RECORD *src_record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	
	dest_record->alpha.length = src_record->alpha.length;
	dest_record->alpha.dcs = src_record->alpha.dcs;
	memcpy((UBYTE *)dest_record->alpha.data, (UBYTE *)src_record->alpha.data, data->alpha_max*sizeof(USHORT));
	memcpy(dest_record->number, src_record->number, data->number_max/2);
	if (data->ext_max>0)
	{
		memcpy(dest_record->ext_data, src_record->ext_data, data->ext_max);
	}
	dest_record->ton_npi = src_record->ton_npi;
	
	return;
}


/*******************************************************************************

 $Function:    	ATB_pb_OK
 
 $Description:	This function is called if the requested FS command executed successfully.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
 
*******************************************************************************/


PB_RET ATB_pb_OK(SHORT phonebook_id)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	PB_RET result;

	switch(data->command_id)
	{
		case PB_INITIALISE:
			result = ATB_status_Initialise(phonebook_id);
			break;
		case PB_WRITEREC:
			result = ATB_status_WriteRec(phonebook_id);
			break;
		case PB_DELETEREC:
			result = ATB_status_DeleteRec(phonebook_id);
			break;
	}

	return result;
}


/*******************************************************************************

 $Function:    	ATB_pb_Error
 
 $Description:	This function is called if an error was returned while executing the
 				requested command.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				error_id		Identifies the error that occurred.

*******************************************************************************/

void ATB_pb_Error(SHORT phonebook_id, SHORT error_id)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);

	/* Forward the error report to the GI */

	GI_pb_Error(phonebook_id, data->command_id, error_id);
	data->status = PB_STATUS_NONE;
	
	return;
}

/*******************************************************************************

 $Function:    	ATB_index_GetTable
 
 $Description:	Returns a pointer to the index table specified by the index_type

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index table required.

*******************************************************************************/

SHORT * ATB_index_GetTable(SHORT phonebook_id, T_PB_INDEX index_type)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT *table = NULL;
	
	switch(index_type)
	{
		case INDEX_NAME:
			table = data->name_table;
			break;
		case INDEX_NUMBER:
			table = data->number_table;
			break;
		case INDEX_SEARCH:
			table = data->search_table;
			break;
		default:
			table = GI_pb_GetTable(index_type);
			break;
	}

	return table;
}


/*******************************************************************************

 $Function:    	ATB_index_Find
 
 $Description:	Find the nearest match to the supplied record.  See ATB_pb_Find for
 				more information.

 $Returns:		PB_OK				Action completed OK.
 				PB_BUSY				Failed, phonebook is busy.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist
									(should never happen)

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index_type of the provided record that is to be
								searched for, and the index list that the resulting
								new_log_index pertains to
				record			The record whose index_type is to be matched
				new_log_index	Returns with the logical index of the closest matching
								record
				match			Specifies how good the resulting match was
								(MATCH_NONE, MATCH_START, MATCH_FRAGMENT
								or MATCH_EXACT).
 
*******************************************************************************/

PB_RET ATB_index_Find (SHORT phonebook_id, T_PB_INDEX index_type, T_PB_RECORD *record, SHORT *new_log_index, T_PB_MATCH *match)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	PB_RET result = PB_OK;
	SHORT *table;
	SHORT lower;
	SHORT upper;
	SHORT middle;
	T_PB_RECORD *comp_record;
	T_PB_COMPARE compare;
	UBYTE searching;
	
	/* Ensure that phonebook exists */
	if (!data)
	{
		trace("**ERROR** Phonebook does not exist");
		return PB_BOOKDOESNOTEXIST;
	}

	/* Allocate record for comparison */

	comp_record = ATB_pb_AllocRec(phonebook_id);
	
	/* Make sure we're searching a valid index type */

	if (index_type==INDEX_PHYSICAL || index_type==INDEX_SEARCH)
	{
		GI_pb_Error(phonebook_id, PB_FIND, PB_INDEXINVALID);	
		return PB_INDEXINVALID;
	}
	
	table = ATB_index_GetTable(phonebook_id, index_type);

	/* The initial upper and lower bounds are the bounds of the array */
	lower = 0;
	upper = data->records_used-1;

	searching = TRUE;

	/* If the list is empty, insert at 0 */
	
	if (data->records_used==0)
	{
		middle = 0;
		searching = FALSE;
	}
	
	while (searching)
	{
		/* Compare our record with the middle element of the partition */

		middle = (lower+upper)/2;

		result = ATB_mem_ReadRec(phonebook_id, index_type, middle, comp_record);
		
		if (result!=PB_OK)
			break;
			
		compare = ATB_index_Compare(record, comp_record, index_type);

		switch(compare)
		{
			case COMPARE_IDENTICAL:
				/* We've found a match; stop searching */
				searching = FALSE;
				break;
				
			case COMPARE_FIRSTBEFORE:
				if (middle==lower)
				{
					/* The partition is a single element, so stop here. */
					searching = FALSE;
				}
				else
				{
					/* Our record comes before the middle element;
					 * redefine the partiton */
					upper = middle - 1;
				}
				break;
				
			case COMPARE_FIRSTAFTER:
				if (middle==upper)
				{
				/* The partition is a single element, so stop here.  The record
				 * we need is the record after this one. */
					middle++;
					searching = FALSE;
				}
				else
				{
					/* Our record comes after the middle element;
					 * redefine the partiton */
					lower = middle+1;
				}
				break;
		}
	}	

	/* Store our results */
	
	if (new_log_index)
		*new_log_index = middle;

	if (match)
	{
		ATB_mem_ReadRec(phonebook_id, index_type, middle, comp_record);
		*match = ATB_index_Match(record, comp_record, index_type);
	}

	/* Free allocated record */

	ATB_pb_FreeRec(phonebook_id, comp_record);

	/* Send success/failure info to the GI */
	
	if (result==PB_OK)
		GI_pb_OK(phonebook_id, PB_FIND, NULL);
	else if (result!=PB_EXCT)
		GI_pb_Error(phonebook_id, PB_FIND, result);

	return result;
}


/*******************************************************************************

 $Function:    	ATB_index_GetPhysIndex
 
 $Description:	Returns the physical index corresponding to the given logical index, in
 				the index table specified by the index_type.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier.
				index_type		The index table required.
				log_index		The logical index.

*******************************************************************************/

SHORT ATB_index_GetPhysIndex(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT phys_index;
	SHORT *table;

	switch(index_type)
	{
		case INDEX_PHYSICAL:
			phys_index = log_index;
			break;
		case INDEX_NAME:
		case INDEX_NUMBER:
		case INDEX_SEARCH:
			table = ATB_index_GetTable(phonebook_id, index_type);
			phys_index = table[log_index];
			break;
	}

	return phys_index;
}


/*******************************************************************************

 $Function:    	ATB_index_GetLogIndex
 
 $Description:	Provided with the physical index, finds the logical index of the record
 				in the index table specified.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index table required.
				phys_index		The logical index of the record to find

*******************************************************************************/

SHORT ATB_index_GetLogIndex(SHORT phonebook_id, T_PB_INDEX index_type, SHORT phys_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT *table = ATB_index_GetTable(phonebook_id, index_type);
	SHORT log_index;

	if (index_type==INDEX_PHYSICAL)
	{
		log_index = phys_index;
	}
	else
	{
		for (log_index=0; log_index<data->records_used; log_index++)
		{
			if (table[log_index]==phys_index)
				break;
		}
	}
	
	return log_index;
}


/*******************************************************************************

 $Function:    	ATB_index_AddRec
 
 $Description:	Adds a record to an index list, sorting automatically as required.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index table required.
				phys_index		The physical index of the record to add.
				record			The record to add.
				new_log_index	New logical index of record.

*******************************************************************************/

void ATB_index_AddRec(SHORT phonebook_id, T_PB_INDEX index_type, SHORT phys_index, T_PB_RECORD *record, SHORT *new_log_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT *table = ATB_index_GetTable(phonebook_id, index_type);
	SHORT log_index;
	SHORT found_log_index;

	ATB_index_Find(phonebook_id, index_type, record, &found_log_index, NULL);

	/* Shift end of index table down */

	for (log_index=data->records_used; log_index>found_log_index; log_index--)
	{
		table[log_index] = table[log_index-1];
	}
	
	table[found_log_index] = phys_index;

	if (new_log_index)
		*new_log_index = found_log_index;
	
	return;
}

/*******************************************************************************

 $Function:    	ATB_index_RemoveRec
 
 $Description:	Removes a record reference from an index list, sorting the index list
 				appropriately.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index table required.
				log_index		The logical index of the record to remove

*******************************************************************************/

void ATB_index_RemoveRec(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT *table = ATB_index_GetTable(phonebook_id, index_type);
	SHORT table_index;

	for (table_index = log_index+1; table_index<data->records_used; table_index++)
	{
		table[table_index-1] = table[table_index];
	}

	return;
}


/*******************************************************************************

 $Function:    	ATB_index_Match
 
 $Description:	This function attempts a match between two records based on the
 				index_type specified in index_type.  It returns a value indicating
 				whether there is no match (MATCH_NONE), a partial match
 				(MAX_START), or an exact match (MATCH_EXACT).
				The function GI_pb_Match is called first, to check whether there is
				any user-specific matching required.  If not, then ATB_alpha_Match
				is called for an alpha tag, or ATB_num_Match for a phone number.
				For any other index_type, MATCH_NONE is returned as default.

 $Returns:		MATCH_EXACT, MATCH_START or MATCH_NONE

 $Arguments:	record1		The first record to be compared
				record2		The second record to be compared
				index_type	Indicator of the index_type which is to be matched

*******************************************************************************/

T_PB_MATCH ATB_index_Match(T_PB_RECORD *record1, T_PB_RECORD *record2, T_PB_INDEX index_type)
{
	T_PB_MATCH match;

	match = GI_pb_Match(record1, record2, index_type);

	if (match==MATCH_DEFAULT)
	{
		switch(index_type)
		{
			case INDEX_NAME:
				match = ATB_alpha_Match(&record1->alpha, &record2->alpha);
				break;
			case INDEX_NUMBER:
				match = ATB_num_Match(record1->number, record2->number);
				break;
		}
	}

	return match;
}



/*******************************************************************************

 $Function:    	ATB_index_Compare
 
 $Description:	This function compares two records based on the index_type specified in
 				index_type.  It returns a value indicating whether the first record should
 				come before or after the second.
				The function GI_pb_Compare is called first, to check whether there is
				any user-specific sorting required.  If not, then ATB_alpha_Compare is
				called for an alpha tag, or ATB_num_Compare for a phone number.
				For any other index_type, COMPARE_FIRSTBEFORE is returned as default.

 $Returns:		COMPARE_IDENTICAL		The two records are identical
				COMPARE_FIRSTBEFORE		The first record should come before the second record
				COMPARE_FIRSTAFTER		The first record should come after the second record

 $Arguments:	record1		The first record to be compared
				record2		The second record to be compared
				index_type	Indicator of the index_type which is to be compared

*******************************************************************************/

T_PB_COMPARE ATB_index_Compare(T_PB_RECORD *record1, T_PB_RECORD *record2, T_PB_INDEX index_type)
{
	T_PB_COMPARE compare;
	
	compare = GI_pb_Compare(record1, record2, index_type);

	if (compare==COMPARE_DEFAULT)
	{
		switch(index_type)
		{
			case INDEX_NAME:
				compare = ATB_alpha_Compare(&record1->alpha, &record2->alpha);
				break;
			case INDEX_NUMBER:
				compare = ATB_num_Compare(record1->number, record2->number);
				break;
		}
	}

	return compare;
}


/*******************************************************************************

 $Function:    	ATB_alpha_Compare
 
 $Description:	This function compares two alpha strings on the basis of an ascending
 				alphanumeric latin list, and specifies which record would come first.


 $Returns:		COMPARE_FIRSTBEFORE		The first record should come before the second record
				COMPARE_FIRSTAFTER		The first record should come after the second record

 $Arguments:	alpha1				The first alpha tag
				alpha2				The second alpha tag

*******************************************************************************/

T_PB_COMPARE ATB_alpha_Compare(T_PB_ALPHA *alpha1, T_PB_ALPHA *alpha2)
{
	T_PB_COMPARE compare;
	SHORT charIndex;
	USHORT length1 = alpha1->length;
	USHORT length2 = alpha2->length;
	USHORT char1;
	USHORT char2;

	compare = COMPARE_IDENTICAL;	/* Default */

	/* Special case, length of first string is 0 */
	
	if (length1==0)
		compare = COMPARE_FIRSTBEFORE;
	
	for (charIndex = 0; charIndex < length1; charIndex++)
	{
		if (charIndex==length2)	/* E.g. "Johnson" and "John" */
		{
			compare = COMPARE_FIRSTAFTER;
			break;
		}

		char1 = alpha1->data[charIndex];
		if (char1>=(SHORT)'A' && char1<=(SHORT)'Z')
			char1 += (SHORT)('a'-'A');
		char2 = alpha2->data[charIndex];
		if (char2>=(SHORT)'A' && char2<=(SHORT)'Z')
			char2 += (SHORT)('a'-'A');
		
		if (char1 < char2)
		{
			compare = COMPARE_FIRSTBEFORE;
			break;
		}
		if (char1 > char2)
		{
			compare = COMPARE_FIRSTAFTER;
			break;
		}
		if (charIndex==length1-1 && length2>length1)  /*E.g. "John" and "Johnson" */
		{
			compare = COMPARE_FIRSTBEFORE;
			break;
		}
	}

	return compare;
}


/*******************************************************************************

 $Function:    	ATB_alpha_Match
 
 $Description:	This function matches two alpha strings on the basis of a latin string
 				matched from the start, and specifies how they match.

 $Returns:		MATCH_NONE			The strings do not match
				MATCH_START			The first string matches the start of the second string
				MATCH_EXACT			The two strings match exactly

 $Arguments:	alpha1				The first alpha tag
				alpha2				The second alpha tag

*******************************************************************************/

T_PB_COMPARE ATB_alpha_Match(T_PB_ALPHA *alpha1, T_PB_ALPHA *alpha2)
{
	T_PB_MATCH match;
	SHORT charIndex;
	USHORT length1 = alpha1->length;
	USHORT length2 = alpha2->length;
	SHORT offset;
	UBYTE searching;
	UBYTE some_match;
	USHORT char1, char2;

	match = MATCH_EXACT;	/* Default */
	searching = TRUE;
	some_match = FALSE;
	offset = 0;
	
	do
	{
		for (charIndex = 0; charIndex < length1; charIndex++)
		{

			if (charIndex==(length2-offset))	/* E.g. "Johnson" and "John" */
			{
				searching = FALSE;
				break;							/* No match, will exit do/while */
			}

			char1 = alpha1->data[charIndex];
			if (char1>=(SHORT)'A' && char1<=(SHORT)'Z')
				char1 += (SHORT)('a'-'A');
			char2 = alpha2->data[charIndex+offset];
			if (char2>=(SHORT)'A' && char2<=(SHORT)'Z')
				char2 += (SHORT)('a'-'A');
		
			if (char1 != char2)
			{
				some_match = FALSE;				/* Any fragment so far identified does not fit */
				break;							/* No match, keep looking */
			}

			some_match = TRUE;

			if (charIndex==length1-1 && length2>length1)  /*E.g. "John" and "Johnson" */
			{
				if (offset==0)
					match = MATCH_START;
				else
					match = MATCH_FRAGMENT;
				break;
			}
		}

		if (some_match==TRUE)
		{
			searching = FALSE;
		}
		else
		{
			offset++;

			/* If the fragment won't fit, don't keep looking */
			if ((offset+length1)>length2)
			{
				match=MATCH_NONE;
				searching = FALSE;
			}
		}
	}while (searching);
	
	return match;
}


/*******************************************************************************

 $Function:    	ATB_num_Digit
 
 $Description:	Extracts the four-bit digit from a BCD number

 $Returns:		The four bit digit

 $Arguments:	num				The BCD number
 				numIndex		The position in the BCD number to look

*******************************************************************************/

UBYTE ATB_num_Digit(UBYTE *num, SHORT numIndex)
{
	UBYTE digit;
	UBYTE shift = (numIndex&0x1)*0x4;
		
	digit = num[numIndex>>1];		/* Each BCD digit takes up half a byte */
	digit &= (0xF<<shift);			/* Isolate the digit */
	digit >>= shift;				/* Shift it so it has a value 0x0 - 0xF */
	
	return digit;
}


/*******************************************************************************

 $Function:    	ATB_num_Length
 
 $Description:	Returns the length of a BCD number in digits

 $Returns:		Length of BCD number

 $Arguments:	num				The BCD number

*******************************************************************************/

SHORT ATB_num_Length(UBYTE *num)
{
	SHORT length;
	
	for (length = 0; ATB_num_Digit(num, length)!=0xF; length++)
	{
	}

	return length;
}


/*******************************************************************************

 $Function:    	ATB_num_Compare
 
 $Description:	This function compares two phone numbers by the standard comparison
 				method (ascending numeric, from the end of the number) and specifies
 				which record would come first if sorted in this method.

 $Returns:		COMPARE_FIRSTBEFORE		The first record should come before the second record
				COMPARE_FIRSTAFTER		The first record should come after the second record

 $Arguments:	num1				The first number
				num2				The second number

*******************************************************************************/

T_PB_COMPARE ATB_num_Compare(UBYTE *num1, UBYTE *num2)
{
	T_PB_COMPARE compare;
	SHORT charIndex;
	SHORT length1 = ATB_num_Length(num1);
	SHORT length2 = ATB_num_Length(num2);
	UBYTE digit1;
	UBYTE digit2;

	compare = COMPARE_IDENTICAL;	/* Default */
	
	for (charIndex = 0; charIndex < length1; charIndex++)
	{
		if (charIndex==length2)	/* E.g. "123456" and "1234" */
		{
			compare = COMPARE_FIRSTAFTER;
			break;
		}

		digit1 = ATB_num_Digit(num1, (SHORT)(length1-charIndex-1));
		digit2 = ATB_num_Digit(num2, (SHORT)(length2-charIndex-1));
		
		if (digit1 < digit2)
		{
			compare = COMPARE_FIRSTBEFORE;
			break;
		}
		if (digit1 > digit2)
		{
			compare = COMPARE_FIRSTAFTER;
			break;
		}
		if (charIndex==length1-1 && length2>length1)  /*E.g. "1234" and "123456" */
		{
			compare = COMPARE_FIRSTBEFORE;
			break;
		}
	}

	return compare;
}

/*******************************************************************************

 $Function:    	ATB_num_Match
 
 $Description:	This function matches phone numbers, from the end backwards, and
 				specifies how they match.

 $Returns:		MATCH_NONE			The numbers do not match
				MATCH_START			The first number matches the end of the second number
				MATCH_EXACT			The two numbers match exactly

 $Arguments:	num1				The first number
				num2				The second number

*******************************************************************************/

T_PB_MATCH ATB_num_Match(UBYTE *num1, UBYTE *num2)
{
	T_PB_MATCH match;
	SHORT charIndex;
	SHORT length1 = ATB_num_Length(num1);
	SHORT length2 = ATB_num_Length(num2);
	SHORT offset;
	UBYTE searching;
	UBYTE some_match;
	UBYTE digit1;
	UBYTE digit2;

	match = MATCH_EXACT;	/* Default */
	searching = TRUE;
	some_match = FALSE;
	offset = 0;
	
	do
	{
		for (charIndex = 0; charIndex < length1; charIndex++)
		{
			if (charIndex==(length2-offset))	/* E.g. "12345" and "123" */
			{
				searching = FALSE;
				break;							/* No match, will exit do/while */
			}

			digit1 = ATB_num_Digit(num1, (SHORT)(length1-charIndex-1));
			digit2 = ATB_num_Digit(num2, (SHORT)(length2-charIndex-1));
		
			if (digit1 != digit2)
			{
				some_match = FALSE;				/* Any fragment so far identified does not fit */
				break;							/* No match, keep looking */
			}

			some_match = TRUE;

			if (charIndex==length1-1 && length2>length1)  /*E.g. "123" and "12345" */
			{
				if (offset==0)
					match = MATCH_START;
				else
					match = MATCH_FRAGMENT;
				break;
			}
		}

		if (some_match==TRUE)
		{
			searching = FALSE;
		}
		else
		{
			offset++;

			/* If the fragment won't fit, don't keep looking */
			if ((offset+length1)>length2)
			{
				match=MATCH_NONE;
				searching = FALSE;
			}
		}
	} while (searching);
	
	return match;
}


/*******************************************************************************

 $Function:    	ATB_mem_UpdateCache
 
 $Description:	Updates the cache with the record provided.  If the cache is full, the
 				least used record is overwritten.

 $Returns:		None.

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index table required.
				phys_index		The physical index of the record to add.
				record			The record to add.

*******************************************************************************/

void ATB_mem_UpdateCache(SHORT phonebook_id, SHORT phys_index, T_PB_RECORD *record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT cacheIndex;
	SHORT leastUsed;
	SHORT	leastIndex;
	
	/* First check if record is already in RAM */
	
	if (data->cache[phys_index] != NULL)
	{
		/* Copy the record in case it has changed */

		ATB_pb_CopyRec(phonebook_id, data->cache[phys_index], record);
		
		if (data->in_memory[phys_index]<0xFFFF)
			data->in_memory[phys_index]++;
		return;
	}

	/* If the cache is full, find the least accessed record */

	if (data->cache_size==data->cache_max)
	{
		leastIndex = 0;
		leastUsed = 255;
			
		for (cacheIndex=0; cacheIndex<data->records_max; cacheIndex++)
		{
			if (data->cache[cacheIndex]!=NULL && data->in_memory[cacheIndex] < leastUsed)
			{
				leastUsed = data->in_memory[cacheIndex];
				leastIndex = cacheIndex;
			}
		}

		/* Give the new record the memory allocated for the least used record */
		
		data->cache[phys_index] = data->cache[leastIndex];
		data->cache[leastIndex] = NULL;
		data->in_memory[leastIndex] = 0;
	}
	
	/* If the cache is not full, allocate memory for record */
	
	else
	{
		data->cache[phys_index] = ATB_pb_AllocRec(phonebook_id);
		data->cache_size++;
	}

	/* Copy the record into cache */

	ATB_pb_CopyRec(phonebook_id, data->cache[phys_index], record);
	data->in_memory[phys_index] = 1;

	return;
}

/*******************************************************************************

 $Function:    	ATB_mem_ReadRec
 
 $Description:	Reads a record from the logical position index.  Information is read from
 				cache if it is present in memory, otherwise it will be read from file.

 				The record information will be returned in the data structure pointed to
 				by record.  The T_PB_RECORD structure must be allocated by the caller.

 $Returns:		PB_OK				Action completed OK.
				PB_FILEREADFAIL		File read encountered an error
				PB_RECDOESNOTEXIST	Tried to access a record that does not exist

 $Arguments:	phonebook_id	The phonebook identifier
				index_type		The index list from which the record is to be read
				log_index		Logical index in the index list of the record to read
				record			Structure in which to store record data
 
*******************************************************************************/

PB_RET	ATB_mem_ReadRec(SHORT phonebook_id, T_PB_INDEX index_type, SHORT log_index, T_PB_RECORD *record)
{
	T_PB_DATA *data = ATB_hnd_GetPbData(phonebook_id);
	SHORT phys_index;
	PB_RET result;
	
	/* Check that record exists */

	if (log_index<0
		|| (log_index>=data->records_used && (index_type==INDEX_NAME || index_type==INDEX_NUMBER))
		|| (index_type==INDEX_SEARCH && log_index>data->search_results)
		|| (index_type==INDEX_PHYSICAL && log_index>data->records_max))
	{
		trace("*** Error - record does not exist ***");
		return PB_RECDOESNOTEXIST;
	}
	
	/* Get the corresponding physical record index */
	
	phys_index = ATB_index_GetPhysIndex(phonebook_id, index_type, log_index);

	/* If the record is in the cache, simply copy it.  Otherwise, call FS to read the record. */
	
	if (data->in_memory[phys_index]>0)
	{
		ATB_pb_CopyRec(phonebook_id, record, data->cache[phys_index]);
		result = PB_OK;
	}
	else
	{
		/* NB - assumes blocking read operation.  If the read operation is blocking,
		 * the cache must be large enough to store all the phonebook entries. */
		result = FS_pb_ReadRec(phonebook_id, phys_index, record);
		ATB_mem_UpdateCache(phonebook_id, phys_index, record);
	}

	return result;
}


/*******************************************************************************

 $Function:    	ATB_hnd_GetPbData
 
 $Description:	Returns phonebook data handle

 $Returns:		The phonebook data handle

 $Arguments:	phonebook_id	The phonebook identifier
 
*******************************************************************************/

T_PB_DATA*	ATB_hnd_GetPbData(SHORT phonebook_id)
{
	return phonebook[phonebook_id];
}


/*******************************************************************************

 $Function:    	ATB_hnd_SetPbData
 
 $Description:	Sets phonebook data handle

 $Returns:		None

 $Arguments:	phonebook_id	The phonebook identifier
 				data			The phonebook data
 
*******************************************************************************/

void ATB_hnd_SetPbData(SHORT phonebook_id, T_PB_DATA *data)
{
	phonebook[phonebook_id] = data;

	return;
}

