Boolean HIDSaveElementPref( const CFStringRef inKeyCFStringRef,
						   CFStringRef inAppCFStringRef,
						   IOHIDDeviceRef inIOHIDDeviceRef,
						   IOHIDElementRef inIOHIDElementRef )
{
	Boolean success = FALSE;
	
	if ( inKeyCFStringRef && inAppCFStringRef && inIOHIDDeviceRef && inIOHIDElementRef ) {
		long vendorID = IOHIDDevice_GetVendorID( inIOHIDDeviceRef );
		require( vendorID, Oops );
		
		long productID = IOHIDDevice_GetProductID( inIOHIDDeviceRef );
		require( productID, Oops );
		
		long locID = IOHIDDevice_GetLocationID( inIOHIDDeviceRef );
		require( locID, Oops );
		
		uint32_t usagePage = IOHIDDevice_GetUsagePage( inIOHIDDeviceRef );
		uint32_t usage = IOHIDDevice_GetUsage( inIOHIDDeviceRef );
		
		if ( !usagePage || !usage ) {
			usagePage = IOHIDDevice_GetPrimaryUsagePage( inIOHIDDeviceRef );
			usage = IOHIDDevice_GetPrimaryUsage( inIOHIDDeviceRef );
		}
		require( usagePage && usage, Oops );
		
		uint32_t usagePageE = IOHIDElementGetUsagePage( inIOHIDElementRef );
		uint32_t usageE = IOHIDElementGetUsage( inIOHIDElementRef );
		IOHIDElementCookie eleCookie = IOHIDElementGetCookie( inIOHIDElementRef );
		
		CFStringRef prefCFStringRef = CFStringCreateWithFormat( kCFAllocatorDefault, NULL,
															   CFSTR( "d:{v:%ld, p:%ld, l:%ld, p:%ld, u:%ld}, e:{p:%ld, u:%ld, c:%ld}" ),
															   vendorID,
															   productID,
															   locID,
															   usagePage,
															   usage,
															   usagePageE,
															   usageE,
															   eleCookie );
		
		if ( prefCFStringRef ) {
			CFPreferencesSetAppValue( inKeyCFStringRef, prefCFStringRef, inAppCFStringRef );
			CFRelease( prefCFStringRef );
			success = TRUE;
		}
	}
Oops:   ;
	return success;
}   // HIDSaveElementPref
// Set up a config record for saving
// takes an input records, returns record user can save as they want
// Note: the save rec must be pre-allocated by the calling app and will be filled out
void HIDSetElementConfig(HID_info_ptr		inHIDInfoPtr,
                         IOHIDDeviceRef		inIOHIDDeviceRef,
                         IOHIDElementRef	inIOHIDElementRef,
                         IOHIDElementCookie actionCookie) {
	// must save:
	// actionCookie
	// Device: serial,vendorID, productID, location, usagePage, usage
	// Element: cookie, usagePage, usage,
	inHIDInfoPtr->actionCookie = actionCookie;
	// device
	// need to add serial number when I have a test case
	if (inIOHIDDeviceRef &&
	    inIOHIDElementRef)
	{
		inHIDInfoPtr->device.vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
		inHIDInfoPtr->device.productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
		inHIDInfoPtr->device.locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef);
		inHIDInfoPtr->device.usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef);
		inHIDInfoPtr->device.usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef);
		if (!inHIDInfoPtr->device.usagePage ||
		    !inHIDInfoPtr->device.usage)
		{
			inHIDInfoPtr->device.usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef);
			inHIDInfoPtr->device.usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef);
		}

		inHIDInfoPtr->element.usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef);
		inHIDInfoPtr->element.usage = IOHIDElementGetUsage(inIOHIDElementRef);
		inHIDInfoPtr->element.minReport = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef);
		inHIDInfoPtr->element.maxReport = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef);
		inHIDInfoPtr->element.cookie = IOHIDElementGetCookie(inIOHIDElementRef);
	} else {
		inHIDInfoPtr->device.vendorID = 0;
		inHIDInfoPtr->device.productID = 0;
		inHIDInfoPtr->device.locID = 0;
		inHIDInfoPtr->device.usage = 0;
		inHIDInfoPtr->device.usagePage = 0;

		inHIDInfoPtr->element.usagePage = 0;
		inHIDInfoPtr->element.usage = 0;
		inHIDInfoPtr->element.minReport = 0;
		inHIDInfoPtr->element.maxReport = 0;
		inHIDInfoPtr->element.cookie = 0;
	}
}                                                                               // HIDSetElementConfig
/*
    PsychHIDGetDeviceListByUsages()
 */
void PsychHIDGetDeviceListByUsages(int numUsages, long *usagePages, long *usages, int *numDeviceIndices, int *deviceIndices, pRecDevice *deviceRecords)
{
    pRecDevice currentDevice;
    int        currentDeviceIndex;
    int        currentUsage;
    long       *usagePage;
    long       *usage;

    PsychHIDVerifyInit();
    *numDeviceIndices = 0;
    for (usagePage = usagePages, usage = usages, currentUsage = 0; currentUsage < numUsages; usagePage++, usage++, currentUsage++) {
        currentDeviceIndex = 0;
        for (currentDevice = HIDGetFirstDevice(); currentDevice != NULL; currentDevice = HIDGetNextDevice(currentDevice)) {
            ++currentDeviceIndex;
            if (IOHIDDevice_GetPrimaryUsagePage(currentDevice) == *usagePage && IOHIDDevice_GetPrimaryUsage(currentDevice) == *usage) {
                deviceRecords[*numDeviceIndices] = currentDevice;
                deviceIndices[*numDeviceIndices] = currentDeviceIndex;  //the array is 0-indexed, devices are 1-indexed.
                ++(*numDeviceIndices);
            }
        }
    }
}
Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo,
                                IOHIDDeviceRef *	outFoundDevice,
                                IOHIDElementRef *	outFoundElement) {
	Boolean result = false;

	IOHIDDeviceRef bestIOHIDDeviceRef = NULL;
	IOHIDElementRef bestIOHIDElementRef = NULL;
	int bestScore = 0;

	CFIndex devIndex,
	        devCount = CFArrayGetCount(gDeviceCFArrayRef);

	for (devIndex = 0; devIndex < devCount; devIndex++) {
		int deviceScore = 1;

		IOHIDDeviceRef tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
		                                                                         devIndex);
		if (!tIOHIDDeviceRef) {
			continue;
		}
		// match vendorID, productID (+10, +8)
		if (inSearchInfo->device.vendorID) {
			uint32_t vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef);
			if (vendorID) {
				if (inSearchInfo->device.vendorID == vendorID) {
					deviceScore += 10;
					if (inSearchInfo->device.productID) {
						uint32_t productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef);
						if (productID) {
							if (inSearchInfo->device.productID == productID) {
								deviceScore += 8;
							}                                                   // if (inSearchInfo->device.productID == productID)
						}                                                       // if (productID)
					}                                                           // if (inSearchInfo->device.productID)
				}                                                               // if (inSearchInfo->device.vendorID == vendorID)
			}                                                                   // if vendorID
		}                                                                       // if search->device.vendorID
		                                                                        // match usagePage & usage (+9)
		if (inSearchInfo->device.usagePage &&
		    inSearchInfo->device.usage)
		{
			uint32_t usagePage = IOHIDDevice_GetUsagePage(tIOHIDDeviceRef);
			uint32_t usage = IOHIDDevice_GetUsage(tIOHIDDeviceRef);
			if (!usagePage ||
			    !usage)
			{
				usagePage = IOHIDDevice_GetPrimaryUsagePage(tIOHIDDeviceRef);
				usage = IOHIDDevice_GetPrimaryUsage(tIOHIDDeviceRef);
			}
			if (usagePage) {
				if (inSearchInfo->device.usagePage == usagePage) {
					if (usage) {
						if (inSearchInfo->device.usage == usage) {
							deviceScore += 9;
						}                                                       // if (inSearchInfo->usage == usage)
					}                                                           // if (usage)
				}                                                               // if (inSearchInfo->usagePage == usagePage)
			}                                                                   // if (usagePage)
		}                                                                       // if (inSearchInfo->usagePage &&
		                                                                        // inSearchInfo->usage)
		                                                                        // match location ID (+5)
		if (inSearchInfo->device.locID) {
			uint32_t locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef);
			if (locID) {
				if (inSearchInfo->device.locID == locID) {
					deviceScore += 5;
				}
			}
		}

		// iterate over all elements of this device
		gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef,
		                                                     NULL,
		                                                     0);
		if (gElementCFArrayRef) {
			CFIndex eleIndex,
			        eleCount = CFArrayGetCount(gElementCFArrayRef);
			for (eleIndex = 0; eleIndex < eleCount; eleIndex++) {
				IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
				                                                                            eleIndex);
				if (!tIOHIDElementRef) {
					continue;
				}

				int score = deviceScore;
				// match usage page, usage & cookie
				if (inSearchInfo->element.usagePage &&
				    inSearchInfo->element.usage)
				{
					uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef);
					if (inSearchInfo->element.usagePage == usagePage) {
						uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef);
						if (inSearchInfo->element.usage == usage) {
							score += 5;
							IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef);
							if (inSearchInfo->element.cookie == cookie) {
								score += 4;
							}                                                   // cookies match
						} else {
							score = 0;
						}                                                       // usages match
					} else {
						score = 0;
					}                                                           // usage pages match
				}                                                               // if (search usage page & usage)

#if LOG_SCORING
				if (kHIDPage_KeyboardOrKeypad != tElementRef->usagePage) {      // skip keyboards here
					printf("%s: (%ld:%ld)-I-Debug, score: %ld\t",
					       __PRETTY_FUNCTION__,
					       inSearchInfo->element.usagePage,
					       inSearchInfo->element.usage,
					       score);
					HIDPrintElement(tIOHIDElementRef);
				}

#endif                                                                          // LOG_SCORING
				if (score > bestScore) {
					bestIOHIDDeviceRef = tIOHIDDeviceRef;
					bestIOHIDElementRef = tIOHIDElementRef;
					bestScore = score;
#if LOG_SCORING
					printf("%s: (%ld:%ld)-I-Debug, better score: %ld\t",
					       __PRETTY_FUNCTION__,
					       inSearchInfo->element.usagePage,
					       inSearchInfo->element.usage,
					       score);
					HIDPrintElement(bestIOHIDElementRef);
#endif                                                                          // LOG_SCORING
				}
			}                                                                   // for elements...

			CFRelease(gElementCFArrayRef);
			gElementCFArrayRef = NULL;
		}                                                                       // if (gElementCFArrayRef)
	}                                                                           // for (devIndex = 0; devIndex < devCount;
	                                                                            // devIndex++)
	if (bestIOHIDDeviceRef ||
	    bestIOHIDElementRef)
	{
		*outFoundDevice = bestIOHIDDeviceRef;
		*outFoundElement = bestIOHIDElementRef;
#if LOG_SCORING
		printf("%s: (%ld:%ld)-I-Debug, best score: %ld\t",
		       __PRETTY_FUNCTION__,
		       inSearchInfo->element.usagePage,
		       inSearchInfo->element.usage,
		       bestScore);
		HIDPrintElement(bestIOHIDElementRef);
#endif                                                                          // LOG_SCORING
		result = true;
	}

	return (result);
}                                                                               // HIDFindDeviceAndElement
// utility routine to dump device info
void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef) {
	char cstring[256];
	
	printf("Device: %p = { ", inIOHIDDeviceRef);
	
	char        manufacturer[256] = ""; // name of manufacturer
	CFStringRef tCFStringRef      = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef);
	if (tCFStringRef) {
		(void) CFStringGetCString(tCFStringRef, manufacturer, sizeof(manufacturer), kCFStringEncodingUTF8);
	}
	
	char product[256] = "";      // name of product
	tCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef);
	if (tCFStringRef) {
		(void) CFStringGetCString(tCFStringRef, product, sizeof(product), kCFStringEncodingUTF8);
	}
	
	printf("%s - %s, ", manufacturer, product);
	
	long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
	if (vendorID) {
		if ( HIDGetVendorNameFromVendorID(vendorID, cstring) ) {
			printf(" vendorID: 0x%04lX (\"%s\"), ", vendorID, cstring);
		} else {
			printf(" vendorID: 0x%04lX, ",          vendorID);
		}
	}
	
	long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
	if (productID) {
		if ( HIDGetProductNameFromVendorProductID(vendorID, productID, cstring) ) {
			printf(" productID: 0x%04lX (\"%s\"), ", productID, cstring);
		} else {
			printf(" productID: 0x%04lX, ",          productID);
		}
	}
	
	uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef);
	uint32_t usage     = IOHIDDevice_GetUsage(inIOHIDDeviceRef);
	if (!usagePage || !usage) {
		usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef);
		usage     = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef);
	}
	
	printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage);
	
	tCFStringRef = HIDCopyUsageName(usagePage, usage);
	if (tCFStringRef) {
		(void) CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8);
		printf("\"%s\", ", cstring);
		CFRelease(tCFStringRef);
	}

	tCFStringRef = IOHIDDevice_GetTransport(inIOHIDDeviceRef);
	if (tCFStringRef) {
		(void) CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8);
		printf("Transport: \"%s\", ", cstring);
	}
	
	long vendorIDSource = IOHIDDevice_GetVendorIDSource(inIOHIDDeviceRef);
	if (vendorIDSource) {
		printf("VendorIDSource: %ld, ", vendorIDSource);
	}
	
	long version = IOHIDDevice_GetVersionNumber(inIOHIDDeviceRef);
	if (version) {
		printf("version: %ld, ", version);
	}
	
	tCFStringRef = IOHIDDevice_GetSerialNumber(inIOHIDDeviceRef);
	if (tCFStringRef) {
		(void) CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8);
		printf("SerialNumber: \"%s\", ", cstring);
	}
	
	long country = IOHIDDevice_GetCountryCode(inIOHIDDeviceRef);
	if (country) {
		printf("CountryCode: %ld, ", country);
	}
	
	long locationID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef);
	if (locationID) {
		printf("locationID: 0x%08lX, ", locationID);
	}
	
#if false
	CFArrayRef pairs = IOHIDDevice_GetUsagePairs(inIOHIDDeviceRef);
	if (pairs) {
		CFIndex idx, cnt = CFArrayGetCount(pairs);
		for (idx = 0; idx < cnt; idx++) {
			const void * pair = CFArrayGetValueAtIndex(pairs, idx);
			CFShow(pair);
		}
	}
#endif // if false

	long maxInputReportSize = IOHIDDevice_GetMaxInputReportSize(inIOHIDDeviceRef);
	if (maxInputReportSize) {
		printf("MaxInputReportSize: %ld, ", maxInputReportSize);
	}
	
	long maxOutputReportSize = IOHIDDevice_GetMaxOutputReportSize(inIOHIDDeviceRef);
	if (maxOutputReportSize) {
		printf("MaxOutputReportSize: %ld, ", maxOutputReportSize);
	}
	
	long maxFeatureReportSize = IOHIDDevice_GetMaxFeatureReportSize(inIOHIDDeviceRef);
	if (maxFeatureReportSize) {
		printf("MaxFeatureReportSize: %ld, ", maxOutputReportSize);
	}
	
	long reportInterval = IOHIDDevice_GetReportInterval(inIOHIDDeviceRef);
	if (reportInterval) {
		printf("ReportInterval: %ld, ", reportInterval);
	}
	
	IOHIDQueueRef queueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
	if (queueRef) {
		printf("queue: %p, ", queueRef);
	}
	
	IOHIDTransactionRef transactionRef = IOHIDDevice_GetTransaction(inIOHIDDeviceRef);
	if (transactionRef) {
		printf("transaction: %p, ", transactionRef);
	}
	
	printf("}\n");
	fflush(stdout);
}   // HIDDumpDeviceInfo
/*
    PsychHIDGetDeviceListByUsage()
*/ 
void PsychHIDGetDeviceListByUsage(long usagePage, long usage, int *numDeviceIndices, int *deviceIndices, pRecDevice *deviceRecords)
{
    pRecDevice 			currentDevice;
    int				currentDeviceIndex;

    PsychHIDVerifyInit();
    currentDeviceIndex=0;
    *numDeviceIndices=0;
    for(currentDevice=HIDGetFirstDevice(); currentDevice != NULL; currentDevice=HIDGetNextDevice(currentDevice)){    
        ++currentDeviceIndex;
#ifndef __LP64__        
        if(currentDevice->usagePage==usagePage && currentDevice->usage==usage){
#else
        if(IOHIDDevice_GetUsagePage(currentDevice) == usagePage && IOHIDDevice_GetUsage(currentDevice) == usage){
#endif
            deviceRecords[*numDeviceIndices]=currentDevice;
            deviceIndices[*numDeviceIndices]=currentDeviceIndex;  //the array is 0-indexed, devices are 1-indexed.   
            ++(*numDeviceIndices);
        }
    }
}
 
/*
    PsychHIDGetDeviceListByUsages()
 */ 
void PsychHIDGetDeviceListByUsages(int numUsages, long *usagePages, long *usages, int *numDeviceIndices, int *deviceIndices, pRecDevice *deviceRecords)
{
    pRecDevice 			currentDevice;
    int				currentDeviceIndex;
    int				currentUsage;
    long 			*usagePage;
    long			*usage;
	
    PsychHIDVerifyInit();
    *numDeviceIndices=0;
    for(usagePage=usagePages, usage=usages, currentUsage=0; currentUsage<numUsages; usagePage++, usage++, currentUsage++){
		currentDeviceIndex=0;
		for(currentDevice=HIDGetFirstDevice(); currentDevice != NULL; currentDevice=HIDGetNextDevice(currentDevice)){    
			++currentDeviceIndex;     
#ifndef __LP64__        
			if(currentDevice->usagePage==*usagePage && currentDevice->usage==*usage){
#else
            if(IOHIDDevice_GetPrimaryUsagePage(currentDevice) == *usagePage && IOHIDDevice_GetPrimaryUsage(currentDevice) == *usage){
#endif
				deviceRecords[*numDeviceIndices]=currentDevice;
				deviceIndices[*numDeviceIndices]=currentDeviceIndex;  //the array is 0-indexed, devices are 1-indexed.   
				++(*numDeviceIndices);
			}
		}
	}
}


/*
    PsychHIDGetIndexFromRecord()
    
    The inverse of PsychHIDGetDeviceRecordPtrFromIndex. 
    
    This O(n) where n is the number of device elements.   We could make it O(1) if we modified
    the element structure in the HID Utilities library to include a field specifying the index of the element or 
    collection.
    
    Note that if PsychHIDGetIndexFromRecord() is O(n) then its caller, PsychHIDGetCollections, is O(n^2) for each
    device, whereas if PsychHIDGetIndexFromRecord() is O(1) then psychHIDGetCollections becomes O(n) for each 
    device.   
*/
int PsychHIDGetIndexFromRecord(pRecDevice deviceRecord, pRecElement elementRecord, HIDElementTypeMask typeMask)
{
    int 		elementIndex;
    pRecElement		currentElement;						
    
    if(elementRecord==NULL)
        return(0);
    elementIndex=1;
    for(currentElement=HIDGetFirstDeviceElement(deviceRecord, typeMask);
        currentElement != elementRecord && currentElement != NULL;
        currentElement=HIDGetNextDeviceElement(currentElement, typeMask))
        ++elementIndex;
    if(currentElement==elementRecord)
        return(elementIndex);
    else{
        PsychErrorExitMsg(PsychError_internal, "Element record not found within device record");
        return(0); //make the compiler happy
    }
}