// File : "TclABUtils.c"
//                        Created: 2003-12-05 10:08:03
//              Last modification: 2009-03-11 12:27:43
// 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.
// Versions history: see the Changes.Log file.


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



/*
 *----------------------------------------------------------------------
 *
 * TclAB_MakeValueRefFromTclObj --
 *
 *	This function is invoked to build a CF object representing a value or 
 *	multivalue from a Tcl variable or list. 
 *  
 *----------------------------------------------------------------------
 */

bool
TclAB_MakeValueRefFromTclObj(Tcl_Interp *interp, Tcl_Obj * inValue, ABPropertyType inPropType, CFTypeRef * outValue)
{
	bool		result = false;
	
	*outValue = NULL;
	
	switch (inPropType) {
		case kABStringProperty:
		*outValue = (CFTypeRef) TclAB_TclObjToCFString(inValue);
		result = (*outValue != NULL);
		break;
		
		
		case kABDateProperty: {
			CFAbsoluteTime		theAbsTime;
			if (Tcl_GetDoubleFromObj(interp, inValue, &theAbsTime) == TCL_OK) {
				theAbsTime -= kCFAbsoluteTimeIntervalSince1970;
				*outValue = CFDateCreate(NULL, theAbsTime);
				result = (*outValue != NULL);
			}
		}
		break;
		
		
		case kABIntegerProperty: {
			int			theInt;
			if (Tcl_GetIntFromObj(interp, inValue, &theInt) == TCL_OK) {
				*outValue = CFNumberCreate(NULL, kCFNumberSInt32Type, &theInt);
				result = (*outValue != NULL);
			}
		}
		break;
		
		
		case kABRealProperty: {
			double		theDouble;
			if (Tcl_GetDoubleFromObj(interp, inValue, &theDouble) == TCL_OK) {
				*outValue = CFNumberCreate(NULL, kCFNumberDoubleType, &theDouble);
				result = (*outValue != NULL);
			}
		}
		break;
		
		
		case kABDataProperty: {
			int				byteLen;
			const UInt8 *	theBytes;
			
			theBytes = Tcl_GetByteArrayFromObj(inValue, &byteLen);
			if (theBytes != NULL) {
				*outValue = CFDataCreate(NULL, theBytes, byteLen);
				result = (*outValue != NULL);
			}
		}
		break;
		
		
		case kABArrayProperty: {
			int					i, len = 1;
			Tcl_Obj	*			theObj;
			CFStringRef			theString;
			CFMutableArrayRef	theArray = NULL;
			
			theArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
			if (theArray != NULL) {
				if (Tcl_ListObjLength(interp, inValue, &len) == TCL_OK) {
					for (i = 0; i < len; i++) {
						if (Tcl_ListObjIndex(interp, inValue, i, &theObj) == TCL_OK) {
							theString = TclAB_TclObjToCFString(theObj);
							if (theString) {
								CFArrayAppendValue(theArray, theString);
								CFRelease(theString);
							} 
						} 
					}
				} 
			} 
			*outValue = theArray;
			result = (*outValue != NULL);
		}
		break;
		
		
		case kABDictionaryProperty:
		result = TclAB_MakeDictRefFromTclObj(interp, inValue, inPropType, outValue);
		break;
		
		
		case kABMultiStringProperty:
		case kABMultiDateProperty:
		case kABMultiIntegerProperty:
		case kABMultiRealProperty:
		case kABMultiDataProperty:
		case kABMultiArrayProperty:
		case kABMultiDictionaryProperty:
		result = TclAB_MakeMultiValueFromTclObj(interp, inValue, inPropType, outValue);
		break;
		
		
		case kABErrorInProperty:
		default:
		break;
	}
	
	return result;
}

	
bool
TclAB_MakeMultiValueFromTclObj(Tcl_Interp *interp, Tcl_Obj * inValue, ABPropertyType inPropType, CFTypeRef * outValue)
{
	CFTypeRef				subLabel = NULL, subValue = NULL;
	ABMutableMultiValueRef 	multiValue;
	int						j, listLen, elemLen;
	Tcl_Obj					*elemPtr, *labelPtr, *valuePtr;
	bool					result = false;
	ABPropertyType			singlePropertyType;

	// Create a mutable multiValue
	multiValue = ABMultiValueCreateMutable();
	*outValue = multiValue;
	result = (*outValue != NULL);

	if (multiValue) {
		Tcl_IncrRefCount(inValue);
		Tcl_ListObjLength(interp, inValue, &listLen);
		singlePropertyType = (inPropType & ~kABMultiValueMask);

		// Parse the value as a Tcl list of two elements sublists and
		// loop over all the pairs
		for (j = 0; j < listLen; j++) {
			Tcl_ListObjIndex(interp, inValue, j, &elemPtr);
			
			if (elemPtr) {
				Tcl_IncrRefCount(elemPtr);
				
				Tcl_ListObjLength(interp, elemPtr, &elemLen);
				if (elemLen == 2) {
					Tcl_ListObjIndex(interp, elemPtr, 0, &labelPtr);
					Tcl_ListObjIndex(interp, elemPtr, 1, &valuePtr);
					
					if (labelPtr && valuePtr) {
						Tcl_IncrRefCount(labelPtr);
						Tcl_IncrRefCount(valuePtr);
						
						// Make CF value out of the Tcl objects
						result = TclAB_MakeValueRefFromTclObj(interp, valuePtr, singlePropertyType, &subValue);
						if (result) {
							subLabel = (CFTypeRef) TclAB_TclObjToCFString(labelPtr);
							// Add the value/label pair to the multivalue
							if (subValue) {
								result = ABMultiValueAdd(multiValue, subValue, subLabel, NULL);
							} 
						} 
						if (subValue) CFRelease(subValue);
						if (subLabel) CFRelease(subLabel);
						subValue = NULL;
						subLabel = NULL;
						Tcl_DecrRefCount(labelPtr);
						Tcl_DecrRefCount(valuePtr);
					} 
				}
				
				Tcl_DecrRefCount(elemPtr);
			} 
		}
		Tcl_DecrRefCount(inValue);
	} 
	
	return result;
}


bool
TclAB_MakeDictRefFromTclObj(Tcl_Interp *interp, Tcl_Obj * inValue, ABPropertyType inPropType, CFTypeRef * outValue)
{
	int						k, count, listLen, elemLen;
	bool					result = false;
	Tcl_Obj					*elemPtr, *keyPtr, *valuePtr;
	CFDictionaryRef			theDict = NULL;
	CFStringRef				keyRef, valueRef;
	CFStringRef *			theKeys;
	CFStringRef *			theVals;
		
	// The dictionary is represented as a list of two-items sublists, each
	// one corresponding to a key/value pair
	Tcl_IncrRefCount(inValue);
	Tcl_ListObjLength(interp, inValue, &listLen);
	
	// Allocate memory to store the keys and values
	theKeys = (CFStringRef*) NewPtrClear(sizeof(CFStringRef) * listLen);
	theVals = (CFStringRef*) NewPtrClear(sizeof(CFStringRef) * listLen);
	if ((theKeys != NULL) && (theVals != NULL)) {
		// Loop over all the elements of the flat dict list
		count = 0;
		for (k = 0; k < listLen; k++) {
			Tcl_ListObjIndex(interp, inValue, k, &elemPtr);
			if (elemPtr) {
				Tcl_ListObjLength(interp, elemPtr, &elemLen);
				if (elemLen == 2) {
					Tcl_IncrRefCount(elemPtr);
					Tcl_ListObjIndex(interp, elemPtr, 0, &keyPtr);
					Tcl_ListObjIndex(interp, elemPtr, 1, &valuePtr);
					
					// Make CFStrings out of the Tcl objects
					keyRef = TclAB_TclObjToCFString(keyPtr);
					valueRef = TclAB_TclObjToCFString(valuePtr);
					if (keyRef && valueRef) {
						theKeys[count] = keyRef;
						theVals[count] = valueRef;
						count++;
					} 
					Tcl_DecrRefCount(elemPtr);
				}
			} 
		}
		if (count > 0) {
			// Create and populate a CFDictionary with the key/value pairs
			theDict = CFDictionaryCreate(NULL, (const void **) theKeys, (const void **) theVals, count,  
										 &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		} 
	}
	if (theKeys) DisposePtr( (Ptr) theKeys);
	if (theVals) DisposePtr( (Ptr) theVals);
	
	*outValue = theDict;
	result = (*outValue != NULL);
	Tcl_DecrRefCount(inValue);
	
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_TypeFromRecord --
 *
 *	This function is invoked to determine the type (group or person) 
 *  corresponding to a particular record reference.
 *	
 *----------------------------------------------------------------------
 */

int
TclAB_TypeFromRecord(ABAddressBookRef ab, ABRecordRef inRecord)
{
	CFStringRef		typeRef;
	int				kind = rec_undefined;
	
	// Get the address book
	ab = ABGetSharedAddressBook();

	if (inRecord != NULL) {
		typeRef = ABRecordCopyRecordType(inRecord);
		if (typeRef != NULL) {
			if ( CFStringCompare(typeRef, CFSTR("ABPerson"), 0) == kCFCompareEqualTo ) {
				kind = rec_person;
			} else if ( CFStringCompare(typeRef, CFSTR("ABGroup"), 0) == kCFCompareEqualTo ) {
				kind = rec_group;
			} 
			CFRelease(typeRef);
		}
	} 
	
	return kind;
}
	

/*
 *----------------------------------------------------------------------
 *
 * TclAB_RecordFromUID --
 *
 *	This function is invoked to determine the record corresponding 
 *  to a particular UID. 
 *  
 * Results:
 *	A boolean (true if succeeded, false otherwise)
 *
 * Side effects:
 *	The caller is responsible for releasing the inUID CFStringRef.
 *	
 *----------------------------------------------------------------------
 */

bool
TclAB_RecordFromUID(Tcl_Interp *interp, ABAddressBookRef ab, CFStringRef inUID, ABRecordRef * outRecordRef)
{
	ABRecordRef	recordRef = NULL;
	
	if (inUID) {
		recordRef = ABCopyRecordForUniqueId(ab, inUID);
	} 
	*outRecordRef = recordRef;
	if (recordRef == NULL) {
		Tcl_Obj *	theObj = Tcl_NewStringObj("unrecognized record ID ", -1);
		if (inUID) {
			Tcl_AppendObjToObj(theObj, TclAB_CFStringToTclObj(inUID));
		} 
		Tcl_SetObjResult(interp, theObj);
	}  
	return (recordRef != NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_CheckItem --
 *
 *	This function is invoked to check the validity of an item's UID 
 *	(person or group) and returns the corresponding record reference in 
 *	the outItemRef argument.
 *  
 * Side effects:
 *	Note that the function takes care of releasing the recordRef if it has 
 *	been found but does not have the required type. This is because the 
 *	caller, when receiving a result "false", cannot distinguish this 
 *	situation from the case where the record itself could not be found.
 *	
 *----------------------------------------------------------------------
 */

bool
TclAB_CheckItem(Tcl_Interp *interp, ABAddressBookRef ab, Tcl_Obj * inUID,
				CONST84 char * inWhich, int inRecordType, ABRecordRef * outItemRef)
{
	CFStringRef			uidRef;
	int					kind;
	bool				result = true;
	
	if (inUID == NULL) {
		return false;
	} 
	
	uidRef = TclAB_TclObjToCFString(inUID);
	if (uidRef != NULL) {
		// Find the record corresponding to the UID
		*outItemRef = ABCopyRecordForUniqueId(ab, uidRef);
		CFRelease(uidRef);
		
		if (!*outItemRef) {
			Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "unrecognized record ID in ", 
								   inWhich, " argument", (char *) NULL);
			result = false;
		} else {
			if (inRecordType == rec_undefined) {
				return true;
			} else {
				// Is it the expected type (person or group)?
				kind = TclAB_TypeFromRecord(ab, *outItemRef);
				
				if (kind != inRecordType) {
					// CFRelease the record. The caller won't have to release it.
					CFRelease(*outItemRef);
					Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "not a ", 
										   inRecordType == rec_person ? "person":"group", " ID in ", 
										   inWhich, " argument", (char *) NULL);
					result = false;
				} 
			}
		}
	} else {
		result = false;
	}
	
	return result;
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_CheckMultiValueProperty --
 *
 *	This function is invoked to check if property is a multi-value 
 *	property. Used by the [addressbook identifier] and 
 *	[addressbook label] commands.
 *  
 * kABMultiStringProperty = 0x100 | kABStringProperty,
 * kABMultiIntegerProperty = 0x100 | kABIntegerProperty,
 * kABMultiRealProperty = 0x100 | kABRealProperty,
 * kABMultiDateProperty = 0x100 | kABDateProperty,
 * kABMultiArrayProperty = 0x100 | kABArrayProperty,
 * kABMultiDictionaryProperty = 0x100 | kABDictionaryProperty,
 * kABMultiDataProperty = 0x100 | kABDataProperty
 *	
 *----------------------------------------------------------------------
 */

bool
TclAB_CheckMultiValueProperty(ABAddressBookRef ab, CFStringRef inPropertyRef, int inRecordType)
{
	ABPropertyType	thePropType;

	if (inRecordType == rec_person) {
		thePropType = ABTypeOfProperty(ab, kABPersonRecordType, inPropertyRef);
	} else {
		thePropType = ABTypeOfProperty(ab, kABGroupRecordType, inPropertyRef);
	}

	// All multivalue constants are above 0x100 = 256
	return (thePropType > 256);
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_SetResultWithArrayOfRecords --
 *
 *	This function is invoked to build a Tcl list from a CFArray.
 *  
 *----------------------------------------------------------------------
 */

void
TclAB_SetResultWithArrayOfRecords(Tcl_Interp *interp, CFArrayRef allElements, 
							Boolean onlyID, int inRecordType)
{
	CFIndex 		theCount;
	ABRecordRef	 	theRecord;
	CFStringRef		nameRef = NULL, uniqueId = NULL;
	Tcl_Obj *		listObj;
	Tcl_Obj *		subListObj;
	int				result, i;
	
    Tcl_ResetResult(interp);
	if (!allElements) {
		return;
	} 
	
	theCount = CFArrayGetCount(allElements);
	listObj = Tcl_NewListObj(0, NULL);
	for (i = 0; i < theCount; i++) {
		// Get the record at index i
		theRecord = (ABRecordRef) CFArrayGetValueAtIndex(allElements,i);
		// Get the unique id for this record
		uniqueId = ABRecordCopyUniqueId(theRecord);	
		if (uniqueId != NULL) {
			if (onlyID) {
				goto LISTONLYID;
			} 
			// Try to get the entry for the kABGroupNameProperty. If that fails, just list the UID
			if (inRecordType == rec_group) {
				nameRef = ABRecordCopyValue(theRecord, kABGroupNameProperty);
			} else if (inRecordType == rec_person) {
				nameRef = ABRecordCopyValue(theRecord, kABLastNameProperty);
			} 
			
			if (nameRef) {
				Tcl_Obj *	objs[2];
				
				objs[0] = TclAB_CFStringToTclObj(uniqueId);				
				objs[1] = TclAB_CFStringToTclObj(nameRef);			
				subListObj = Tcl_NewListObj(2, objs);
				result = Tcl_ListObjAppendElement(interp, listObj, subListObj);
				CFRelease(nameRef);
			} else {
				LISTONLYID:
				result = Tcl_ListObjAppendElement(interp, listObj, TclAB_CFStringToTclObj(uniqueId));
			}
			CFRelease(uniqueId);
			if (result != TCL_OK) {
				break;
			}
		} 
	}
	Tcl_SetObjResult(interp, listObj);
}


/*
 *----------------------------------------------------------------------
 *
 * TclAB_MakeSearchRefFromTcl --
 *
 *	This function is invoked to build a CF object representing a value to
 *	search from a Tcl variable or list. 
 *  
 *----------------------------------------------------------------------
 */

bool
TclAB_MakeSearchRefFromTcl(Tcl_Interp *interp, Tcl_Obj * inValue, ABPropertyType inPropType,
						CFStringRef * outLabel, CFStringRef * outKey, CFTypeRef * outValue)
{
	int					listLen, subListLen;
    Tcl_Obj				*labelPtr, *dictPtr, *keyPtr, *valuePtr;
	bool				result = false;
	ABPropertyType		singlePropertyType;
	
	*outLabel = NULL;
	*outKey = NULL;
	*outValue = NULL;
	
	switch (inPropType) {
		case kABStringProperty:
		case kABDateProperty:
		case kABIntegerProperty:
		case kABRealProperty:
		case kABDataProperty:
		case kABArrayProperty:
		result = TclAB_MakeValueRefFromTclObj(interp, inValue, inPropType, outValue);
		break;


		case kABMultiStringProperty:
		case kABMultiDateProperty:
		case kABMultiIntegerProperty:
		case kABMultiRealProperty:		
		case kABMultiDataProperty:
		case kABMultiArrayProperty:
		if (inValue) {
			Tcl_IncrRefCount(inValue);
			Tcl_ListObjLength(interp, inValue, &listLen);
			
			// The value should be a two-elements Tcl list representing a
			// label/value pair
			if (listLen == 2) {
				Tcl_ListObjIndex(interp, inValue, 0, &labelPtr);
				Tcl_ListObjIndex(interp, inValue, 1, &valuePtr);
				singlePropertyType = (inPropType & ~kABMultiValueMask);
				
				if (labelPtr && valuePtr) {
					Tcl_IncrRefCount(labelPtr);
					Tcl_IncrRefCount(valuePtr);
					
					// Make CF value out of the Tcl objects
					result = TclAB_MakeValueRefFromTclObj(interp, valuePtr, singlePropertyType, outValue);
					result = (*outValue != NULL);
					if (Tcl_GetCharLength(labelPtr)) {
						*outLabel = TclAB_TclObjToCFString(labelPtr);
					}
					Tcl_DecrRefCount(labelPtr);
					Tcl_DecrRefCount(valuePtr);
				} 
			} else {
				// Malformed value
				Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "malformed argument. Should be: {label value}", (char *) NULL);
			}
			Tcl_DecrRefCount(inValue);
		} else {
			Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "invalid value", (char *) NULL);
		}
		break;

		
		case kABDictionaryProperty:
		case kABMultiDictionaryProperty:
		if (inValue) {
			// The value should be a two-elements Tcl list representing a
			// single label/dictionary pair
			Tcl_IncrRefCount(inValue);
			Tcl_ListObjLength(interp, inValue, &listLen);
			if (listLen == 2) {
				Tcl_ListObjIndex(interp, inValue, 0, &labelPtr);
				Tcl_ListObjIndex(interp, inValue, 1, &dictPtr);

				if (labelPtr && dictPtr) {
					// The dictionary itself should be a two-elements Tcl
					// list representing a single key/value pair.
					Tcl_IncrRefCount(labelPtr);
					Tcl_IncrRefCount(dictPtr);
					Tcl_ListObjLength(interp, dictPtr, &subListLen);
					if (subListLen == 2) {
						Tcl_ListObjIndex(interp, dictPtr, 0, &keyPtr);
						Tcl_ListObjIndex(interp, dictPtr, 1, &valuePtr);
						
						if (keyPtr && valuePtr) {
							Tcl_IncrRefCount(keyPtr);
							Tcl_IncrRefCount(valuePtr);

							// Make CFStrings out of the Tcl objects
							*outValue = TclAB_TclObjToCFString(valuePtr);
							result = (*outValue != NULL);
							if (Tcl_GetCharLength(keyPtr)) {
								*outKey = TclAB_TclObjToCFString(keyPtr);
							}
							Tcl_DecrRefCount(keyPtr);
							Tcl_DecrRefCount(valuePtr);
						} else {
							Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "can't get key or value", (char *) NULL);
						}
					} else {
						// Malformed value
						Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "malformed inner list. Should be: {key value}", (char *) NULL);
					}
					if (result && Tcl_GetCharLength(labelPtr) != 0) {
						*outLabel = TclAB_TclObjToCFString(labelPtr);
					}
					Tcl_DecrRefCount(labelPtr);
					Tcl_DecrRefCount(dictPtr);
				} 
			} else {
				// Malformed value
				Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "malformed outer list. Should be: {label {key value}}", (char *) NULL);
			}
			Tcl_DecrRefCount(inValue);
		} else {
			Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "can't make string from value", (char *) NULL);
		}
		break;

		
		case kABErrorInProperty:
		default:
		break;
	}

	return result;
}



// Tcl_Obj <> CFStringRef conversions
// ==================================


CFMutableStringRef TclAB_CFStringNormalize(CFStringRef theString, CFStringNormalizationForm theForm)
{
	CFMutableStringRef theMutableString = CFStringCreateMutableCopy(NULL, 0, theString);
	if (theMutableString) {
		CFStringNormalize(theMutableString, theForm);
	}
	return theMutableString;

}


CFStringRef TclAB_TclObjToCFString(Tcl_Obj * inObj)
{
	CFStringRef		theString = NULL;
	if (inObj != NULL) {		
		theString = CFStringCreateWithCharacters(NULL, Tcl_GetUnicode(inObj), Tcl_GetCharLength(inObj));
	}
	return theString;
}


Tcl_Obj * TclAB_CFStringToTclObj(CFStringRef inStrRef)
{
	Tcl_Obj *			outObj = NULL;
	CFIndex 			len;
	const UniChar *		chars;
	
	len = CFStringGetLength(inStrRef);
	chars = CFStringGetCharactersPtr(inStrRef);
	
	if (chars != NULL) {
		outObj = Tcl_NewUnicodeObj(chars, len);
	} else {
		UniChar * buffer = (UniChar*) ckalloc(len * sizeof(UniChar));
		CFStringGetCharacters(inStrRef, CFRangeMake(0, len), buffer);
		outObj = Tcl_NewUnicodeObj(buffer, len);
		ckfree((char*) buffer);
	}
	
	return outObj;
}




