// File: TclABTasks.cc"
//                        Created: 2003-12-05 10:08:03
//              Last modification: 2009-03-10 16:27:18
// Author: Bernard Desgraupes
// e-mail: <bdesgraupes@users.sourceforge.net>
// 
// Utility functions called by the TclABCmd command functions
// 
// (c) Copyright: Bernard Desgraupes, 2003-2009
//         All rights reserved.
// This software is free software with BSD licence.
// Version history: see the Changes.Log file.


/* TclABTasks.h included in precompiled header */
#include "TclAB.h"
#include "TclABTasks.h"
#include "TclABUtils.h"


static CONST char *theABPropertyNames[] = {
	"Array", "Data", "Date", "Dictionary", "Integer", "Real", "String", 
	"MultiArray","MultiData", "MultiDate", "MultiDictionary", 
	"MultiInteger", "MultiReal", "MultiString", (char *) NULL
};
		
static ABPropertyType theABPropertyTypes[] = {
	kABArrayProperty, 
	kABDataProperty, 
	kABDateProperty, 
	kABDictionaryProperty, 
	kABIntegerProperty, 
	kABRealProperty, 
	kABStringProperty,
	kABMultiArrayProperty, 
	kABMultiDataProperty, 
	kABMultiDateProperty, 
	kABMultiDictionaryProperty, 
	kABMultiIntegerProperty, 
	kABMultiRealProperty, 
	kABMultiStringProperty, 
	(ABPropertyType) NULL
};

/*
 *----------------------------------------------------------------------
 *
 * TclAB_ReturnRecord --
 *
 *	This function is invoked to return the property/values pairs in the 
 *	result object. 
 *  
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets the array.
 *	
 *----------------------------------------------------------------------
 */

void
TclAB_ReturnRecord(Tcl_Interp *interp, ABAddressBookRef ab, 
							 ABRecordRef inRecord, int inRecordType)
{
	CFIndex 			theCount;
	CFStringRef			theProperty;
	CFTypeRef			theValue;
	CFArrayRef			allProps;
	int					i;
	Tcl_Obj *			valueObj;
	Tcl_Obj *			listObj = NULL;

	allProps = ABCopyArrayOfPropertiesForRecordType(ab, inRecordType ? kABPersonRecordType : kABGroupRecordType);
	theCount = CFArrayGetCount(allProps);
	
	listObj = Tcl_NewListObj(0, NULL);
		
	for (i = 0; i < theCount; i++) {
		// Get the property at index i
		theProperty = CFArrayGetValueAtIndex(allProps, i);
		
		if (theProperty) {
			theValue = ABRecordCopyValue(inRecord, theProperty);
			if (!theValue) {
				continue;
			} 
			valueObj = TclAB_BuildValueForResult(interp, ab, theProperty, theValue, inRecordType);
			if (valueObj) {
				Tcl_ListObjAppendElement(interp, listObj, valueObj);
			} 			
			CFRelease(theValue);
		}
	}
	Tcl_SetObjResult(interp, listObj);
	
	CFRelease(allProps);
}
 
 
/*
 *----------------------------------------------------------------------
 *
 * TclAB_BuildValueForResult --
 *
 *	This function is invoked to return a specific property/value pair. 
 *  
 * Results:
 *	Result is formatted as a keyed list. For instance: 
 *	Multi dictionary
 *	  { {label1 { {key11 value11} {key12 value12} {key13 value13} } }   {label2 { {key21 value2} {key22 value22} {key23 value23} } } }
 *	Multi string / multi date
 *	  {label1 value1} {label2 value2} {label3 value3}
 *
 *----------------------------------------------------------------------
 */
// 	kABArrayProperty, 
// 	kABDataProperty, 
// 	kABDateProperty, 
// 	kABDictionaryProperty, 
// 	kABIntegerProperty, 
// 	kABRealProperty, 
// 	kABStringProperty,
// 	kABMultiArrayProperty, 
// 	kABMultiDataProperty, 
// 	kABMultiDateProperty, 
// 	kABMultiDictionaryProperty, 
// 	kABMultiIntegerProperty, 
// 	kABMultiRealProperty, 
// 	kABMultiStringProperty, 


Tcl_Obj *
TclAB_BuildValueForResult(Tcl_Interp *interp, ABAddressBookRef ab, 
				   CFStringRef inProperty, CFTypeRef inValue, int inRecordType)
{
	ABPropertyType		thePropType;
	Tcl_Obj *			propertyObj;
	Tcl_Obj *			valueObj;
	Tcl_Obj *			listObj = NULL;
	Tcl_Obj *			objs[2];
	
	if (inRecordType == rec_person) {
		thePropType = ABTypeOfProperty(ab, kABPersonRecordType, inProperty);
	} else {
		thePropType = ABTypeOfProperty(ab, kABGroupRecordType, inProperty);
	}
	
	valueObj = TclAB_MakeTclObjForValue(interp, thePropType, inValue);
	if (valueObj) {
		propertyObj = TclAB_CFStringToTclObj(inProperty);
		if (propertyObj) {
			
			objs[0] = propertyObj;
			objs[1] = valueObj;
			listObj = Tcl_NewListObj(2, objs);
		} 
	} 
	return listObj;
}


Tcl_Obj *
TclAB_MakeTclObjForValue(Tcl_Interp *interp, ABPropertyType inPropType, CFTypeRef inValue)
{
	CFStringRef				theString;
	int						j, k;
	Tcl_Obj *				valueObj = NULL;
	Tcl_Obj *				subObj[2];
	Tcl_Obj *				pairObj = NULL;
	
	if (inValue) {
		switch (inPropType) {
			
			case kABStringProperty:
			valueObj = TclAB_CFStringToTclObj(inValue);
			break;
			
			
			case kABDateProperty: {
				CFAbsoluteTime		theAbsTime;
				theAbsTime = CFDateGetAbsoluteTime(inValue);
				// Offset the value by 31 years: 1 jan 1970 is the reference date for 
				// Unix, while it is 1 jan 2001 for the CFDate functions. This will 
				// make the returned value suitable for use with [clock format]:
				// kCFAbsoluteTimeIntervalSince1970 = 978307200 (declared in CFDate.h)
				// [clock format 978307200 -gmt 1]  --->  Mon Jan 01 00:00:00 GMT 2001
				theAbsTime += kCFAbsoluteTimeIntervalSince1970;
				valueObj = Tcl_NewWideIntObj(theAbsTime);			
			}
			break;
			
			
			case kABIntegerProperty: {
				SInt32			theSInt32;
				if (CFNumberGetValue(inValue, kCFNumberSInt32Type, &theSInt32)) {
					valueObj = Tcl_NewIntObj(theSInt32);
				} 
			}
			break;
			
			
			case kABRealProperty: {
				double			theDouble;
				if (CFNumberGetValue(inValue, kCFNumberDoubleType, &theDouble)) {
					valueObj = Tcl_NewDoubleObj(theDouble);
				} 
			}
			break;
			
			
			case kABDataProperty: {
				const UInt8 *	theBytes = CFDataGetBytePtr(inValue);
				if (theBytes) {
					valueObj = Tcl_NewByteArrayObj(theBytes, CFDataGetLength(inValue));
				} 
			}
			break;
			
			
			case kABArrayProperty: {
				CFIndex			theCount;
				valueObj = Tcl_NewListObj(0, NULL);
				theCount = CFArrayGetCount(inValue);
				for (j = 0; j < theCount; j++) {
					theString = CFArrayGetValueAtIndex(inValue, j);
					if (theString) {
						Tcl_ListObjAppendElement(interp, valueObj, TclAB_CFStringToTclObj(theString));
					} 
				}
			}
			break;
			
			
			case kABDictionaryProperty: {
				CFIndex 			dictCount;
				CFStringRef *		theKeys;
				CFStringRef *		theVals;
				
				dictCount = CFDictionaryGetCount(inValue);
				// Allocate memory to store the keys and values
				theKeys = (CFStringRef*) NewPtrClear(sizeof(CFStringRef) * dictCount);
				theVals = (CFStringRef*) NewPtrClear(sizeof(CFStringRef) * dictCount);
				if ((theKeys != NULL) && (theVals != NULL)) {
					// A dictionary is represented as a list of two-elements sublists (key/value)					
					valueObj = Tcl_NewListObj(0, NULL);
					
					// Fill the keys and values arrays from this dictionary
					CFDictionaryGetKeysAndValues(inValue, (const void **) theKeys, (const void **) theVals);
					// Loop over all the entries
					for (k = 0; k < dictCount; k++) {
						if (theKeys[k] && theVals[k] ) {
							// Make a two items sublist for each key/value pair
							subObj[0] = TclAB_CFStringToTclObj(theKeys[k]);
							subObj[1] = TclAB_CFStringToTclObj(theVals[k]);
							pairObj = Tcl_NewListObj(2, subObj);
							Tcl_ListObjAppendElement(interp, valueObj, pairObj);
						} 
					}
				} else {
					// "Could not get keys and values for dictionary"
				}
				if (theKeys) DisposePtr( (Ptr) theKeys);
				if (theVals) DisposePtr( (Ptr) theVals);
			}
			break;
			
			
			case kABMultiStringProperty:
			case kABMultiDateProperty:
			case kABMultiIntegerProperty:
			case kABMultiRealProperty:
			case kABMultiDataProperty:
			case kABMultiArrayProperty:
			case kABMultiDictionaryProperty: {
				ABMutableMultiValueRef 	multiValue;
				ABPropertyType			singlePropertyType;
				CFIndex					mvCount;
				CFTypeRef				subLabel, subValue;
				CFStringRef				localizedStr;
		
				multiValue = (ABMutableMultiValueRef) inValue;
				mvCount = ABMultiValueCount(multiValue);
				singlePropertyType = (inPropType & ~kABMultiValueMask);
				
				valueObj = Tcl_NewListObj(0, NULL);
				
				for (j = 0; j < mvCount; j++) {
					subLabel = ABMultiValueCopyLabelAtIndex(multiValue, j);
					subValue = ABMultiValueCopyValueAtIndex(multiValue, j);
					if (subLabel && subValue) {
						localizedStr = ABCopyLocalizedPropertyOrLabel(subLabel);					
						if (localizedStr) {
							subObj[0] = TclAB_CFStringToTclObj(localizedStr);
							subObj[1] = TclAB_MakeTclObjForValue(interp, singlePropertyType, subValue);
							if (subObj[0] && subObj[1]) {
								pairObj = Tcl_NewListObj(2, subObj);
								Tcl_ListObjAppendElement(interp, valueObj, pairObj);
							} 
							CFRelease(localizedStr);
						}
					} 
					if (subLabel) CFRelease(subLabel);
					if (subValue) CFRelease(subValue);
				}
			}
			break;
			
			
			case kABErrorInProperty:
			default:
			break;
		}
	}
	return valueObj;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_SetValue --
 *
 *	This function is invoked to set a specific property/value pair. 
 *  
 * Results:
 *	None.
 *
 * Side effects:
 *	
 *----------------------------------------------------------------------
 */

bool
TclAB_SetValue(Tcl_Interp *interp, ABRecordRef inRecord, CFStringRef inProperty, 
					Tcl_Obj * inValue, ABPropertyType inPropType)
{
	CFTypeRef	valueRef = NULL;
	bool		result = false;
	
	if (inValue) {
		// Remove the old value of the property
		result = ABRecordRemoveValue(inRecord, inProperty);
		// Create a CF reference for the new value
		if (TclAB_MakeValueRefFromTclObj(interp, inValue, inPropType, &valueRef)) {
			result = ABRecordSetValue(inRecord, inProperty, valueRef);
		} 
	} 
	if (valueRef != NULL) {
		CFRelease(valueRef);
	} 
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_ListItems --
 *
 *	This function is invoked by the [addressbook persons] and 
 *	[addressbook groups] Tcl commands.
 *	See the user documentation for details on what they do. If no option is 
 *  specified, it returns a list of one or two elements lists: the first element 
 *  is the UID and the second, if it exists, is the name. 
 *  If the option -ids is specified, only UIDs are listed.
 *  If the -ingroup argument is specified, only items from this group are 
 *  returned: use it with [addressbook groups] to list subgroups of a group
 *  or with [addressbook persons] to list members of a particular group.
 *
 * Syntax:
 *  addressbook (groups|persons) ?-ids? ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_ListItems(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)				/* Type of the items */
{
	CFArrayRef		allItems = NULL;
	int				index;
	ABAddressBookRef ab;
	Boolean			onlyID = false;
	ABRecordRef		inGroup;
	
	static CONST char *personsSwitches[] = {
		"-ids", "-ingroup", (char *) NULL
	};
		
	enum {
		TCLAB_PERSONS_IDS, TCLAB_PERSONS_INGROUP
	};
	
	if (objc < 2 || objc > 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "?-ids? ?-ingroup groupID?");
		return TCL_ERROR;
	}
	
	if (objc == 3 || objc == 5) {
		if (Tcl_GetIndexFromObj(interp, objv[2], personsSwitches, "option", 0, &index) != TCL_OK) {
			return TCL_ERROR;
		} else {
			onlyID = true;
		}
	}
			
	// Get the address book
	ab = ABGetSharedAddressBook();    
	
	if (objc == 4 || objc == 5) {
		if (Tcl_GetIndexFromObj(interp, objv[objc-2], personsSwitches, "option", 0, &index) != TCL_OK) {
			return TCL_ERROR;
		} else {
			if ( !TclAB_CheckItem(interp, ab, objv[objc-1], "-ingroup", rec_group, &inGroup) ) {
				return TCL_ERROR;
			} 
			if (inRecordType == rec_group) {
				allItems = ABGroupCopyArrayOfAllSubgroups(inGroup);
			} else if (inRecordType == rec_person) {
				allItems = ABGroupCopyArrayOfAllMembers(inGroup);
			} 
			CFRelease(inGroup);
		}
	} else {
		if (inRecordType == rec_group) {
			allItems = ABCopyArrayOfAllGroups(ab);
		} else if (inRecordType == rec_person) {
			allItems = ABCopyArrayOfAllPeople(ab);
		} 
	}
	
	if (allItems != NULL) {
		TclAB_SetResultWithArrayOfRecords(interp, allItems, onlyID, inRecordType);
		CFRelease(allItems);
	} else {
		return TCL_ERROR;
	} 
	
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_CreateRecord --
 *
 *	This function is invoked to process the [addressbook add group] and
 *	the [addressbook add person] Tcl commands.
 *	See the user documentation for details on what it does. It is used 
 *	to add either a new group or a new person record to the database.
 *
 * Syntax:
 *  addressbook add (group | person) name ?-ingroup groupID?
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_CreateRecord(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)				/* Type of the items */
{
	ABAddressBookRef	ab;
	ABRecordRef			theRecord = NULL, inGroup;
	CFStringRef			uniqueId, nameRef;
	int					index, result, handled;
	Boolean				isInGroup = false;
	
	static CONST char *createRecSwitches[] = {
		"-ingroup", (char *) NULL
	};
	
	if (objc != 4 && objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "(group|person) name ?-ingroup groupID?");
		return TCL_ERROR;
	}
	
	if (objc == 6) {
		if (Tcl_GetIndexFromObj(interp, objv[4], createRecSwitches, "option", 0, &index) != TCL_OK) {
			return TCL_ERROR;
		} else {
			isInGroup = true;
		}
	}
	
	nameRef = TclAB_TclObjToCFString(objv[3]);		
	if (!nameRef) {
		Tcl_SetResult(interp, "can't get name", TCL_STATIC);
		return TCL_ERROR;
	} 

	switch (inRecordType) {
		case rec_group:		
		theRecord = ABGroupCreate();
		break;
		
		case rec_person:		
		theRecord = ABPersonCreate();
		break;
	}
	if (!theRecord) {
		Tcl_AppendResult(interp, "can't create new '", 
							   inRecordType ? "Person":"Group" , "' record", (char *) NULL);
		return TCL_ERROR;
	} 
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Set the name
	result = ABRecordSetValue(theRecord, inRecordType ? kABLastNameProperty:kABGroupNameProperty, nameRef);
	CFRelease(nameRef);
	if (!result) {
		Tcl_SetResult(interp, "can't set name for new record", TCL_STATIC);
		CFRelease(theRecord);
		return TCL_ERROR;
	}
	
    // Add the record to the Address Book
	handled = ABAddRecord(ab, theRecord);
	
	// Handle the -ingroup optional argument
	if (handled && isInGroup) {
		if ( !TclAB_CheckItem(interp, ab, objv[5], "-ingroup", rec_group, &inGroup) ) {
			result = TCL_ERROR;
			goto DONE;
		} 

		// Are we adding a subgroup or a member
		switch (inRecordType) {
			case rec_group:		
			handled = ABGroupAddGroup(inGroup, theRecord);
			break;
			
			case rec_person:		
			handled = ABGroupAddMember(inGroup, theRecord);
			break;
		}
		CFRelease(inGroup);
	} 
	
    if ( !handled ) {
		Tcl_SetResult(interp, "can't add the new record to the Address Book", TCL_STATIC);
		result = TCL_ERROR;
    } else {
		// Retrieve the UID for the newly created record
		uniqueId = ABRecordCopyUniqueId(theRecord);
		if (uniqueId) {
			Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(uniqueId));
			CFRelease(uniqueId);
		} 
		result = TCL_OK;
	}

DONE:            
    CFRelease(theRecord);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_PropertyAdd --
 *
 *	This function is invoked to process the [addressbook property add]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook property add (-groups|-persons) propName propType
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_PropertyAdd(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)
{
	ABAddressBookRef		ab;
	CFStringRef				theName;
	CFNumberRef				theValue;
	CFMutableDictionaryRef	theDict;
	int						index, result = TCL_OK;
	int						theInt;
	
	Tcl_ResetResult(interp);
	
	if (objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "add (-groups|-persons) propName propType");
		return TCL_ERROR;
	}
	
	Tcl_IncrRefCount(objv[4]);
	
	if (Tcl_GetIndexFromObj(interp, objv[5], theABPropertyNames, "type", 0, &index) != TCL_OK) {
		return TCL_ERROR;
	} 
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	theName = TclAB_TclObjToCFString(objv[4]);	
	
	if (theName) {
		// Check that this property does not already exist
		CFArrayRef			theItems;
		theItems = ABCopyArrayOfPropertiesForRecordType(ab, inRecordType ? kABPersonRecordType : kABGroupRecordType);
		if (theItems && CFArrayContainsValue(theItems, CFRangeMake(0, CFArrayGetCount(theItems)), theName) ) {
			CFRelease(theItems);
			Tcl_AppendResult(interp, "property '", Tcl_GetString(objv[4]), "' already exists", (char *) NULL);
			result = TCL_ERROR;
			goto DONE;
		}
		
		// Create a CFDictionary. Keys are CFStrings so use
		// kCFCopyStringDictionaryKeyCallBacks instead of
		// kCFTypeDictionaryKeyCallBacks
		theDict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		if (theDict) {
			// Store the property/type pair in the CFDictionary
			theInt = theABPropertyTypes[index];
			theValue = CFNumberCreate(NULL, kCFNumberSInt32Type, &theInt);
			if (theValue) {
				CFDictionaryAddValue(theDict, theName, theValue);
				// Add the property to the Address Book
				if ( !ABAddPropertiesAndTypes(ab, inRecordType ? kABPersonRecordType : kABGroupRecordType, theDict) ) {
					Tcl_AppendResult(interp, "can't add property '", Tcl_GetString(objv[4]), "' to ", inRecordType ? "Person":"Group" , " record type", (char *) NULL);
					result = TCL_ERROR;
				} 
				CFRelease(theValue);
			} else {
				Tcl_AppendResult(interp, "can't get property type", (char *) NULL);
				result = TCL_ERROR;
			}
			CFRelease(theDict);
		} 
		CFRelease(theName);
	} else {
		Tcl_AppendResult(interp, "can't get property name", (char *) NULL);
		result = TCL_ERROR;
	} 

DONE:
	Tcl_DecrRefCount(objv[4]);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_PropertyNames --
 *
 *	This procedure is invoked to process the [addressbook property] Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook property names (-groups|-persons)
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_PropertyNames(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)
{
	CFIndex 			theCount;
	CFStringRef			theProperty;
	CFArrayRef			theItems;
	int					i, result;
	ABAddressBookRef	ab;
	Tcl_Obj *			listObj;

	if (objc > 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "names (-groups|-persons)");
		return TCL_ERROR;
	}
	
	// Get the address book
	ab = ABGetSharedAddressBook();    
	// Get the CFArray of properties
	theItems = ABCopyArrayOfPropertiesForRecordType(ab, inRecordType ? kABPersonRecordType : kABGroupRecordType);
	if (theItems) {
		theCount = CFArrayGetCount(theItems);
		listObj = Tcl_NewListObj(0, NULL);
		
		for (i = 0; i < theCount; i++) {
			// Get the value at index i
			theProperty = CFArrayGetValueAtIndex(theItems, i);
			if (theProperty) {
				result = Tcl_ListObjAppendElement(interp, listObj, TclAB_CFStringToTclObj(theProperty));
			}		
		}
		CFRelease(theItems);
		Tcl_SetObjResult(interp, listObj);
	} else {
		Tcl_AppendResult(interp, "can't get array of properties for record type", (char *) NULL);
		return TCL_ERROR;
	}
	
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_PropertyRemove --
 *
 *	This function is invoked to process the [addressbook property remove]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook property remove (-groups|-persons|recordID) propName
 * 
 * WARNING: 
 * --------
 * This command currently does not work and always returns value 0 because
 * the function ABRemoveProperties is not implemented. Invoking it leaves
 * the following message in the console:
 *     "Removal of custom properties is not yet implemented"
 * 
 *----------------------------------------------------------------------
 */

int
TclAB_PropertyRemove(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)
{
	ABAddressBookRef	ab;
	CFStringRef			propRef;
	CFArrayRef			theArray;
	int					num, result = TCL_OK;
	
	Tcl_ResetResult(interp);
	
	if (objc != 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "remove (-groups|-persons|recordID) propName");
		return TCL_ERROR;
	}
	
	Tcl_IncrRefCount(objv[4]);
	
	// Get the property name
	propRef = TclAB_TclObjToCFString(objv[4]);		
	
	if (propRef) {
		// Get the address book
		ab = ABGetSharedAddressBook();
		
		if (inRecordType == rec_undefined) {
			// The third argument was a record ID
			CFStringRef			uidRef;
			ABRecordRef			theRecord;
			
			uidRef = TclAB_TclObjToCFString(objv[3]);		
			
			if (uidRef) {
				// Find the record corresponding to the UID
				theRecord = ABCopyRecordForUniqueId(ab, uidRef);
				CFRelease(uidRef);
				if (theRecord) {
					if (!ABRecordRemoveValue(theRecord, propRef)) {
						Tcl_AppendResult(interp, "can't remove property '", Tcl_GetString(objv[4]), 
											   "' from the record", (char *) NULL);
						result = TCL_ERROR;
					} else {
						num = 1;
						Tcl_SetObjResult(interp, Tcl_NewIntObj(num));
					}
					CFRelease(theRecord);
				} else {
					Tcl_AppendResult(interp, "can't get record for ID", (char *) NULL);
					result = TCL_ERROR;
				}
			} else {
				Tcl_AppendResult(interp, "can't get UID", (char *) NULL);
				result = TCL_ERROR;
			}
		} else {
			// The third argument was -groups or -persons.
			// Create a CFArray to store the property.
			theArray = CFArrayCreate(NULL, (void*) &propRef, 1, &kCFTypeArrayCallBacks);
			
			if (theArray) {
				// Remove the property from all the records of this type in the Address Book
				// (as of 2009-03-03, the ABRemoveProperties function is still not working, see comment above)
				num = ABRemoveProperties(ab, inRecordType ? kABPersonRecordType : kABGroupRecordType, theArray);
				CFRelease(theArray);
				Tcl_SetObjResult(interp, Tcl_NewIntObj(num));
				result = TCL_OK;
			} else {
				Tcl_AppendResult(interp, "can't create properties array", (char *) NULL);
				result = TCL_ERROR;
			}
		}
		CFRelease(propRef);
	} else {
		Tcl_AppendResult(interp, "can't get property", (char *) NULL);
		result = TCL_ERROR;
	}
   
   Tcl_DecrRefCount(objv[4]);
   return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_PropertyType --
 *
 *	This function is invoked to process the [addressbook property type]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook property type (-groups|-persons) propName
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_PropertyType(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[],			/* Argument values. */
	int inRecordType)
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef;
	ABPropertyType		thePropType;
	int					index;
	
	Tcl_ResetResult(interp);
	
	if (objc != 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "type (-groups|-persons) propName");
		return TCL_ERROR;
	}
		
	// Make CFString objects
	propertyRef = TclAB_TclObjToCFString(objv[4]);		

	if (!propertyRef) {
		return TCL_ERROR;
	} 
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Get the type of the property
	if (inRecordType == rec_person) {
		thePropType = ABTypeOfProperty(ab, kABPersonRecordType, propertyRef);
	} else {
		thePropType = ABTypeOfProperty(ab, kABGroupRecordType, propertyRef);
	}
	CFRelease(propertyRef);
	
	if (thePropType == kABErrorInProperty) {
		Tcl_AppendResult(interp, "unknown property '", Tcl_GetString(objv[4]), 
							   "' for ", inRecordType ? "Person":"Group" , " record type", (char *) NULL);
	   return TCL_ERROR;
   } 
						   
	for (index = 0; theABPropertyTypes[index] != (ABPropertyType) NULL; index++) {
		if (theABPropertyTypes[index] == thePropType) {
			break;
		} 
	}
	if (theABPropertyTypes[index] == (ABPropertyType) NULL) {
		Tcl_SetResult(interp, "Unknown", TCL_STATIC);
		return TCL_ERROR;
	} else {
		Tcl_AppendResult(interp, theABPropertyNames[index], (char *) NULL);
	}
	
	return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_IdentifierCount --
 *
 *	This function is invoked to process the [addressbook identifier count]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier count personID propName
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_IdentifierCount(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef = NULL, valueRef;
	ABRecordRef			personRecord;
	int					result = TCL_OK;
	unsigned int		mvCount;

	if (objc != 5) {
		Tcl_WrongNumArgs(interp, 2, objv, "recordID propName");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if third argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[3], "third", rec_person, &personRecord) ) {
		return TCL_ERROR;
	} 
	
	Tcl_IncrRefCount(objv[4]);
	
	// Make CFString object for property
	propertyRef = TclAB_TclObjToCFString(objv[4]);		

	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, rec_person) ) {
		Tcl_SetResult(interp, "not a multi-string list property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
			
	valueRef = ABRecordCopyValue(personRecord, propertyRef);
	if (!valueRef) {
		Tcl_AppendResult(interp, "can't get value for '",
						Tcl_GetString(objv[4]), "' property", (char *) NULL);
		result = TCL_ERROR;
	} else {
		mvCount = ABMultiValueCount((ABMutableMultiValueRef) valueRef);
		CFRelease(valueRef);
		Tcl_SetObjResult(interp, Tcl_NewIntObj(mvCount));
	}
	
DONE:
	Tcl_DecrRefCount(objv[4]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(personRecord);
	return result;
}
 				   

/*
 *----------------------------------------------------------------------
 *
 * TclAB_IdentifierGet --
 *
 *	This function is invoked to process the [addressbook identifier get]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier get personID propName index
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_IdentifierGet(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef = NULL, valueRef, identRef;
	ABRecordRef			personRecord;
	int					index, result = TCL_OK;
	
	if (objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID propName index");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if third argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[3], "third", rec_person, &personRecord) ) {
		return TCL_ERROR;
	} 
		
	// Make CFString object for property
	Tcl_IncrRefCount(objv[4]);
	propertyRef = TclAB_TclObjToCFString(objv[4]);		

	if (!propertyRef) {
		Tcl_SetResult(interp, "can't get property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	}

	// Retrieve the index
	Tcl_IncrRefCount(objv[5]);
	if (Tcl_GetIntFromObj(NULL, objv[5], &index) != TCL_OK) {
		result = TCL_ERROR;
		goto DONE;
	}
	
	// Check the property's type
	if ( ! TclAB_CheckMultiValueProperty(ab, propertyRef, rec_person) ) {
		Tcl_SetResult(interp, "not a multi-string list property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
	
	valueRef = ABRecordCopyValue(personRecord, propertyRef);
	if (!valueRef) {
		Tcl_AppendResult(interp, "can't get value for '", Tcl_GetString(objv[4]), "' property", (char *) NULL);
		result = TCL_ERROR;
	} else {
		identRef = ABMultiValueCopyIdentifierAtIndex((ABMutableMultiValueRef) valueRef, index);
		if (identRef) {
			Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(identRef));
			CFRelease(identRef);
		} else {
			Tcl_AppendResult(interp, "can't get identifier at index ", Tcl_GetString(objv[5]), (char *) NULL);
			result = TCL_ERROR;
		}
		CFRelease(valueRef);
	}
	
DONE:
	Tcl_DecrRefCount(objv[4]);
	Tcl_DecrRefCount(objv[5]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(personRecord);
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_IdentifierIndex --
 *
 *	This function is invoked to process the [addressbook identifier index]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier index personID propName identifier
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_IdentifierIndex(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef = NULL, identRef, multivalueRef;
	ABRecordRef			personRecord;
	int					index, result = TCL_OK;
	
	if (objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID propName identifier");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if third argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[3], "third", rec_person, &personRecord) ) {
		return TCL_ERROR;
	} 
		
	// Make CFString object for property
	Tcl_IncrRefCount(objv[4]);
	propertyRef = TclAB_TclObjToCFString(objv[4]);		

	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, rec_person) ) {
		Tcl_SetResult(interp, "not a multi-string list property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
			
	Tcl_IncrRefCount(objv[5]);
	identRef = TclAB_TclObjToCFString(objv[5]);		
	
	if (!identRef) {
		Tcl_SetResult(interp, "can't get identifier", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} else {
		multivalueRef = ABRecordCopyValue(personRecord, propertyRef);
		if (!multivalueRef) {
			Tcl_AppendResult(interp, "can't get value for '", Tcl_GetString(objv[4]), "' property", (char *) NULL);
			result = TCL_ERROR;
		} else {
			index = ABMultiValueIndexForIdentifier((ABMutableMultiValueRef) multivalueRef, identRef);
			if (index == 0x7fffffff) {
				Tcl_AppendResult(interp, "bad identifier ", Tcl_GetString(objv[5]), (char *) NULL);
				result = TCL_ERROR;
			} else {
				Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
			}
			CFRelease(multivalueRef);
		}
		CFRelease(identRef);
	}
	
DONE:
	Tcl_DecrRefCount(objv[4]);
	Tcl_DecrRefCount(objv[5]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(personRecord);
	return result;
}
 				   
				   
/*
 *----------------------------------------------------------------------
 *
 * TclAB_IdentifierPrimary --
 *
 *	This function is invoked to process the [addressbook identifier primary]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier primary personID propName ?ident?
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_IdentifierPrimary(
    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			personRecord;
	int					result = TCL_OK;
	
	if (objc != 5 && objc != 6) {
		Tcl_WrongNumArgs(interp, 2, objv, "personID propName ?identifier?");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if third argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[3], "third", rec_person, &personRecord) ) {
		return TCL_ERROR;
	} 
		
	// Get the property
	Tcl_IncrRefCount(objv[4]);
	propertyRef = TclAB_TclObjToCFString(objv[4]);		

	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, rec_person) ) {
		Tcl_SetResult(interp, "not a multi-string list property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
	
	multivalueRef = ABRecordCopyValue(personRecord, propertyRef);
	if (!multivalueRef) {
		Tcl_AppendResult(interp, "can't get value for '", Tcl_GetString(objv[4]), "' property", (char *) NULL);
		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 identifier", 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 (!ABMultiValueSetPrimaryIdentifier(mutableRef, newvalueRef)) {
						Tcl_SetResult(interp, "can't set primary identifier", TCL_STATIC);
						result = TCL_ERROR;
					} else if (!ABRecordSetValue(personRecord, 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 identRef = ABMultiValueCopyPrimaryIdentifier(multivalueRef);
			if (identRef) {
				Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(identRef));
				CFRelease(identRef);
			} else {
				Tcl_AppendResult(interp, "can't get primary identifier for '", Tcl_GetString(objv[4]), "' property", (char *) NULL);
				result = TCL_ERROR;
			}
		}
		CFRelease(multivalueRef);
	}
	
DONE:
	Tcl_DecrRefCount(objv[4]);
	if (objc == 6) Tcl_DecrRefCount(objv[5]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(personRecord);
	return result;
}
 				   
				   
/*
 *----------------------------------------------------------------------
 *
 * TclAB_IdentifierSet --
 *
 *	This function is invoked to process the [addressbook identifier set]
 *	Tcl command.
 *	See the user documentation for details on what it does.
 *
 * Syntax:
 *  addressbook identifier set groupID personID propName ?identifier?
 *  
 *  If the ?identifier? argument is specified, the command tries to set the 
 *  distribution identifier of the property to that value, otherwise it returns 
 *  the current value of the distribution identifier. It concerns 
 *  multi-value list properties and applies to a particular person belonging to 
 *  a particular group.
 *  
 * Results:
 *	A standard Tcl result.
 *
 *----------------------------------------------------------------------
 */

int
TclAB_IdentifierSet(
    Tcl_Interp *interp,				/* Current interpreter. */
    int objc,						/* Number of arguments. */
    Tcl_Obj *CONST objv[])			/* Argument values. */
{
	ABAddressBookRef	ab;
	CFStringRef			propertyRef = NULL, newvalueRef = NULL;
	ABRecordRef			groupRecord, personRecord;
	int					result = TCL_OK;
	
	if (objc != 6 && objc != 7) {
		Tcl_WrongNumArgs(interp, 2, objv, "groupID personID propName ?identifier?");
		return TCL_ERROR;
	}
	
    Tcl_ResetResult(interp);
	
	// Get the address book
	ab = ABGetSharedAddressBook();
	
	// Check if third argument is a group and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[3], "third", rec_group, &groupRecord) ) {
		return TCL_ERROR;
	}
	
	// Check if fourth argument is a person and get the corresponding record
	if ( !TclAB_CheckItem(interp, ab, objv[4], "fourth", rec_person, &personRecord) ) {
		CFRelease(groupRecord);
		return TCL_ERROR;
	} 
	
	Tcl_IncrRefCount(objv[5]);
	
	// Make CFString object for property
	propertyRef = TclAB_TclObjToCFString(objv[5]);		
	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, rec_person) ) {
		Tcl_SetResult(interp, "not a multi-string list property", TCL_STATIC);
		result = TCL_ERROR;
		goto DONE;
	} 
	
	if (objc == 7) {
		Tcl_IncrRefCount(objv[6]);
		newvalueRef = TclAB_TclObjToCFString(objv[6]);		
		if (!newvalueRef) {
			Tcl_SetResult(interp, "can't get identifier", TCL_STATIC);
			result = TCL_ERROR;
			goto DONE;
		} else {
			if (!ABGroupSetDistributionIdentifier(groupRecord, personRecord, propertyRef, newvalueRef)) {
				Tcl_AppendResult(interp, "can't set distribution identifier for '", 
									   Tcl_GetString(objv[5]), "' property", (char *) NULL);
				result = TCL_ERROR;
			} else {
				Tcl_SetObjResult(interp, objv[5]);
			}
			CFRelease(newvalueRef);
		}
	} else {
		CFStringRef	identRef = ABGroupCopyDistributionIdentifier(groupRecord, personRecord, propertyRef);
		if (identRef) {
			Tcl_SetObjResult(interp, TclAB_CFStringToTclObj(identRef));
			CFRelease(identRef);
		} else {
			Tcl_AppendResult(interp, "can't get distribution identifier of '", 
								   Tcl_GetString(objv[5]), "' property for this record", (char *) NULL);
			result = TCL_ERROR;
		}
	}
	
DONE:
	Tcl_DecrRefCount(objv[5]);
	if (objc == 7) Tcl_DecrRefCount(objv[6]);
	if (propertyRef != NULL) CFRelease(propertyRef);
	CFRelease(groupRecord);
	CFRelease(personRecord);
	return result;
}
 				   
				   
int	
TclAB_GetSearchComparison(Tcl_Interp *interp, Tcl_Obj * inObj, Boolean isNumeric, Boolean noCase, 
						  ABSearchComparison * outComparison)
{
	ABSearchComparison	comparison = kABEqual;
	int					opIndex;
	
	static CONST char *searchOpSwitches[] = {
		"==", "!=", "<", "<=", ">", ">=", "^=", "=$", "!<", "!>", (char *) NULL
	};
	
	enum {
		TCLAB_SEARCHOP_EQUAL, TCLAB_SEARCHOP_NOTEQUAL, TCLAB_SEARCHOP_LESSTHAN, 
		TCLAB_SEARCHOP_LESSTHANOREQUAL, TCLAB_SEARCHOP_GREATERTHAN, 
		TCLAB_SEARCHOP_GREATERTHANOREQUAL, TCLAB_SEARCHOP_PREFIXMATCH,
		TCLAB_SEARCHOP_SUFFIXMATCH, TCLAB_SEARCHOP_NOTLESSTHAN, 
		TCLAB_SEARCHOP_NOTGREATERTHAN
	};
	
	if (Tcl_GetIndexFromObj(interp, inObj, searchOpSwitches, "operator", 0, &opIndex) != TCL_OK) {
		return TCL_ERROR;
	} 
	switch (opIndex) {
		case TCLAB_SEARCHOP_EQUAL:
		if (!isNumeric && noCase) {
			comparison = kABEqualCaseInsensitive;
		} else {
			comparison = kABEqual;
		}
		break;
		
		case TCLAB_SEARCHOP_NOTEQUAL:
		if (!isNumeric && noCase) {
			comparison = kABNotEqualCaseInsensitive;
		} else {
			comparison = kABNotEqual;
		}
		break;
		
		case TCLAB_SEARCHOP_LESSTHAN:
		case TCLAB_SEARCHOP_LESSTHANOREQUAL:
		if (isNumeric) {
			if (opIndex == TCLAB_SEARCHOP_LESSTHAN) {
				comparison = kABLessThan;
			} else {
				comparison = kABLessThanOrEqual;
			} 
		} else {
			Tcl_SetResult(interp, "< and <= not applicable for string properties", TCL_STATIC);
			return TCL_ERROR;
		}
		break;
		
		case TCLAB_SEARCHOP_GREATERTHAN:
		case TCLAB_SEARCHOP_GREATERTHANOREQUAL:
		if (isNumeric) {
			if (opIndex == TCLAB_SEARCHOP_GREATERTHAN) {
				comparison = kABGreaterThan;
			} else {
				comparison = kABGreaterThanOrEqual;
			} 
		} else if (noCase) {
			comparison = kABContainsSubStringCaseInsensitive;
		} else {
			comparison = kABContainsSubString;
		}
		break;
		
		case TCLAB_SEARCHOP_NOTLESSTHAN:
		if (isNumeric) {
			comparison = kABGreaterThanOrEqual;
		} else {
			Tcl_SetResult(interp, "!< not applicable for string properties", TCL_STATIC);
			return TCL_ERROR;
		}
		break;
		
		case TCLAB_SEARCHOP_NOTGREATERTHAN:
		if (isNumeric) {
			comparison = kABLessThanOrEqual;
		} else if (noCase) {
			comparison = kABDoesNotContainSubStringCaseInsensitive;
		} else {
			comparison = kABDoesNotContainSubString;
		}
		break;
		
		case TCLAB_SEARCHOP_PREFIXMATCH:
		if (isNumeric) {
			Tcl_SetResult(interp, "prefix matching not applicable for numeric properties", TCL_STATIC);
			return TCL_ERROR;
		} else if (noCase) {
			comparison = kABPrefixMatchCaseInsensitive;
		} else {
			comparison = kABPrefixMatch;
		}
		break;
		
		case TCLAB_SEARCHOP_SUFFIXMATCH:
		if (isNumeric) {
			Tcl_SetResult(interp, "suffix matching not applicable for numeric properties", TCL_STATIC);
			return TCL_ERROR;
		} else if (noCase) {
			comparison = kABSuffixMatchCaseInsensitive;
		} else {
			comparison = kABSuffixMatch;
		}
		break;
		
	}
	
	*outComparison = comparison;
	
	return TCL_OK;
}
