//	    File: HID_Queue_Utilities.c
//	Abstract: HID Queue 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.
//	
//*****************************************************
#include "HID_Utilities_External.h"

// ==================================
// private functions

// creates a queue for a device, creates and opens device interface if required
static IOReturn HIDCreateQueue(IOHIDDeviceRef inIOHIDDeviceRef) {
	IOReturn result = kIOReturnSuccess;
	if ( inIOHIDDeviceRef ) {
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		
		// do we already have a queue?
		IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
		if ( tIOHIDQueueRef ) { // (yes)
			assert( IOHIDQueueGetTypeID() == CFGetTypeID(tIOHIDQueueRef) );
		} else {
			tIOHIDQueueRef = IOHIDQueueCreate(kCFAllocatorDefault, inIOHIDDeviceRef, kDeviceQueueSize, kIOHIDOptionsTypeNone);
			if ( tIOHIDQueueRef ) { // did that work
				HIDReportErrorNum("Failed to create queue via create", result);
			} else {
				result = kIOReturnSuccess;
			}
		}
	} else {
		HIDReportErrorNum("HID device ref does not exist for queue creation", result);
	}
	
	return (result);
} /* HIDCreateQueue */

// ---------------------------------
// returns true if queue is empty false otherwise
// error if no device, empty if no queue
static unsigned char HIDIsDeviceQueueEmpty(IOHIDDeviceRef inIOHIDDeviceRef) {
	if ( inIOHIDDeviceRef ) { // need device and queue
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
		if ( tIOHIDQueueRef ) {
			IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO);
			
			while ( tIOHIDElementRef ) {
				if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) {
					return (false);
				}
				
				tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO);
			}
		} else {
			HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty.");
		}
	} else {
		HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty.");
	}
	
	return (true);
} /* HIDIsDeviceQueueEmpty */

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

// disposes and releases queue, sets queue to NULL,.
// Note: will have no effect if device or queue do not exist
static IOReturn HIDDisposeReleaseQueue(IOHIDDeviceRef inIOHIDDeviceRef) {
	IOReturn result = kIOReturnSuccess;
	if ( inIOHIDDeviceRef ) {
		IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
		if ( tIOHIDQueueRef ) {
			// stop queue
			IOHIDQueueStop(tIOHIDQueueRef);
			
			// release the queue
			CFRelease(tIOHIDQueueRef);
		}
	} else {
		HIDReportError("NULL device passed to HIDDisposeReleaseQueue.");
	}
	
	return (result);
} /* HIDDisposeReleaseQueue */

// ==================================
// public functions
// ----------------------------------

// queues specific element, performing any device queue set up required
// queue is started and ready to return events on exit from this function
int  HIDQueueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) {
	IOReturn result = kIOReturnSuccess;
	if ( inIOHIDDeviceRef ) {
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		if ( inIOHIDElementRef ) {
			assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) );
			IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
			if ( !tIOHIDQueueRef ) {         // if no queue create queue
				result = HIDCreateQueue(inIOHIDDeviceRef);
				if ( kIOReturnSuccess == result ) {
					tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
				}
			}
			if ( tIOHIDQueueRef ) {
				// stop queue
				IOHIDQueueStop(tIOHIDQueueRef);
				// queue element
				if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) {
					IOHIDQueueAddElement(tIOHIDQueueRef, inIOHIDElementRef);
				}
				
				// restart queue
				IOHIDQueueStart(tIOHIDQueueRef);
			} else {
				HIDReportError("No queue for device passed to HIDQueueElement.");
				if ( kIOReturnSuccess == result ) {
					result = kIOReturnError;
				}
			}
		} else {
			HIDReportError("NULL element passed to HIDQueueElement.");
			result = kIOReturnBadArgument;
		}
	} else {
		HIDReportError("NULL device passed to HIDQueueElement.");
		result = kIOReturnBadArgument;
	}
	
	return (result);
} /* HIDQueueElement */
// ---------------------------------

// adds all elements to queue, performing any device queue set up required
// queue is started and ready to return events on exit from this function
int  HIDQueueDevice(IOHIDDeviceRef inIOHIDDeviceRef) {
	IOReturn result = kIOReturnSuccess;
	// error checking
	if ( !inIOHIDDeviceRef ) {
		HIDReportError("Device does not exist, cannot queue device.");
		return (kIOReturnBadArgument);
	}
	if ( !inIOHIDDeviceRef ) { // must have interface
		HIDReportError("Device does not have hid device ref, cannot queue device.");
		return (kIOReturnError);
	}
	
	IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
	if ( !tIOHIDQueueRef ) {         // if no queue create queue
		result = HIDCreateQueue(inIOHIDDeviceRef);
		if ( kIOReturnSuccess == result ) {
			tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
		}
	}
	if ( (kIOReturnSuccess != result) || (!tIOHIDQueueRef) ) {
		HIDReportErrorNum("Could not queue device due to problem creating queue.", result);
		if ( kIOReturnSuccess != result ) {
			return (result);
		} else {
			return (kIOReturnError);
		}
	}
	
	// stop queue
	IOHIDQueueStop(tIOHIDQueueRef);
	
	// queue element
	IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO);
	
	while ( tIOHIDElementRef ) {
		if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) {
			IOHIDQueueAddElement(tIOHIDQueueRef, tIOHIDElementRef);
		}
		
		tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO);
	}
	
	// restart queue
	IOHIDQueueStart(tIOHIDQueueRef);
	
	return (result);
} /* HIDQueueDevice */

// ---------------------------------
// removes element for queue, if last element in queue will release queue and closes device interface
int  HIDDequeueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) {
	IOReturn result = kIOReturnSuccess;
	if ( inIOHIDDeviceRef ) {
		assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) );
		if ( inIOHIDElementRef ) {
			assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) );
			IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
			if ( tIOHIDQueueRef ) {
				// stop queue
				IOHIDQueueStop(tIOHIDQueueRef);
				// de-queue element
				if ( IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) {
					IOHIDQueueRemoveElement(tIOHIDQueueRef, inIOHIDElementRef);
				}
				// release device queue and close interface if queue empty
				if ( HIDIsDeviceQueueEmpty(inIOHIDDeviceRef) ) {
					result = HIDDisposeReleaseQueue(inIOHIDDeviceRef);
					if ( kIOReturnSuccess != result ) {
						HIDReportErrorNum("Failed to dispose and release queue.", result);
					}
				} else { // not empty so restart queue
					IOHIDQueueStart(tIOHIDQueueRef);
				}
			} else {
				HIDReportError("No queue for device passed to HIDDequeueElement.");
				if ( kIOReturnSuccess == result ) {
					result = kIOReturnError;
				}
			}
		} else {
			HIDReportError("NULL element passed to HIDDequeueElement.");
			result = kIOReturnBadArgument;
		}
	} else {
		HIDReportError("NULL device passed to HIDDequeueElement.");
		result = kIOReturnBadArgument;
	}
	
	return (result);
} /* HIDDequeueElement */

// ---------------------------------
// completely removes all elements from queue and releases queue and closes device interface
// does not release device interfaces, application must call ReleaseHIDDeviceList on exit
int  HIDDequeueDevice(IOHIDDeviceRef inIOHIDDeviceRef) {
	IOReturn result = kIOReturnSuccess;
	// error checking
	if ( !inIOHIDDeviceRef ) {
		HIDReportError("Device does not exist, cannot queue device.");
		return (kIOReturnBadArgument);
	}
	if ( !inIOHIDDeviceRef ) { // must have interface
		HIDReportError("Device does not have hid device ref, cannot queue device.");
		return (kIOReturnError);
	}
	
	IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
	if ( tIOHIDQueueRef ) {
		// iterate through elements and if queued, remove
		IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO);
		
		while ( tIOHIDElementRef ) {
			// de-queue element
			if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) {
				IOHIDQueueRemoveElement(tIOHIDQueueRef, tIOHIDElementRef);
			}
			
			tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO);
		}
		
		// ensure queue is disposed and released
		result = HIDDisposeReleaseQueue(inIOHIDDeviceRef);
		if ( kIOReturnSuccess != result ) {
			HIDReportErrorNum("Failed to dispose and release queue.", result);
		}
	} else {
		HIDReportError("No queue for device passed to HIDDequeueElement.");
		if ( kIOReturnSuccess == result ) {
			result = kIOReturnError;
		}
	}
	
	return (result);
} /* HIDDequeueDevice */
// ---------------------------------

// releases all device queues for quit or rebuild (must be called)
// does not release device interfaces, application must call ReleaseHIDDeviceList on exit
IOReturn HIDReleaseAllDeviceQueues(void) {
	IOReturn result = kIOReturnSuccess;
	IOHIDDeviceRef tIOHIDDeviceRef = HIDGetFirstDevice();
	
	while ( tIOHIDDeviceRef ) {
		result = HIDDequeueDevice(tIOHIDDeviceRef);
		if ( kIOReturnSuccess != result ) {
			HIDReportErrorNum("Could not dequeue device.", result);
		}
		
		tIOHIDDeviceRef = HIDGetNextDevice(tIOHIDDeviceRef);
	}
	
	return (result);
} /* HIDReleaseAllDeviceQueues */

// ---------------------------------
// Get the next event in the queue for a device
// elements or entire device should be queued prior to calling this with HIDQueueElement or HIDQueueDevice
// returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise
// Note: kIOReturnUnderrun returned from getNextEvent indicates an empty queue not an error condition
// Note: application should pass in a pointer to a IOHIDEventStruct cast to a void (for CFM compatibility)
unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHIDValueRef) {
	if ( inIOHIDDeviceRef ) {
		IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
		if ( tIOHIDQueueRef ) {
			if ( pIOHIDValueRef ) {
				*pIOHIDValueRef = IOHIDQueueCopyNextValueWithTimeout(tIOHIDQueueRef, 0.0);
				if ( *pIOHIDValueRef ) {
					return (true);
				}
			}
		} else {
			HIDReportError("Could not get HID event, hid queue reference does not exist.");
		}
	} else {
		HIDReportError("Could not get HID event, device does not exist.");
	}
	
	return (false);     // did not get event
} /* HIDGetEvent */
