//	    File: HID_Utilities.c
//	Abstract: Implementation of the HID utilities
//	 Version: 2.0
//	
//	Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
//	Inc. ("Apple") in consideration of your agreement to the following
//	terms, and your use, installation, modification or redistribution of
//	this Apple software constitutes acceptance of these terms.  If you do
//	not agree with these terms, please do not use, install, modify or
//	redistribute this Apple software.
//	
//	In consideration of your agreement to abide by the following terms, and
//	subject to these terms, Apple grants you a personal, non-exclusive
//	license, under Apple's copyrights in this original Apple software (the
//	"Apple Software"), to use, reproduce, modify and redistribute the Apple
//	Software, with or without modifications, in source and/or binary forms;
//	provided that if you redistribute the Apple Software in its entirety and
//	without modifications, you must retain this notice and the following
//	text and disclaimers in all such redistributions of the Apple Software.
//	Neither the name, trademarks, service marks or logos of Apple Inc. may
//	be used to endorse or promote products derived from the Apple Software
//	without specific prior written permission from Apple.  Except as
//	expressly stated in this notice, no other rights or licenses, express or
//	implied, are granted by Apple herein, including but not limited to any
//	patent rights that may be infringed by your derivative works or by other
//	works in which the Apple Software may be incorporated.
//	
//	The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
//	MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
//	THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
//	FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
//	OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
//	
//	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
//	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//	INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
//	MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
//	AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
//	STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
//	POSSIBILITY OF SUCH DAMAGE.
//	
//	Copyright (C) 2009 Apple Inc. All Rights Reserved.
//	
//***************************************************
#pragma mark - includes & imports
//-----------------------------------------------------

#include <AssertMacros.h>

#include "HID_Utilities_External.h"

//***************************************************
#pragma mark - typedefs, enums, defines, etc.
//-----------------------------------------------------
#define FAKE_MISSING_NAMES  1                    // set this to true while debuging to get more explicit element names; false for
// the
// generic ones

#define kPercentMove        10                   // precent of overall range a element must move to register
#define kNameKeyCFStringRef CFSTR("Name")        // dictionary key

//***************************************************
#pragma mark - local ( static ) function prototypes
//-----------------------------------------------------

static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context);
static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context);
static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage);

//***************************************************
#pragma mark - exported globals
//-----------------------------------------------------

IOHIDManagerRef gIOHIDManagerRef = NULL;
CFMutableArrayRef gDeviceCFArrayRef = NULL;
CFIndex gDeviceIndex;
CFArrayRef gElementCFArrayRef = NULL;

//***************************************************
#pragma mark - local ( static ) globals
//-----------------------------------------------------

//***************************************************
#pragma mark - exported function implementations
//-----------------------------------------------------

//*************************************************************************
//
// HIDBuildMultiDeviceList( inUsagePages, inUsages, inNumDeviceTypes )
//
// Purpose:	builds list of devices with elements
//
// Inputs:	inUsagePages		- inNumDeviceTypes sized array of matching usage pages
//			inUsages			- inNumDeviceTypes sized array of matching usages
//			inNumDeviceTypes	- number of usage pages & usages
//
// Returns:	Boolean		- if successful
//
Boolean HIDBuildMultiDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) {
	Boolean result = FALSE;                // assume failure ( pessimist! )
	Boolean first = (!gIOHIDManagerRef);   // not yet created?
	if ( first ) {
		// create the manager
		gIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
	}
	if ( gIOHIDManagerRef ) {
		CFMutableArrayRef hidMatchingCFMutableArrayRef = NULL;
		if ( inUsages && inUsagePages && inNumDeviceTypes ) {
			hidMatchingCFMutableArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
			if ( hidMatchingCFMutableArrayRef ) {
				int idx;
				for ( idx = 0; idx < inNumDeviceTypes; idx++ ) {    // for all usage and usage page types
					// Set up matching dictionary. returns NULL on error.
					CFMutableDictionaryRef hidMatchingCFDictRef = hu_SetUpMatchingDictionary(inUsagePages[idx], inUsages[idx]);
					if ( hidMatchingCFDictRef ) {
						CFArrayAppendValue(hidMatchingCFMutableArrayRef, (void *) hidMatchingCFDictRef);
						CFRelease(hidMatchingCFDictRef);
					} else {
						fprintf(stderr, "%s: Couldn’t create a matching dictionary.", __PRETTY_FUNCTION__);
					}
				}
			} else {
				fprintf(stderr, "%s: Couldn’t create a matching array.", __PRETTY_FUNCTION__);
			}
		}
		
		// set it for IOHIDManager to use to match against
		IOHIDManagerSetDeviceMatchingMultiple(gIOHIDManagerRef, hidMatchingCFMutableArrayRef);
		if ( hidMatchingCFMutableArrayRef ) {
			CFRelease(hidMatchingCFMutableArrayRef);
		}
		if ( first ) {
			// open it
			IOReturn tIOReturn = IOHIDManagerOpen(gIOHIDManagerRef, kIOHIDOptionsTypeNone);
			if ( kIOReturnSuccess != tIOReturn ) {
				fprintf(stderr, "%s: Couldn’t open IOHIDManager.", __PRETTY_FUNCTION__);
				goto Oops;
			}
		}
		
		HIDRebuildDevices();
		result = TRUE;
	} else {
		fprintf(stderr, "%s: Couldn’t create a IOHIDManager.", __PRETTY_FUNCTION__);
	}
	
Oops:   ;
	return (result);
}   // HIDBuildMultiDeviceList

/*************************************************************************
 *
 * HIDBuildDeviceList( inUsagePage, inUsage )
 *
 * Purpose:  builds list of devices with elements
 *
 * Notes:	same as above but this uses a single inUsagePage and usage
 *			allocates memory and captures devices
 *			list is allocated internally within HID Utilites and can be accessed via accessor functions
 *			structures within list are considered flat and user accessable, but not user modifiable
 *			can be called again to rebuild list to account for new devices
 *			( will do the right thing in case of disposing existing list )
 *
 * Inputs:   inUsagePage		- usage page
 *			inUsage			- usages
 *
 * Returns:  Boolean		- if successful
 */

Boolean HIDBuildDeviceList(UInt32 inUsagePage, UInt32 inUsage) {
	return ( HIDBuildMultiDeviceList(&inUsagePage, &inUsage, 1) ); // call HIDBuildMultiDeviceList with a single usage
}

/*************************************************************************
 *
 * HIDUpdateDeviceList( inUsagePages, inUsages, inNumDeviceTypes )
 *
 * Purpose:  updates the current device list for any new/removed devices
 *
 * Notes:	if this is called before HIDBuildDeviceList then it functions like HIDBuildMultiDeviceList
 *			inUsagePage & inUsage are each a inNumDeviceTypes sized array of matching usage and usage pages
 *
 * Inputs:   inUsagePages		- inNumDeviceTypes sized array of matching usage pages
 *			inUsages			- inNumDeviceTypes sized array of matching usages
 *			inNumDeviceTypes - number of usage pages & usages
 *
 * Returns:  Boolean		- TRUE if the device config changed
 */

Boolean HIDUpdateDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) {
	return ( HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes) );
}

/*************************************************************************
 *
 * HIDReleaseDeviceList( void )
 *
 * Purpose:  release list built by above functions
 *
 * Notes:	MUST be called prior to application exit to properly release devices
 *			if not called( or app crashes ) devices can be recovered by pluging into different location in USB chain
 *
 * Inputs:   none
 *
 * Returns:  none
 */

void HIDReleaseDeviceList(void) {
	if ( gDeviceCFArrayRef ) {
		CFRelease(gDeviceCFArrayRef);
		gDeviceCFArrayRef = NULL;
	}
} // HIDReleaseDeviceList

/*************************************************************************
 *
 * HIDHaveDeviceList( void )
 *
 * Purpose:  does a device list exist?
 *
 * Inputs:   none
 *
 * Returns:  Boolean		- TRUE if we have previously built a device list
 */

Boolean HIDHaveDeviceList(void) {
	return (NULL != gDeviceCFArrayRef);
}

//*************************************************************************
//
// HIDRebuildDevices(  )
//
// Purpose:	rebuilds the (internal) list of IOHIDDevices
//
// Inputs:	none
//
// Returns:	none
//

void HIDRebuildDevices(void) {
	// get the set of devices from the IOHID manager
	CFSetRef devCFSetRef = IOHIDManagerCopyDevices(gIOHIDManagerRef);
	if ( devCFSetRef ) {
		// if the existing array isn't empty...
		if ( gDeviceCFArrayRef ) {
			// release it
			CFRelease(gDeviceCFArrayRef);
		}
		
		// create an empty array
		gDeviceCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
		// now copy the set to the array
		CFSetApplyFunction(devCFSetRef, CFSetApplierFunctionCopyToCFArray, (void *) gDeviceCFArrayRef);
		// now sort the array by location ID's
		CFIndex cnt = CFArrayGetCount(gDeviceCFArrayRef);
		CFArraySortValues(gDeviceCFArrayRef, CFRangeMake(0, cnt), CFDeviceArrayComparatorFunction, NULL);
		
		// and release the set we copied from the IOHID manager
		CFRelease(devCFSetRef);
	}
}   // HIDRebuildDevices

// ---------------------------------

// how many HID devices have been found
// returns 0 if no device list exist

UInt32 HIDCountDevices(void) {
	return ( CFArrayGetCount(gDeviceCFArrayRef) );
}

// ---------------------------------

// how many elements does a specific device have
// returns 0 if device is invlaid or NULL

UInt32 HIDCountDeviceElements(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) {
	int count = 0;
	if ( inIOHIDDeviceRef ) {
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		
		gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone);
		if ( gElementCFArrayRef ) {
			CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef);
			for ( idx = 0; idx < cnt; idx++ ) {
				IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx);
				if ( !tIOHIDElementRef ) {
					continue;
				}
				
				IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
				
				switch ( type ) {
					case kIOHIDElementTypeInput_Misc:
					case kIOHIDElementTypeInput_Button:
					case kIOHIDElementTypeInput_Axis:
					case kIOHIDElementTypeInput_ScanCodes:
					{
						if ( typeMask & kHIDElementTypeInput ) {
							count++;
						}
						
						break;
					}
						
					case kIOHIDElementTypeOutput:
					{
						if ( typeMask & kHIDElementTypeOutput ) {
							count++;
						}
						
						break;
					}
						
					case kIOHIDElementTypeFeature:
					{
						if ( typeMask & kHIDElementTypeFeature ) {
							count++;
						}
						
						break;
					}
						
					case kIOHIDElementTypeCollection:
					{
						if ( typeMask & kHIDElementTypeCollection ) {
							count++;
						}
						
						break;
					}
					default: {
						break;
					}
				}   // switch ( type )
				
			}       // next idx
			
			CFRelease(gElementCFArrayRef);
			gElementCFArrayRef = NULL;
		}           // if ( gElementCFArrayRef )
		
	}               // if ( inIOHIDDeviceRef )
	
	return (count);
} /* HIDCountDeviceElements */

// ---------------------------------

// get the first device in the device list
// returns NULL if no list exists or it's empty

IOHIDDeviceRef HIDGetFirstDevice(void) {
	IOHIDDeviceRef result = NULL;
	
	gDeviceIndex = 0;
	if ( gDeviceCFArrayRef ) {
		CFIndex count = CFArrayGetCount(gDeviceCFArrayRef);
		if ( (gDeviceIndex >= 0) && (gDeviceIndex < count) ) {
			result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex);
		}
	}
	
	return (result);
} /* HIDGetFirstDevice */

// ---------------------------------

// get next device in list given current device as parameter
// returns NULL if end of list

IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef) {
	IOHIDDeviceRef result = NULL;
	if ( gDeviceCFArrayRef && inIOHIDDeviceRef ) {
		CFIndex idx, cnt = CFArrayGetCount(gDeviceCFArrayRef);
		// quick case to verify the current device index is valid for current device
		if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) {
			result = (IOHIDDeviceRef)         CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex);
			if ( result && (result == inIOHIDDeviceRef) ) {
				result = NULL;
				gDeviceIndex++; // bump index
			} else {
				// previous index was invalid;
				gDeviceIndex = -1;
				// search for current device's index
				for ( idx = 0; idx < cnt; idx++ ) {
					result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, idx);
					if ( (result) && (result == inIOHIDDeviceRef) ) {
						gDeviceIndex = idx + 1;   // found valid index; bump to next one
						break;
					}
				}
				
				result = NULL;
			}
			if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) {
				result = (IOHIDDeviceRef)     CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex);
			}
		}   // if valid index
		
	}       // if ( gDeviceCFArrayRef && inIOHIDDeviceRef )
	
	return (result);
} /* HIDGetNextDevice */

// ---------------------------------

// get the first element of device passed in as parameter
// returns NULL if no list exists or device does not exists or is NULL
IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) {
	IOHIDElementRef result = NULL;
	if ( inIOHIDDeviceRef ) {
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		
		gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone);
		if ( gElementCFArrayRef ) {
			CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef);
			for ( idx = 0; idx < cnt; idx++ ) {
				IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx);
				if ( !tIOHIDElementRef ) {
					continue;
				}
				
				IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
				
				switch ( type ) {
					case kIOHIDElementTypeInput_Misc:
					case kIOHIDElementTypeInput_Button:
					case kIOHIDElementTypeInput_Axis:
					case kIOHIDElementTypeInput_ScanCodes:
					{
						if ( typeMask & kHIDElementTypeInput ) {
							result = tIOHIDElementRef;
						}
						
						break;
					}
						
					case kIOHIDElementTypeOutput:
					{
						if ( typeMask & kHIDElementTypeOutput ) {
							result = tIOHIDElementRef;
						}
						
						break;
					}
						
					case kIOHIDElementTypeFeature:
					{
						if ( typeMask & kHIDElementTypeFeature ) {
							result = tIOHIDElementRef;
						}
						
						break;
					}
						
					case kIOHIDElementTypeCollection:
					{
						if ( typeMask & kHIDElementTypeCollection ) {
							result = tIOHIDElementRef;
						}
						
						break;
					}
					default: {
						break;
					}
				}           // switch ( type )
				if ( result ) {
					break;  // DONE!
				}
			}               // next idx
			
			CFRelease(gElementCFArrayRef);
			gElementCFArrayRef = NULL;
		}                   //      if ( gElementCFArrayRef )
		
	}                       //  if ( inIOHIDDeviceRef )
	
	return (result);
} /* HIDGetFirstDeviceElement */

// ---------------------------------

// get next element of given device in list given current element as parameter
// will walk down each collection then to next element or collection (depthwise traverse)
// returns NULL if end of list
// uses mask of HIDElementTypeMask to restrict element found
// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality
IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef inIOHIDElementRef, HIDElementTypeMask typeMask) {
	IOHIDElementRef result = NULL;
	if ( inIOHIDElementRef ) {
		assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) );
		
		IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef);
		if ( tIOHIDDeviceRef ) {
			Boolean found = FALSE;
			
			gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone);
			if ( gElementCFArrayRef ) {
				CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef);
				for ( idx = 0; idx < cnt; idx++ ) {
					IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx);
					if ( !tIOHIDElementRef ) {
						continue;
					}
					if ( !found ) {
						if ( inIOHIDElementRef == tIOHIDElementRef ) {
							found = TRUE;
						}
						
						continue;   // next element
					} else {
						// we've found the current element; now find the next one of the right type
						IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
						
						switch ( type ) {
							case kIOHIDElementTypeInput_Misc:
							case kIOHIDElementTypeInput_Button:
							case kIOHIDElementTypeInput_Axis:
							case kIOHIDElementTypeInput_ScanCodes:
							{
								if ( typeMask & kHIDElementTypeInput ) {
									result = tIOHIDElementRef;
								}
								
								break;
							}
								
							case kIOHIDElementTypeOutput:
							{
								if ( typeMask & kHIDElementTypeOutput ) {
									result = tIOHIDElementRef;
								}
								
								break;
							}
								
							case kIOHIDElementTypeFeature:
							{
								if ( typeMask & kHIDElementTypeFeature ) {
									result = tIOHIDElementRef;
								}
								
								break;
							}
								
							case kIOHIDElementTypeCollection:
							{
								if ( typeMask & kHIDElementTypeCollection ) {
									result = tIOHIDElementRef;
								}
								
								break;
							}
							default: {
								break;
							}
						}           // switch ( type )
						if ( result ) {
							break;  // DONE!
						}
					}               // if ( !found )
					
				}                   // next idx
				
				CFRelease(gElementCFArrayRef);
				gElementCFArrayRef = NULL;
			}                       //      if ( gElementCFArrayRef )
			
		}                           //  if ( inIOHIDDeviceRef )
		
	}                               //  if ( inIOHIDElementRef )
	
	return (result);
} /* HIDGetNextDeviceElement */

#if 0
// ---------------------------------
// get previous element of given device in list given current element as parameter
// this wlaks directly up the tree to the top element and does not search at each level
// returns NULL if beginning of list
// uses mask of HIDElementTypeMask to restrict element found
// use kHIDElementTypeIO to get non-collection elements
IOHIDElementRef HIDGetPreviousDeviceElement(IOHIDElementRef pElement, HIDElementTypeMask typeMask) {
	IOHIDElementRef pPreviousElement = pElement->pPrevious;
	
	// walk back up tree to element prior
	while ( pPreviousElement && !HIDMatchElementTypeMask(pPreviousElement->type, typeMask) ) {
		pElement = pPreviousElement; // look at previous element
		pPreviousElement = pElement->pPrevious;
	}
	
	return (pPreviousElement);         // return this element
} /* HIDGetPreviousDeviceElement */

#endif

// 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 ) {
		verify( CFStringGetCString(tCFStringRef, manufacturer, sizeof(manufacturer), kCFStringEncodingUTF8) );
	}
	
	char product[256] = "";      // name of product
	tCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef);
	if ( tCFStringRef ) {
		verify( CFStringGetCString(tCFStringRef, product, sizeof(product), kCFStringEncodingUTF8) );
	}
	
	printf("%s - %s, ", manufacturer, product);
	
	long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
	if ( vendorID ) {
#if 1
		printf("	vendorID:	0x%04lX, ", vendorID);
#else
		if ( HIDGetVendorNameFromVendorID(vendorID, cstring) ) {
			printf("	vendorID:	0x%04lX (\"%s\"), ", vendorID, cstring);
		} else {
			printf("	vendorID:	0x%04lX, ",          vendorID);
		}
		
#endif
	}
	
	long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
	if ( productID ) {
#if 1
		printf("	productID:	0x%04lX, ", productID);
#else
		if ( HIDGetProductNameFromVendorProductID(vendorID, productID, cstring) ) {
			printf("	productID:	0x%04lX (\"%s\"), ", productID, cstring);
		} else {
			printf("	productID:	0x%04lX, ",          productID);
		}
		
#endif
	}
	
	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);
	
#if 1
	tCFStringRef = HIDCopyUsageName(usagePage, usage);
	if ( tCFStringRef ) {
		verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) );
		printf("\"%s\", ", cstring);
		CFRelease(tCFStringRef);
	}
	
#endif
	
#if 1
	tCFStringRef = IOHIDDevice_GetTransport(inIOHIDDeviceRef);
	if ( tCFStringRef ) {
		verify( 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 ) {
		verify( 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 0
	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
	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);
	}
	
#endif
	printf("}\n");
	fflush(stdout);
}   // HIDDumpDeviceInfo

// utility routine to dump element info
void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef) {
	if ( inIOHIDElementRef ) {
		printf("    Element: %p = { ", inIOHIDElementRef);
#if 0
		IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef);
		printf("Device: %p, ", tIOHIDDeviceRef);
#endif
		IOHIDElementRef parentIOHIDElementRef = IOHIDElementGetParent(inIOHIDElementRef);
		printf("parent: %p, ", parentIOHIDElementRef);
#if 0
		CFArrayRef childrenCFArrayRef = IOHIDElementGetChildren(inIOHIDElementRef);
		printf("children: %p: { ", childrenCFArrayRef);
		fflush(stdout);
		CFShow(childrenCFArrayRef);
		fflush(stdout);
		printf(" }, ");
#endif
		IOHIDElementCookie tIOHIDElementCookie = IOHIDElementGetCookie(inIOHIDElementRef);
		printf("cookie: %p, ", tIOHIDElementCookie);
		
		IOHIDElementType tIOHIDElementType = IOHIDElementGetType(inIOHIDElementRef);
		
		switch ( tIOHIDElementType ) {
			case kIOHIDElementTypeInput_Misc:
			{
				printf("type: Misc, ");
				break;
			}
				
			case kIOHIDElementTypeInput_Button:
			{
				printf("type: Button, ");
				break;
			}
				
			case kIOHIDElementTypeInput_Axis:
			{
				printf("type: Axis, ");
				break;
			}
				
			case kIOHIDElementTypeInput_ScanCodes:
			{
				printf("type: ScanCodes, ");
				break;
			}
				
			case kIOHIDElementTypeOutput:
			{
				printf("type: Output, ");
				break;
			}
				
			case kIOHIDElementTypeFeature:
			{
				printf("type: Feature, ");
				break;
			}
				
			case kIOHIDElementTypeCollection:
			{
				IOHIDElementCollectionType tIOHIDElementCollectionType = IOHIDElementGetCollectionType(inIOHIDElementRef);
				
				switch ( tIOHIDElementCollectionType ) {
					case kIOHIDElementCollectionTypePhysical:
					{
						printf("type: Physical Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeApplication:
					{
						printf("type: Application Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeLogical:
					{
						printf("type: Logical Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeReport:
					{
						printf("type: Report Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeNamedArray:
					{
						printf("type: Named Array Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeUsageSwitch:
					{
						printf("type: Usage Switch Collection, ");
						break;
					}
						
					case kIOHIDElementCollectionTypeUsageModifier:
					{
						printf("type: Usage Modifier Collection, ");
						break;
					}
						
					default:
					{
						printf("type: %p Collection, ", (void *) tIOHIDElementCollectionType);
						break;
					}
				} // switch
				
				break;
			}
				
			default:
			{
				printf("type: %p, ", (void *) tIOHIDElementType);
				break;
			}
		}     /* switch */
		
		uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef);
		uint32_t usage = IOHIDElementGetUsage(inIOHIDElementRef);
		printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage);
#if 1
		CFStringRef tCFStringRef = HIDCopyUsageName(usagePage, usage);
		if ( tCFStringRef ) {
			char usageString[256] = "";
			verify( CFStringGetCString(tCFStringRef, usageString, sizeof(usageString), kCFStringEncodingUTF8) );
			printf("\"%s\", ", usageString);
			CFRelease(tCFStringRef);
		}
		
#endif
		CFStringRef nameCFStringRef = IOHIDElementGetName(inIOHIDElementRef);
		char buffer[256];
		if ( nameCFStringRef && CFStringGetCString(nameCFStringRef, buffer, sizeof(buffer), kCFStringEncodingUTF8) ) {
			printf("name: %s, ", buffer);
		}
		
		uint32_t reportID = IOHIDElementGetReportID(inIOHIDElementRef);
		uint32_t reportSize = IOHIDElementGetReportSize(inIOHIDElementRef);
		uint32_t reportCount = IOHIDElementGetReportCount(inIOHIDElementRef);
		printf("report: { ID: %lu, Size: %lu, Count: %lu }, ",
		       (long unsigned int) reportID, (long unsigned int) reportSize, (long unsigned int) reportCount);
		
		uint32_t unit = IOHIDElementGetUnit(inIOHIDElementRef);
		uint32_t unitExp = IOHIDElementGetUnitExponent(inIOHIDElementRef);
		if ( unit || unitExp ) {
			printf("unit: %lu * 10^%lu, ", (long unsigned int) unit, (long unsigned int) unitExp);
		}
		
		CFIndex logicalMin = IOHIDElementGetLogicalMin(inIOHIDElementRef);
		CFIndex logicalMax = IOHIDElementGetLogicalMax(inIOHIDElementRef);
		if ( logicalMin != logicalMax ) {
			printf("logical: {min: %ld, max: %ld}, ", logicalMin, logicalMax);
		}
		
		CFIndex physicalMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef);
		CFIndex physicalMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef);
		if ( physicalMin != physicalMax ) {
			printf("physical: {min: %ld, max: %ld}, ", physicalMin, physicalMax);
		}
		
		Boolean isVirtual = IOHIDElementIsVirtual(inIOHIDElementRef);
		if ( isVirtual ) {
			printf("isVirtual, ");
		}
		
		Boolean isRelative = IOHIDElementIsRelative(inIOHIDElementRef);
		if ( isRelative ) {
			printf("isRelative, ");
		}
		
		Boolean isWrapping = IOHIDElementIsWrapping(inIOHIDElementRef);
		if ( isWrapping ) {
			printf("isWrapping, ");
		}
		
		Boolean isArray = IOHIDElementIsArray(inIOHIDElementRef);
		if ( isArray ) {
			printf("isArray, ");
		}
		
		Boolean isNonLinear = IOHIDElementIsNonLinear(inIOHIDElementRef);
		if ( isNonLinear ) {
			printf("isNonLinear, ");
		}
		
		Boolean hasPreferredState = IOHIDElementHasPreferredState(inIOHIDElementRef);
		if ( hasPreferredState ) {
			printf("hasPreferredState, ");
		}
		
		Boolean hasNullState = IOHIDElementHasNullState(inIOHIDElementRef);
		if ( hasNullState ) {
			printf("hasNullState, ");
		}
		
		printf(" }\n");
	}
}   // HIDDumpElementInfo

void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef) {
	printf("    Element: %p = { ", inIOHIDElementRef);
	
	CFIndex calMin = IOHIDElement_GetCalibrationMin(inIOHIDElementRef);
	CFIndex calMax = IOHIDElement_GetCalibrationMax(inIOHIDElementRef);
	printf("cal: {min: %ld, max: %ld}, ", calMin, calMax);
	
	CFIndex satMin = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef);
	CFIndex satMax = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef);
	printf("sat: {min: %ld, max: %ld}, ", satMin, satMax);
	
	CFIndex deadMin = IOHIDElement_GetCalibrationDeadZoneMin(inIOHIDElementRef);
	CFIndex deadMax = IOHIDElement_GetCalibrationDeadZoneMax(inIOHIDElementRef);
	printf("dead: {min: %ld, max: %ld}, ", deadMin, deadMax);
	
	double_t granularity = IOHIDElement_GetCalibrationGranularity(inIOHIDElementRef);
	printf("granularity: %6.2f }\n", granularity);
} // HIDDumpElementCalibrationInfo

//***************************************************
#pragma mark - local ( static ) function implementations
//-----------------------------------------------------

//*************************************************************************
//
// CFSetApplierFunctionCopyToCFArray( value, context )
//
// Purpose:	CFSetApplierFunction to copy the CFSet to a CFArray
//
// Notes:	called one time for each item in the CFSet
//
// Inputs:	value           - the current element of the CFSet
//			context			- the CFMutableArrayRef we're adding the CFSet elements to
//
// Returns:	nothing
//
static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context) {
	// printf( "%s: 0x%08lX\n", __PRETTY_FUNCTION__, (long unsigned int) value );
	CFArrayAppendValue( (CFMutableArrayRef) context, value );
}   // CFSetApplierFunctionCopyToCFArray

// ---------------------------------
// used to sort the CFDevice array after copying it from the (unordered) (CF)set.
// we compare based on the location ID's since they're consistant (across boots & launches).
//
static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context) {
#pragma unused( context )
	CFComparisonResult result = kCFCompareEqualTo;
	
	long loc1 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val1 );
	long loc2 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val2 );
	if ( loc1 < loc2 ) {
		result = kCFCompareLessThan;
	} else if ( loc1 > loc2 ) {
		result = kCFCompareGreaterThan;
	}
	
	return (result);
}   // CFDeviceArrayComparatorFunction

//*************************************************************************
//
// hu_SetUpMatchingDictionary( inUsagePage, inUsage )
//
// Purpose:	builds a matching dictionary based on usage page and usage
//
// Notes:	Only called by HIDBuildMultiDeviceList
//
// Inputs:	inUsagePage				- usage page
//			inUsage					- usages
//
// Returns:	CFMutableDictionaryRef  - the matching dictionary
//

static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage) {
	// create a dictionary to add usage page/usages to
	CFMutableDictionaryRef refHIDMatchDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault,
	                                                                         0,
	                                                                         &kCFTypeDictionaryKeyCallBacks,
	                                                                         &kCFTypeDictionaryValueCallBacks);
	if ( refHIDMatchDictionary ) {
		if ( inUsagePage ) {
			// Add key for device type to refine the matching dictionary.
			CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
			if ( pageCFNumberRef ) {
				CFDictionarySetValue(refHIDMatchDictionary,
				                     CFSTR(kIOHIDPrimaryUsagePageKey), pageCFNumberRef);
				CFRelease(pageCFNumberRef);
				// note: the usage is only valid if the usage page is also defined
				if ( inUsage ) {
					CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
					if ( usageCFNumberRef ) {
						CFDictionarySetValue(refHIDMatchDictionary,
						                     CFSTR(kIOHIDPrimaryUsageKey), usageCFNumberRef);
						CFRelease(usageCFNumberRef);
					} else {
						fprintf(stderr, "%s: CFNumberCreate( usage ) failed.", __PRETTY_FUNCTION__);
					}
				}
			} else {
				fprintf(stderr, "%s: CFNumberCreate( usage page ) failed.", __PRETTY_FUNCTION__);
			}
		}
	} else {
		fprintf(stderr, "%s: CFDictionaryCreateMutable failed.", __PRETTY_FUNCTION__);
	}
	
	return (refHIDMatchDictionary);
}   // hu_SetUpMatchingDictionary
