// File: "TclAB.c"
//                        Created: 2003-11-26 12:54:15
//              Last modification: 2009-09-04 09:30:30
// Author: Bernard Desgraupes
// e-mail: <bdesgraupes@users.sourceforge.net>
// 
// Load this extension in the Tcl interpreter with the following instruction:
//         package require addressbook
// 
// (c) Copyright: Bernard Desgraupes, 2003-2009
//         All rights reserved.
// This software is free software with BSD licence.
// Version history: see the Changes.Log file.


#include "TclAB.h"
#include "TclABVersion.h"
#include "TclABTasks.h"
#include "TclABUtils.h"

/*
 * Prototypes for static procedures
 */
static int	TclABCmd_AddressBook(ClientData clientData,  Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);

static int	TclABCmd_Add(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Changed(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Count(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Create(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Delete(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Export(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_GetMe(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Groups(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Identifier(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Image(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Import(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Label(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Parents(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Persons(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Property(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Record(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Remove(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Save(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Search(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Set(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_SetMe(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);
static int	TclABCmd_Type(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]);


// The init function called when the package is loaded in the Tcl interpreter
int Addressbook_Init(Tcl_Interp *interp);


/*
 *----------------------------------------------------------------------
 *
 * Addressbook_Init --
 *
 *	This procedure is invoked when the package is loaded.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int Addressbook_Init(Tcl_Interp *interp) {	
#ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
		return TCL_ERROR;
    }
#endif

	// Register the [addressbook] command
    Tcl_CreateObjCommand(interp, "addressbook", TclABCmd_AddressBook,
			     (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
				 
    // Declare the addressbook package
    if (Tcl_PkgProvide(interp, "addressbook", TCLADDRESSBOOK_VERSION_STRING) != TCL_OK) {
		return TCL_ERROR;
    }       
		
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_AddressBook --
 *
 *	This procedure is invoked to process the "addressbook" Tcl command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_AddressBook(
    ClientData clientData,		/* Not used. */
    Tcl_Interp *interp,			/* Current interpreter. */
    int objc,					/* Number of arguments. */
    Tcl_Obj *CONST objv[])		/* Argument values. */
{
	int index, result;
	
	static CONST char *switches[] = {
		"add", "changed", "count", "create", "delete", "export", 
		"getme", "groups", "identifier", "image", "import", "label", 
		"parents", "persons", "property", "record", "remove", "save", 
		"search", "set", "setme", "type", (char *) NULL 
	};
	
	enum {
		TCLAB_ADD, TCLAB_CHANGED, TCLAB_COUNT, TCLAB_CREATE, TCLAB_DELETE, TCLAB_EXPORT, 
		TCLAB_GETME, TCLAB_GROUPS, TCLAB_IDENTIFIER, TCLAB_IMAGE, TCLAB_IMPORT, TCLAB_LABEL, 
		TCLAB_PARENTS, TCLAB_PERSONS, TCLAB_PROPERTY, TCLAB_RECORD, TCLAB_REMOVE, TCLAB_SAVE, 
		TCLAB_SEARCH, TCLAB_SET, TCLAB_SETME, TCLAB_TYPE 
	};
		
	if (objc < 2) {
		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?arg ...?");
		return TCL_ERROR;
	}
	
	if (Tcl_GetIndexFromObj(interp, objv[1], switches, "subcommand", 0, &index)
		!= TCL_OK) {
		return TCL_ERROR;
	}
	
	switch (index) {
		case TCLAB_ADD:
		result = TclABCmd_Add(clientData, interp, objc, objv);
		break;
		
		case TCLAB_CHANGED:
		result = TclABCmd_Changed(clientData, interp, objc, objv);
		break;
		
		case TCLAB_COUNT:
		result = TclABCmd_Count(clientData, interp, objc, objv);
		break;
		
		case TCLAB_CREATE:
		result = TclABCmd_Create(clientData, interp, objc, objv);
		break;
		
		case TCLAB_DELETE:
		result = TclABCmd_Delete(clientData, interp, objc, objv);
		break;
		
		case TCLAB_EXPORT:			
		result = TclABCmd_Export(clientData, interp, objc, objv);
		break;
		
		case TCLAB_GETME:			
		result = TclABCmd_GetMe(clientData, interp, objc, objv);
		break;
		
		case TCLAB_GROUPS:
		result = TclABCmd_Groups(clientData, interp, objc, objv);
		break;
		
		case TCLAB_IDENTIFIER:			
		result = TclABCmd_Identifier(clientData, interp, objc, objv);
		break;
		
		case TCLAB_IMAGE:			
		result = TclABCmd_Image(clientData, interp, objc, objv);
		break;
		
		case TCLAB_IMPORT:			
		result = TclABCmd_Import(clientData, interp, objc, objv);
		break;
		
		case TCLAB_LABEL:			
		result = TclABCmd_Label(clientData, interp, objc, objv);
		break;
		
		case TCLAB_PARENTS:			
		result = TclABCmd_Parents(clientData, interp, objc, objv);
		break;
		
		case TCLAB_PERSONS:			
		result = TclABCmd_Persons(clientData, interp, objc, objv);
		break;
		
		case TCLAB_PROPERTY:			
		result = TclABCmd_Property(clientData, interp, objc, objv);
		break;
		
		case TCLAB_RECORD:			
		result = TclABCmd_Record(clientData, interp, objc, objv);
		break;
		
		case TCLAB_REMOVE:
		result = TclABCmd_Remove(clientData, interp, objc, objv);
		break;
		
		case TCLAB_SAVE:			
		result = TclABCmd_Save(clientData, interp, objc, objv);
		break;
		
		case TCLAB_SEARCH:			
		result = TclABCmd_Search(clientData, interp, objc, objv);
		break;
		
		case TCLAB_SET:			
		result = TclABCmd_Set(clientData, interp, objc, objv);
		break;
		
		case TCLAB_SETME:			
		result = TclABCmd_SetMe(clientData, interp, objc, objv);
		break;
		
		case TCLAB_TYPE:			
		result = TclABCmd_Type(clientData, interp, objc, objv);
		break;
		
		default:
		panic("Tcl_GetIndexFromObj returned unrecognized subcommand");
		result = TCL_ERROR;	/* Should never be reached. */
	}
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Add --
 *
 *	This procedure is invoked to process the [addressbook add] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook add groupID recordID ?recordID ...?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
	
int
TclABCmd_Add(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int					i, kind, result = TCL_OK, success;
	CFStringRef			strRef;
	ABAddressBookRef 	ab;
	ABRecordRef			objRecord, groupRecord;
	
	if (objc < 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "groupID recordID ?recordID ...?");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if second argument is a group and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[2], "second", rec_group, &groupRecord) ) {
		return TCL_ERROR;
	} 
	
	for ( i = 3; i < objc; i++) {
		
		Tcl_IncrRefCount(objv[i]);
		// Next argument should be a person or a group ID
		strRef = TclAB_TclObjToCFString(objv[i]);		
		
		// Find the record corresponding to the UID
		success = TclAB_RecordFromUID(interp, ab, strRef, &objRecord);
		CFRelease(strRef);
		if (!success) {
			Tcl_DecrRefCount(objv[i]);
			result = TCL_ERROR;
			break;
		} 
		
		// Is objRecord a person or a group?
		kind = TclAB_TypeFromRecord(ab, objRecord);
		switch (kind) {
			case rec_person:
			if (!ABGroupAddMember(groupRecord, objRecord)) {
				Tcl_AppendResult(interp, "can't add member ", Tcl_GetString(objv[i]), " to group", (char *) NULL);
				result = TCL_ERROR;
			} 
			break;
			
			case rec_group:
			if (!ABGroupAddGroup(groupRecord, objRecord)) {
				Tcl_AppendResult(interp, "can't add subgroup ", Tcl_GetString(objv[i]), " to group", (char *) NULL);
				result = TCL_ERROR;
			} 
			break;
			
			default:
			Tcl_AppendResult(interp, "unknown type for recordID ", Tcl_GetString(objv[i]), (char *) NULL);
			result = TCL_ERROR;
			break;
		}
		Tcl_DecrRefCount(objv[i]);
		CFRelease(objRecord);
		if (result == TCL_ERROR) {
			break;
		} 
	}
	
	CFRelease(groupRecord);
	return result; 
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Changed --
 *
 *	This procedure is invoked to process the [addressbook changed] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook changed
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Changed(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int				result;
	ABAddressBookRef ab;
	
	if (objc != 2) {
		Tcl_WrongNumArgs(interp, 1, objv, "changed");
		return TCL_ERROR;
	}
	
	// Get the address book - there is only one
	ab = ABGetSharedAddressBook();
	
	result = ABHasUnsavedChanges(ab);
	Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result));
	
	return TCL_OK; 
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Count --
 *
 *	This procedure is invoked to process the [addressbook count] Tcl command.
 *	See the user documentation for details on what it does.
 *	
 * Syntax:
 *  addressbook count (-groups|-persons) ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Count(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFArrayRef			theItems = NULL;
	CFIndex 			theCount;
	int					index, rectype;
	ABRecordRef			inGroupRef;
	
	static CONST char *countSwitches[] = {
		"-groups", "-persons", "-ingroup", (char *) NULL
	};
	
	enum {
		TCLAB_COUNT_GROUPS, TCLAB_COUNT_PERSONS, TCLAB_COUNT_INGROUP
	};
	
	if (objc != 3 && objc != 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "(-groups|-persons) ?-ingroup groupID?");
		return TCL_ERROR;
	}
	
	if (Tcl_GetIndexFromObj(interp, objv[2], countSwitches,
							"option", 0, &rectype) != TCL_OK) {
		return TCL_ERROR;
	}
			
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	if (objc == 5) {
		if (Tcl_GetIndexFromObj(interp, objv[objc-2], countSwitches, "option", 0, &index) != TCL_OK) {
			return TCL_ERROR;
		} else {
			if ( !TclAB_CheckItem(interp, ab, objv[objc-1], "-ingroup", rec_group, &inGroupRef) ) {
				return TCL_ERROR;
			} 
			if (rectype == TCLAB_COUNT_GROUPS) {
				theItems = ABGroupCopyArrayOfAllSubgroups(inGroupRef);
			} else if (rectype == TCLAB_COUNT_PERSONS) {
				theItems = ABGroupCopyArrayOfAllMembers(inGroupRef);
			} 
			CFRelease(inGroupRef);
		}
	} else {
		if (rectype == TCLAB_COUNT_GROUPS) {
			theItems = ABCopyArrayOfAllGroups(ab);
		} else if (rectype == TCLAB_COUNT_PERSONS) {
			theItems = ABCopyArrayOfAllPeople(ab);
		} 
	}
	
	if (!theItems) {
		Tcl_SetResult(interp, "can't get list of groups", TCL_STATIC);
		return TCL_ERROR;
	} else {
		theCount = CFArrayGetCount(theItems);
		CFRelease(theItems);
		Tcl_SetObjResult(interp, Tcl_NewIntObj(theCount) );
	}
	
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Create --
 *
 *	This procedure is invoked to process the [addressbook create] Tcl command.
 *	See the user documentation for details on what it does. It can be used 
 *	either to add a new group or person record to the database, or to add
 *	a new property.
 *
 * Syntax:
 *  addressbook create (group|person) name ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Create(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int		index;
	
	static CONST char *createSubcmds[] = {
		"group", "person", (char *) NULL
	};
	
	enum {
		TCLAB_ADD_GROUP, TCLAB_ADD_PERSON
	};
	
	if (objc != 4 && objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "(group|person) name ?-ingroup groupID?");
		return TCL_ERROR;
	}

	if (Tcl_GetIndexFromObj(interp, objv[2], createSubcmds, "type", 0, &index) != TCL_OK) {
		return TCL_ERROR;
	} 
	
	return TclAB_CreateRecord(interp, objc, objv, index);
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Delete --
 *
 *	This procedure is invoked to process the [addressbook delete] Tcl command.
 *	See the user documentation for details on what it does.
 *	Remove recordID from the database.
 *
 * Syntax:
 *  addressbook delete recordID
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Delete(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord;
	CFStringRef			strRef;
	int					result = TCL_OK, success;

	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID");
		return TCL_ERROR;
	}
		
	strRef = TclAB_TclObjToCFString(objv[2]);

	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Find the record corresponding to the UID
	success = TclAB_RecordFromUID(interp, ab, strRef, &theRecord);
	CFRelease(strRef);
	if (!success) {return TCL_ERROR;} 

	if (!ABRemoveRecord(ab, theRecord)) {
		Tcl_SetResult(interp, "can't remove record from database", TCL_STATIC);
		result = TCL_ERROR;
	} 
	
	CFRelease(theRecord);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Export --
 *
 *	This procedure is invoked to process the [addressbook export] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook export personID
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Export(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord;
	CFDataRef			vCard;
	Tcl_Obj *			objPtr;
	
	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if second argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[2], "second", rec_person, &theRecord) ) {
		return TCL_ERROR;
	} 
	
	vCard = ABPersonCopyVCardRepresentation(theRecord);
	CFRelease(theRecord);
	if (vCard) {
		objPtr = Tcl_NewByteArrayObj( CFDataGetBytePtr(vCard), CFDataGetLength(vCard) );
		Tcl_SetObjResult(interp, objPtr);
		CFRelease(vCard);
	} else {
		Tcl_SetResult(interp, "can't get vCard representation", TCL_STATIC);
		return TCL_ERROR;
	}
	
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_GetMe --
 *
 *	This procedure is invoked to process the [addressbook getme] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook getme varname
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_GetMe(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABPersonRef			myRecord;
	int					result = TCL_OK;
	
	static CONST char *getmeSwitches[] = {
		"-id", (char *) NULL
	};
		
	if (objc != 2 && objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "?-id?");
		return TCL_ERROR;
	}
	
	Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	myRecord = ABGetMe(ab);
	
	if (myRecord) {
		CFRetain(myRecord);
		if (objc == 3) {
			CFStringRef	uniqueId;
			int			index;
			
			if (Tcl_GetIndexFromObj(interp, objv[2], getmeSwitches, "option", 0, &index) != TCL_OK) {
				result = TCL_ERROR;
			} else {
				uniqueId = ABRecordCopyUniqueId(myRecord);
				if (uniqueId) {
					Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(uniqueId));
					CFRelease(uniqueId);
				} else {
					Tcl_SetResult(interp, "can't get ID", TCL_STATIC);
					result = TCL_ERROR;
				}
			}
		} else {
			// Store the record's data in the result object
			TclAB_ReturnRecord(interp, ab, myRecord, rec_person);
		}
		CFRelease(myRecord);
	} else {
		Tcl_SetResult(interp, "can't get 'Me' record", TCL_STATIC);
		result = TCL_ERROR;
	}
	
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Groups --
 *
 *	This procedure is invoked to process the [addressbook groups] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook groups ?-ids? ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Groups(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	return TclAB_ListItems(interp, objc, objv, rec_group);
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Identifier --
 *
 *	This procedure is invoked to process the [addressbook identifier] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier count personID propName
 *  addressbook identifier get personID propName index
 *  addressbook identifier index personID propName identifier
 *  addressbook identifier primary personID propName ?identifier?
 *  addressbook identifier set groupID personID propName ?identifier?
 *  
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Identifier(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int		result = TCL_ERROR, index;
	
	static CONST char *identSubcmdSwitches[] = {
		"count", "get", "index", "primary", "set", (char *) NULL
	};
	
	enum {
		TCLAB_IDENTIFIER_COUNT, TCLAB_IDENTIFIER_GET, TCLAB_IDENTIFIER_INDEX, 
		TCLAB_IDENTIFIER_PRIMARY, TCLAB_IDENTIFIER_SET
	};
		
	if (objc < 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "(count|get|index|primary|set) ?args?");
		return TCL_ERROR;
	}
	
	if (Tcl_GetIndexFromObj(interp, objv[2], identSubcmdSwitches,
							"subcommand", 0, &index) != TCL_OK) {
		return TCL_ERROR;
	}
		
	switch (index) {
		case TCLAB_IDENTIFIER_COUNT:		
		result = TclAB_IdentifierCount(interp, objc, objv);
		break;
		
		case TCLAB_IDENTIFIER_GET:		
		result = TclAB_IdentifierGet(interp, objc, objv);
		break;
		
		case TCLAB_IDENTIFIER_INDEX:		
		result = TclAB_IdentifierIndex(interp, objc, objv);
		break;
		
		case TCLAB_IDENTIFIER_PRIMARY:		
		result = TclAB_IdentifierPrimary(interp, objc, objv);
		break;
		
		case TCLAB_IDENTIFIER_SET:		
		result = TclAB_IdentifierSet(interp, objc, objv);
		break;
	}
	
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Image --
 *
 *	This procedure is invoked to process the [addressbook image] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook image personID ?imageData?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Image(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord;
	CFDataRef			imageRef = NULL;
	int					length, result = TCL_OK;
	Tcl_Obj *			objPtr;
	unsigned char *		imageBytes;
	
    Tcl_ResetResult(interp);
	
	if (objc != 3 && objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();

	// Check if second argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[2], "personID", rec_person, &theRecord) ) {
		return TCL_ERROR;
	} 
	
	if (objc == 4) {
		imageBytes = Tcl_GetByteArrayFromObj(objv[3], &length);
		imageRef = CFDataCreate(NULL, imageBytes, length);
		if (!imageRef) {
			Tcl_SetResult(interp, "CFDataCreate error", TCL_STATIC);
			result = TCL_ERROR;			
		} else {
			if (!ABPersonSetImageData(theRecord, imageRef)) {
				Tcl_SetResult(interp, "can't set image data", TCL_STATIC);
				result = TCL_ERROR;
			} 
		}
	} else {
		imageRef = ABPersonCopyImageData(theRecord);
		if (!imageRef) {
			Tcl_SetResult(interp, "can't get image data", TCL_STATIC);
			result = TCL_ERROR;
		} else {
			objPtr = Tcl_NewByteArrayObj( CFDataGetBytePtr(imageRef), CFDataGetLength(imageRef) );
			Tcl_SetObjResult(interp, objPtr);
		}
	}
	
	if (imageRef) CFRelease(imageRef);
	CFRelease(theRecord);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Import --
 *
 *	This procedure is invoked to process the [addressbook import] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook import vCardData
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
	
int
TclABCmd_Import(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{

	ABAddressBookRef	ab;
	ABPersonRef			importedRecord, newRecord;
	CFStringRef			theValue, theProperty, uidRef;
	CFArrayRef			allProps;
	CFDataRef			vCard;
	CFIndex 			theCount;
	int					i, length, result = TCL_OK;
	unsigned char *		theBytes;

	Tcl_ResetResult(interp);
	
	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "VCardData");
		return TCL_ERROR;
	}
	
	theBytes = Tcl_GetByteArrayFromObj(objv[2], &length);	

	vCard = CFDataCreate(NULL, theBytes, length);
	if (!vCard) {
		Tcl_SetResult(interp, "CFDataCreate error", TCL_STATIC);
		result = TCL_ERROR;			
	} else {
		// Import the VCard
		importedRecord = ABPersonCreateWithVCardRepresentation(vCard);
		CFRelease(vCard);
		// Adding the imported record directly to the database causes a 
		// crash. So we create a new record and copy the imported property/value
		// pairs to the new record.
		newRecord = ABPersonCreate();
		if (!importedRecord || !newRecord) {
			Tcl_SetResult(interp, "can't create new record with VCard data", TCL_STATIC);
			result = TCL_ERROR;
		} else {
			// Get the address book
			ab = ABGetSharedAddressBook();
			// Loop over all the properties and copy them the the new record
			allProps = ABCopyArrayOfPropertiesForRecordType(ab, kABPersonRecordType);
			if (!allProps) {
				Tcl_SetResult(interp, "can't get list of properties", TCL_STATIC);
				result = TCL_ERROR;
			} else {
				theCount = CFArrayGetCount(allProps);
				for (i = 0; i < theCount; i++) {
					// Get the property at index i
					theProperty = CFArrayGetValueAtIndex(allProps, i);
					if (theProperty) {
						theValue = ABRecordCopyValue(importedRecord, theProperty);
						if (!theValue) {
							continue;
						} 
						ABRecordSetValue(newRecord, theProperty, theValue);
						CFRelease(theValue);
					}
				}
				CFRelease(allProps);
	
				// Also copy the image if there is one
				 {
					 CFDataRef imageRef = ABPersonCopyImageData(importedRecord);
					 if (imageRef) {
						 ABPersonSetImageData(newRecord, imageRef);
						 CFRelease(imageRef);
					 } 
				 }
				 
				// Add the new record to the Address Book. Don't save here: leave 
				// this to the Tcl level.
				if ( ABAddRecord(ab, newRecord) ) {
					// Retrieve the UID for the newly created record
					uidRef = ABRecordCopyUniqueId(newRecord);	
					if (uidRef) {
						Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(uidRef));
						CFRelease(uidRef);
					} 
				} else {
					Tcl_SetResult(interp, "can't add record to the Address Book", TCL_STATIC);
					result = TCL_ERROR;
				}
			}
		}
		if (importedRecord) CFRelease(importedRecord);
		if (newRecord) CFRelease(newRecord);
	}
		
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Label --
 *
 *	This procedure is invoked to process the [addressbook label] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook label personID propName index ?value?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Label(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef = NULL, newvalueRef = NULL;
	CFTypeRef			multivalueRef;
	ABRecordRef			theRecord;
	int					index, kind, result = TCL_OK;
	
	if (objc != 5 && objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID propName index ?value?");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
		
	// Check if second argument is a person and get the corresponding record

	if ( !TclAB_CheckItem(interp, ab, objv[2], "second", rec_undefined, &theRecord) ) {
		return TCL_ERROR;
	} 
	kind = TclAB_TypeFromRecord(ab, theRecord);
	
	// Retrieve the index
	if (Tcl_GetIntFromObj(NULL, objv[4], &index) != TCL_OK) {
		Tcl_SetResult(interp, "invalid index value", TCL_STATIC);
		return TCL_ERROR;
	}
	
	// Get the property
	propertyRef = TclAB_TclObjToCFString(objv[3]);
	if (!propertyRef) {
		Tcl_SetResult(interp, "can't get property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	}

	// Check the property's type
	if ( ! TclAB_CheckMultiValueProperty(ab, propertyRef, kind) ) {
		Tcl_SetResult(interp, "not a multivalue property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
	
	multivalueRef = ABRecordCopyValue(theRecord, propertyRef);
	if (!multivalueRef) {
		Tcl_SetResult(interp, "can't get value for property", TCL_STATIC);
		result = TCL_ERROR;
	} else {
		if (objc == 6) {
			ABMutableMultiValueRef		mutableRef;
			
			Tcl_IncrRefCount(objv[5]);
			newvalueRef = TclAB_TclObjToCFString(objv[5]);

			if (!newvalueRef) {
				Tcl_SetResult(interp, "can't get label", TCL_STATIC);
				result = TCL_ERROR;
				goto DONE;
			} else {
				mutableRef = ABMultiValueCreateMutableCopy(multivalueRef);
				if (!mutableRef) {
					Tcl_SetResult(interp, "can't copy value ref", TCL_STATIC);
					result = TCL_ERROR;
				} else {		
					if (!ABMultiValueReplaceLabel(mutableRef, newvalueRef, index)) {
						Tcl_SetResult(interp, "can't replace label", TCL_STATIC);
						result = TCL_ERROR;
					} else if (!ABRecordSetValue(theRecord, propertyRef, mutableRef)) {
						Tcl_SetResult(interp, "can't set value in record", TCL_STATIC);
						result = TCL_ERROR;
					} else {
						Tcl_SetObjResult(interp, objv[5]);
					}
					CFRelease(mutableRef);
				}
				CFRelease(newvalueRef);
			}			
		} else {
			CFStringRef	labelRef = ABMultiValueCopyLabelAtIndex(multivalueRef, index);
			if (!labelRef) {
				Tcl_SetResult(interp, "can't get label", TCL_STATIC);
				result = TCL_ERROR;
			} else {
				CFStringRef	localizedRef = ABCopyLocalizedPropertyOrLabel(labelRef);
				if (localizedRef) {
					Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(localizedRef));
					CFRelease(localizedRef);
				} 
				CFRelease(labelRef);
			}
		}
		CFRelease(multivalueRef);
	}
	
DONE:
	if (objc == 6) Tcl_DecrRefCount(objv[5]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(theRecord);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Parents --
 *
 *	This procedure is invoked to process the [addressbook parents] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook parents ?-ids? recordID
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Parents(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	CFArrayRef		allParents = NULL;
	ABRecordRef		theRecord;
	CFStringRef		uidRef;
	int				index, kind, success;
	ABAddressBookRef ab;
	Boolean			onlyID = false;
	
	static CONST char *parentsSwitches[] = {
		"-ids", (char *) NULL
	};
		
	if (objc != 3 && objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "?-ids? recordID");
		return TCL_ERROR;
	}
	
	if (objc == 4) {
		if (Tcl_GetIndexFromObj(interp, objv[2], parentsSwitches, "option", 0, &index) != TCL_OK) {
			return TCL_ERROR;
		} else {
			onlyID = true;
		}
	}
			
	// Get the address book
	ab = ABGetSharedAddressBook();    
	
	// Get the UID argument
	uidRef = TclAB_TclObjToCFString(objv[objc-1]);

	// Find the record corresponding to the UID
	success = TclAB_RecordFromUID(interp, ab, uidRef, &theRecord);
	CFRelease(uidRef);
	if (!success) {return TCL_ERROR;} 
		
	// Is it a person or a group?
	kind = TclAB_TypeFromRecord(ab, theRecord);
	if (kind == rec_undefined) {
		Tcl_SetResult(interp, "unknown type for recordID argument", TCL_STATIC);
		CFRelease(theRecord);
		return TCL_ERROR;
	} 
	
	switch (kind) {
		case rec_person:
		allParents = ABPersonCopyParentGroups(theRecord);
		break;
		
		case rec_group:
		allParents = ABGroupCopyParentGroups(theRecord);
		break;
	}

	CFRelease(theRecord);
	
	if (allParents != NULL) {
		TclAB_SetResultWithArrayOfRecords(interp, allParents, onlyID, rec_group);
		CFRelease(allParents);
	} else {
		return TCL_ERROR;
	} 
	
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Persons --
 *
 *	This procedure is invoked to process the [addressbook persons] Tcl command.
 *	See the user documentation for details on what it does. If no option is 
 *  specified, it returns a list of one or two elements lists. The elements 
 *  are the UID and, if it exists, the last name. If the option -ids is 
 *  specified, only UIDs are listed.
 *
 * Syntax:
 *  addressbook persons ?-ids? ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Persons(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	return TclAB_ListItems(interp, objc, objv, rec_person);
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Property --
 *
 *	This procedure is invoked to process the [addressbook property] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook property add (-groups|-persons) propName propType
 *  addressbook property names (-groups|-persons)
 *  addressbook property remove (-groups|-persons|recordID) propName
 *  addressbook property type (-groups|-persons) propName
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Property(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int		result = TCL_ERROR, index, kind;
	
	static CONST char *propSubcmdSwitches[] = {
		"add", "names", "remove", "type", (char *) NULL
	};
	
	enum {
		TCLAB_PROPERTY_ADD, TCLAB_PROPERTY_NAMES, TCLAB_PROPERTY_REMOVE, 
		TCLAB_PROPERTY_TYPE
	};
	
	static CONST char *propertySwitches[] = {
		"-groups", "-persons", (char *) NULL
	};
	
	if (objc < 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "(add|names|remove|type) (-groups|-persons) ?args?");
		return TCL_ERROR;
	}
	
	if (Tcl_GetIndexFromObj(interp, objv[2], propSubcmdSwitches,
							"subcommand", 0, &index) != TCL_OK) {
		return TCL_ERROR;
	}
	
	if (Tcl_GetIndexFromObj(interp, objv[3], propertySwitches,
							"option", 0, &kind) != TCL_OK) {
		if (index != TCLAB_PROPERTY_REMOVE) {
			return TCL_ERROR;
		} else {
			// For the [remove] subcommand, the third argument
			// can also be a record ID. Pass rec_undefined in that case.
			kind = rec_undefined;
		}
	}
	
	switch (index) {
		case TCLAB_PROPERTY_ADD:		
		result = TclAB_PropertyAdd(interp, objc, objv, kind);
		break;
		
		case TCLAB_PROPERTY_NAMES:		
		result = TclAB_PropertyNames(interp, objc, objv, kind);
		break;
		
		case TCLAB_PROPERTY_REMOVE:		
		result = TclAB_PropertyRemove(interp, objc, objv, kind);
		break;
		
		case TCLAB_PROPERTY_TYPE:		
		result = TclAB_PropertyType(interp, objc, objv, kind);
		break;
	}
	
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Record --
 *
 *	This procedure is invoked to process the [addressbook record] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook record recordID 
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Record(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord;
	CFStringRef			uidRef;
	int					kind, success;
	
	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID");
		return TCL_ERROR;
	}
		
	Tcl_ResetResult(interp);
	
	// Get the UID argument
	uidRef = TclAB_TclObjToCFString(objv[2]);

	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Find the record corresponding to the UID
	success = TclAB_RecordFromUID(interp, ab, uidRef, &theRecord);
	CFRelease(uidRef);
	if (!success) {return TCL_ERROR;} 
	
	// Is it a person or a group?
	kind = TclAB_TypeFromRecord(ab, theRecord);
	if (kind == rec_undefined) {
		Tcl_SetResult(interp, "unknown type for recordID argument", TCL_STATIC);
		CFRelease(theRecord);
		return TCL_ERROR;
	} 
	
	// Store the record's data in the result object
	TclAB_ReturnRecord(interp, ab, theRecord, kind);
	
	CFRelease(theRecord);
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Remove --
 *
 *	This procedure is invoked to process the [addressbook remove] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook remove groupID recordID ?recordID ...?
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Remove(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	int					i, kind, result = TCL_OK, success;
	CFStringRef			strRef;
	ABAddressBookRef 	ab;
	ABRecordRef			objRecord, groupRecord;
	
	if (objc < 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "groupID recordID ?recordID ...?");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Second argument is a group ID
	// Check if it is a group and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[2], "second", rec_group, &groupRecord) ) {
		return TCL_ERROR;
	} 
	
	for ( i = 3; i < objc; i++) {
		
		Tcl_IncrRefCount(objv[i]);
		
		// Get the record ID
		strRef = TclAB_TclObjToCFString(objv[i]);
		
		// Find the record corresponding to the UID
		success = TclAB_RecordFromUID(interp, ab, strRef, &objRecord);
		CFRelease(strRef);
		if (!success) {
			Tcl_DecrRefCount(objv[i]);
			result = TCL_ERROR;
			break;
		} 
		
		// Is objRecord a person or a group?
		kind = TclAB_TypeFromRecord(ab, objRecord);
		switch (kind) {
			case rec_person:
			if (!ABGroupRemoveMember(groupRecord, objRecord)) {
				Tcl_AppendResult(interp, "can't remove member ", Tcl_GetString(objv[i]), " from group", (char *) NULL);
				result = TCL_ERROR;
			} 
			break;
			
			case rec_group:
			if (!ABGroupRemoveGroup(groupRecord, objRecord)) {
				Tcl_AppendResult(interp, "can't remove subgroup ", Tcl_GetString(objv[i]), " from group", (char *) NULL);
				result = TCL_ERROR;
			} 
			break;
			
			default:
			Tcl_AppendResult(interp, "unknown type for recordID ", Tcl_GetString(objv[i]), (char *) NULL);
			result = TCL_ERROR;
			break;
		}
		Tcl_DecrRefCount(objv[i]);
		CFRelease(objRecord);
		if (result == TCL_ERROR) {
			break;
		} 
	}
	
	CFRelease(groupRecord);
	return result; 
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Save --
 *
 *	This procedure is invoked to process the [addressbook save] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook save
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */
			
int
TclABCmd_Save(
    ClientData clientData,			/* Not used. */
    Tcl_Interp * interp,			/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef ab;
	
	if (objc != 2) {
		Tcl_WrongNumArgs(interp, 1, objv, "save");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();    

	return (ABSave(ab)) ? TCL_OK : TCL_ERROR; 
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Search --
 *
 *	This procedure is invoked to process the [addressbook search] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook search ?(-groups|-persons)? ?-ids? ?-nocase? property op value
 *  
 *  The op argument can be: 
 *           "==",  "!=",  "<",  "<=",  ">",  ">=",  "^=",  "=$"
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Search(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABSearchElementRef	theSearchElement = NULL;
	ABSearchComparison	comparison = kABEqual;
	ABPropertyType		thePropType;
	CFStringRef			propertyRef, keyRef, labelRef;
	CFTypeRef			valueRef;
	CFArrayRef			foundItemsRef;
	int					i, index, kind = rec_person;
	Boolean				onlyID = false, noCase = false, isNumeric;
	
	static CONST char *searchSwitches[] = {
		"-groups", "-persons", "-ids", "-nocase", (char *) NULL
	};
		
	enum {
		TCLAB_SEARCH_GROUPS, TCLAB_SEARCH_PERSONS, 
		TCLAB_SEARCH_IDS, TCLAB_SEARCH_NOCASE
	};

	if (objc < 5 || objc > 8) {
		Tcl_WrongNumArgs(interp, 2, objv, "?(-groups|-persons)? ?-ids? ?-nocase? property op value");
		return TCL_ERROR;
	}
	if (objc > 5) {
		for (i = objc-4; i > 1; i--) {
			if (Tcl_GetIndexFromObj(interp, objv[i], searchSwitches, "option", 0, &index) != TCL_OK) {
				return TCL_ERROR;
			} 
			switch (index) {
			  case TCLAB_SEARCH_GROUPS:
			  case TCLAB_SEARCH_PERSONS:
				kind = index;
				break;
				
			  case TCLAB_SEARCH_IDS:
				onlyID = true;
				break;
				
			  case TCLAB_SEARCH_NOCASE:
				noCase = true;
				break;
			}
		}
	}
		
	// Make CF objects. The kind of CF object for value depends on the type of 
	// the property (string, integer, date etc.)
	propertyRef = TclAB_TclObjToCFString(objv[objc-3]);
	if (!propertyRef) {
		Tcl_SetResult(interp, "can't create property reference", TCL_STATIC);
		return TCL_ERROR;
	} 
	
	// Get the address book
	ab = ABGetSharedAddressBook();    

	thePropType = ABTypeOfProperty(ab, kind ? kABPersonRecordType : kABGroupRecordType, propertyRef);
	switch (thePropType) {
		case kABDateProperty:
		case kABIntegerProperty:
		case kABRealProperty:
		case kABMultiDateProperty:
		case kABMultiIntegerProperty:
		case kABMultiRealProperty:
		isNumeric = true;
		break;
		
		default:
		isNumeric = false;
		break;		
	}

	// Create a CF reference for the searched value, the label and the key
	if (!TclAB_MakeSearchRefFromTcl(interp, objv[objc-1], thePropType, &labelRef, &keyRef, &valueRef)) {
		CFRelease(propertyRef);
		return TCL_ERROR;
	} 
	if (!valueRef) {
		Tcl_SetResult(interp, "can't create value reference", TCL_STATIC);
		CFRelease(propertyRef);
		return TCL_ERROR;
	} 
			
	if (TclAB_GetSearchComparison(interp, objv[objc-2], isNumeric, noCase, &comparison) != TCL_OK) {
		return TCL_ERROR;
	} 

	switch (kind) {
		case rec_person:
		theSearchElement = ABPersonCreateSearchElement(propertyRef, labelRef, keyRef, valueRef, comparison);
		break;
		
		case rec_group:
		theSearchElement = ABGroupCreateSearchElement(propertyRef, labelRef, keyRef, valueRef, comparison);
		break;
	}
	CFRelease(propertyRef);
	CFRelease(valueRef);
	if (keyRef) CFRelease(keyRef);
	if (labelRef) CFRelease(labelRef);
	
	if (theSearchElement) {
		foundItemsRef = ABCopyArrayOfMatchingRecords(ab, theSearchElement);
		CFRelease(theSearchElement);
		if (foundItemsRef) {
			TclAB_SetResultWithArrayOfRecords(interp, foundItemsRef, onlyID, kind);
			CFRelease(foundItemsRef);
		} else {
			Tcl_SetResult(interp, "can't get array of matching records", TCL_STATIC);
			return TCL_ERROR;
		}
	} else {
		Tcl_SetResult(interp, "can't build search element", TCL_STATIC);
		return TCL_ERROR;
	}
	
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Set --
 *
 *	This procedure is invoked to process the [addressbook set] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook set recordID property ?value?
 *  
 *  If the ?value? argument is specified, the command tries to set the 
 *  property to that value, otherwise it returns the current value of 
 *  the property if any.
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Set(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			uidRef, propertyRef;
	CFTypeRef			valueRef;
	ABRecordRef			theRecord;
	ABPropertyType		thePropType;
	int					kind, result = TCL_OK, success;
	
	if (objc != 4 && objc != 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID property ?value?");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
		
	// Get the UID
	uidRef = TclAB_TclObjToCFString(objv[2]);

	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Find the record corresponding to the UID
	success = TclAB_RecordFromUID(interp, ab, uidRef, &theRecord);
	CFRelease(uidRef);
	if (!success) {return TCL_ERROR;} 
	
	// Is recordID a person or a group?
	kind = TclAB_TypeFromRecord(ab, theRecord);

	// Find the property type (string, date, multivalue...)
	Tcl_IncrRefCount(objv[3]);
	propertyRef = TclAB_TclObjToCFString(objv[3]);
	
	if (propertyRef) {
		if (kind == rec_person) {
			thePropType = ABTypeOfProperty(ab, kABPersonRecordType, propertyRef);
		} else {
			thePropType = ABTypeOfProperty(ab, kABGroupRecordType, propertyRef);
		}
		if (thePropType == kABErrorInProperty) {
			Tcl_AppendResult(interp, "unknown property '", Tcl_GetString(objv[3]), 
					   "' for ", kind ? "Person":"Group" , " records", (char *) NULL);
			CFRelease(propertyRef);
			result = TCL_ERROR;
			goto DONE;
		} 
		
		if (objc == 5) {
			Tcl_IncrRefCount(objv[4]);
			if (!TclAB_SetValue(interp, theRecord, propertyRef, objv[4], thePropType)) {
				Tcl_AppendResult(interp, "can't set value for '", 
									   Tcl_GetString(objv[3]), "' property", (char *) NULL);
				result = TCL_ERROR;
			} else {
				Tcl_SetObjResult(interp, objv[4]);
			}
			Tcl_DecrRefCount(objv[4]);
		} else {
			valueRef = ABRecordCopyValue(theRecord, propertyRef);
			if (!valueRef) {
				Tcl_AppendResult(interp, "can't get value of '", 
									   Tcl_GetString(objv[3]), "' for this record", (char *) NULL);
				result = TCL_ERROR;
			} else {				
				Tcl_Obj *			valueObj;
				
				valueObj = TclAB_MakeTclObjForValue(interp, thePropType, valueRef);
				if (valueObj) {
					Tcl_SetObjResult(interp, valueObj);		
				} 
				CFRelease(valueRef);
			}
		}
		CFRelease(propertyRef);
	} else {
		Tcl_SetResult(interp, "can't get property ref", TCL_STATIC);
		result = TCL_ERROR;
	}
	
DONE:
	Tcl_DecrRefCount(objv[3]);
	CFRelease(theRecord);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_SetMe --
 *
 *	This procedure is invoked to process the [addressbook setme] Tcl command.
 *	It sets the record that represents the logged-in user. 
 *
 * Syntax:
 *  addressbook setme recordID 
 *  
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_SetMe(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord;
	
	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
		
	// Check if the second argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[2], "second", rec_person, &theRecord) ) {
		return TCL_ERROR;
	} 
	
	ABSetMe(ab, theRecord);
	
	CFRelease(theRecord);
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclABCmd_Type --
 *
 *	This procedure is invoked to process the [addressbook type] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook type recordID
 *  
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
TclABCmd_Type(
    ClientData clientData,			/* Not used. */
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			uidRef;
	CFStringRef			typeRef;
	ABRecordRef			theRecord;
	int					result = TCL_OK, success;
	
	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID");
		return TCL_ERROR;
	}
		
	// Get the UID
	uidRef = TclAB_TclObjToCFString(objv[2]);

	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Is it a group or a person ?
	// NB: we could get the type from the UID directly with the function
	// ABCopyRecordTypeFromUniqueId() but it has been introduced only in 
	// Panther and is not available in Jaguar. Workaround: get the record 
	// reference and then the type from this record.
	//		typeRef = ABCopyRecordTypeFromUniqueId(ab, uidRef);
	success = TclAB_RecordFromUID(interp, ab, uidRef, &theRecord);
	CFRelease(uidRef);
	if (!success) {return TCL_ERROR;} 
	
	typeRef = ABRecordCopyRecordType(theRecord);
	CFRelease(theRecord);
	if (typeRef) {
		Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(typeRef));
		CFRelease(typeRef);
	} else {
		Tcl_SetResult(interp, "unrecognized type for record", TCL_STATIC);
		result = TCL_ERROR;
	}
	
	return result;
}
