/////////////////////////////////////////////////////////////////////////////
// Name:        hid.cpp
// Purpose:     DARWIN HID layer for WX Implementation
// Author:      Ryan Norton
// Modified by:
// Created:     11/11/2003
// RCS-ID:      $Id: hid.cpp,v 1.5 2004/09/27 23:19:21 RN Exp $
// Copyright:   (c) Ryan Norton
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ===========================================================================
// declarations
// ===========================================================================

// ---------------------------------------------------------------------------
// headers
// ---------------------------------------------------------------------------

#ifdef __GNUG__
#pragma implementation "hid.h"
#endif

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/defs.h"
#endif


//DARWIN _ONLY_
#ifdef __DARWIN__

#include "wx/mac/carbon/private/hid.h"
#include "wx/string.h"
#include "wx/log.h"


// ---------------------------------------------------------------------------
// assertion macros
// ---------------------------------------------------------------------------

#define wxFORCECHECK_MSG(arg, msg)  \
{\
	if (arg) \
	{\
		wxLogSysError(wxString::Format(wxT("Message:%s\nHID: %s failed!"), wxT(msg), wxT(#arg)));\
		return false;\
	}\
}
#define wxIOCHECK(arg, msg)  wxFORCECHECK_MSG(arg != kIOReturnSuccess, msg)
#define wxKERNCHECK(arg, msg) wxFORCECHECK_MSG(arg != KERN_SUCCESS, msg)
#define wxSCHECK(arg, msg) wxFORCECHECK_MSG(arg != S_OK, msg)

#ifdef __WXDEBUG___
#	define wxVERIFY(arg)	wxASSERT(arg)
#else
#	define wxVERIFY(arg)	arg
#endif

/*
void CFShowTypeIDDescription(CFTypeRef pData)
{
	if(!pData)
	{
		wxASSERT(false);
		return;
	}
	
	wxMessageBox(
		CFStringGetCStringPtr(
			CFCopyTypeIDDescription(CFGetTypeID(pData)),CFStringGetSystemEncoding()
							 )
				);	
}
*/

// ============================================================================
// implementation
// ============================================================================

// ---------------------------------------------------------------------------
// wxHIDDevice
// ---------------------------------------------------------------------------

bool wxHIDDevice::Create (const int& nClass, const int& nType)
{
	//Create the mach port
	wxIOCHECK(IOMasterPort(bootstrap_port, &m_pPort), "Could not create mach port");

	//Dictionary that will hold first
	//the matching dictionary for determining which kind of devices we want,
	//then later some registry properties from an iterator (see below)
	CFMutableDictionaryRef pDictionary;

	//Create a dictionary
	//The call to IOServiceMatching filters down the
	//the services we want to hid services (and also eats the
	//dictionary up for us (consumes one reference))
	wxVERIFY((pDictionary = IOServiceMatching(kIOHIDDeviceKey)) != NULL );

	//Here we'll filter down the services to what we want
	if (nType != -1)
	{
		CFNumberRef pType = CFNumberCreate(kCFAllocatorDefault,
									kCFNumberIntType, &nType);
		CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsageKey), pType);
		CFRelease(pType);
	}
	if (nClass != -1)
	{
		CFNumberRef pClass = CFNumberCreate(kCFAllocatorDefault,
									kCFNumberIntType, &nClass);
		CFDictionarySetValue(pDictionary, CFSTR(kIOHIDPrimaryUsagePageKey), pClass);
		CFRelease(pClass);
	}

	//Now get the maching services
	io_iterator_t pIterator;
	wxIOCHECK(IOServiceGetMatchingServices(m_pPort, pDictionary, &pIterator), "No Matching HID Services");
	wxASSERT(pIterator != NULL);

	//Now we iterate through them
	io_object_t pObject;
	while ( (pObject = IOIteratorNext(pIterator)) != NULL)
	{
		wxVERIFY(IORegistryEntryCreateCFProperties(pObject, &pDictionary,
											kCFAllocatorDefault, kNilOptions) == KERN_SUCCESS);

		//Just for sanity :)
		wxASSERT(CFGetTypeID(CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDProductKey))) == CFStringGetTypeID());
			
		//Get [product] name
		m_szName = CFStringGetCStringPtr	(
						(CFStringRef) CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDProductKey)), 
						CFStringGetSystemEncoding()
										);

		//
		//Now the hard part - in order to scan things we need "cookies" -
		//
		wxCFArray CookieArray = CFDictionaryGetValue(pDictionary, CFSTR(kIOHIDElementKey));
		BuildCookies(CookieArray);
		if (m_ppQueue != NULL)
			wxVERIFY((*m_ppQueue)->start(m_ppQueue) == S_OK);

		//Create the interface (good grief - long function names!)
		SInt32 nScore;
		IOCFPlugInInterface** ppPlugin;
		wxIOCHECK(IOCreatePlugInInterfaceForService(pObject, kIOHIDDeviceUserClientTypeID,
											kIOCFPlugInInterfaceID, &ppPlugin, &nScore), "");
											   
		//Now, the final thing we can check before we fall back to asserts
		//(because the dtor only checks if the device is ok, so if anything
		//fails from now on the dtor will delete the device anyway, so we can't break from this).
			
		//Get the HID interface from the plugin to the mach port
		wxSCHECK((*ppPlugin)->QueryInterface(ppPlugin,
							CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (void**) &m_ppDevice), "");
							
		//release the plugin
		(*ppPlugin)->Release(ppPlugin);
		
		//open the HID interface...
		wxVERIFY((*m_ppDevice)->open(m_ppDevice, 0) == S_OK);
		
		//cleanup
		CFRelease(pDictionary);
		IOObjectRelease(pObject);
		break;
	}
	//iterator cleanup
	IOObjectRelease(pIterator);
		
	return true;
}//end Create()
	
void wxHIDDevice::AddCookie(CFTypeRef Data, const int& i)
{
	CFNumberGetValue(
				(CFNumberRef) CFDictionaryGetValue	( (CFDictionaryRef) Data
										, CFSTR(kIOHIDElementCookieKey)
										),	
				kCFNumberIntType,
				&m_pCookies[i]
				);
}

void wxHIDDevice::AddCookieInQueue(CFTypeRef Data, const int& i)
{
	AddCookie(Data, i);
	wxVERIFY((*m_ppQueue)->addElement(m_ppQueue, m_pCookies[i], 0) == S_OK);//3rd Param flags (none yet)
}
	
void wxHIDDevice::InitCookies(const size_t& dwSize, bool bQueue)
{
	m_pCookies = new IOHIDElementCookie[dwSize];
	if (bQueue)
	{
		wxASSERT( m_ppQueue != NULL);
		wxVERIFY(  (m_ppQueue = (*m_ppDevice)->allocQueue(m_ppDevice)) != NULL);
		wxVERIFY(  (*m_ppQueue)->create(m_ppQueue, 0, 512) == S_OK); //Param 2, flags, none yet
	}		
}

bool wxHIDDevice::IsActive(const int& nIndex)
{
	wxASSERT(m_pCookies[nIndex] != NULL);
	IOHIDEventStruct Event;
	(*m_ppDevice)->getElementValue(m_ppDevice, m_pCookies[nIndex], &Event);
	return !!Event.value;
}
	

wxHIDDevice::~wxHIDDevice()
{
	if (m_ppDevice != NULL)
	{
		(*m_ppDevice)->close(m_ppDevice);
		(*m_ppDevice)->Release(m_ppDevice);
		mach_port_deallocate(mach_task_self(), m_pPort);
	}
	
	if (m_pCookies != NULL)
	{
		delete [] m_pCookies;
		if (m_ppQueue != NULL)
		{
			(*m_ppQueue)->stop(m_ppQueue);
			(*m_ppQueue)->dispose(m_ppQueue);
			(*m_ppQueue)->Release(m_ppQueue);
		}
	}
}

// ---------------------------------------------------------------------------
// wxHIDKeyboard
// ---------------------------------------------------------------------------

enum
{
	WXK_RSHIFT = 400,
	WXK_RALT,
	WXK_RCONTROL,
	WXK_RMENU
	
};

bool wxHIDKeyboard::Create()
{
	return wxHIDDevice::Create(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard);
}

void wxHIDKeyboard::BuildCookies(wxCFArray& Array)
{
	Array = CFDictionaryGetValue((CFDictionaryRef)Array[0], CFSTR(kIOHIDElementKey));
	InitCookies(500);
	int i,
		nUsage;
	for (i = 0; i < Array.Count(); ++i)
	{
		CFNumberGetValue(
			(CFNumberRef) CFDictionaryGetValue((CFDictionaryRef) Array[i], CFSTR(kIOHIDElementUsageKey)), 
				kCFNumberLongType, &nUsage);
			
		if (nUsage >= kHIDUsage_KeyboardA && nUsage <= kHIDUsage_KeyboardZ)
			AddCookie(Array[i], 'A' + (nUsage - kHIDUsage_KeyboardA) );
		else if (nUsage >= kHIDUsage_Keyboard1 && nUsage <= kHIDUsage_Keyboard9)
			AddCookie(Array[i], '1' + (nUsage - kHIDUsage_Keyboard1) );
		else if (nUsage >= kHIDUsage_KeyboardF1 && nUsage <= kHIDUsage_KeyboardF12)
			AddCookie(Array[i], WXK_F1 + (nUsage - kHIDUsage_KeyboardF1) );
		else if (nUsage >= kHIDUsage_KeyboardF13 && nUsage <= kHIDUsage_KeyboardF24)
			AddCookie(Array[i], WXK_F13 + (nUsage - kHIDUsage_KeyboardF13) );
		else if (nUsage >= kHIDUsage_Keypad1 && nUsage <= kHIDUsage_Keypad9)
			AddCookie(Array[i], WXK_NUMPAD1 + (nUsage - kHIDUsage_Keypad1) );
		else switch (nUsage)
		{
			//0's (wx & ascii go 0-9, but HID goes 1-0)
			case kHIDUsage_Keyboard0:
				AddCookie(Array[i],'0');
				break;
			case kHIDUsage_Keypad0:
				AddCookie(Array[i],WXK_NUMPAD0);
				break;
				
			//Basic
			case kHIDUsage_KeyboardReturnOrEnter:
				AddCookie(Array[i], WXK_RETURN);
				break;
			case kHIDUsage_KeyboardEscape:
				AddCookie(Array[i], WXK_ESCAPE);
				break;
			case kHIDUsage_KeyboardDeleteOrBackspace:
				AddCookie(Array[i], WXK_BACK);
				break;
			case kHIDUsage_KeyboardTab:
				AddCookie(Array[i], WXK_TAB);
				break;
			case kHIDUsage_KeyboardSpacebar:
				AddCookie(Array[i], WXK_SPACE);
				break;
			case kHIDUsage_KeyboardPageUp:
				AddCookie(Array[i], WXK_PRIOR);
				break;
			case kHIDUsage_KeyboardEnd:
				AddCookie(Array[i], WXK_END);
				break;
			case kHIDUsage_KeyboardPageDown:
				AddCookie(Array[i], WXK_NEXT);
				break;
			case kHIDUsage_KeyboardRightArrow:
				AddCookie(Array[i], WXK_RIGHT);
				break;
			case kHIDUsage_KeyboardLeftArrow:
				AddCookie(Array[i], WXK_LEFT);
				break;
			case kHIDUsage_KeyboardDownArrow:
				AddCookie(Array[i], WXK_DOWN);
				break;
			case kHIDUsage_KeyboardUpArrow:
				AddCookie(Array[i], WXK_UP);
				break;
						
			//LEDS
			case kHIDUsage_KeyboardCapsLock:
				AddCookie(Array[i],WXK_CAPITAL);
				break;
			case kHIDUsage_KeypadNumLock:
				AddCookie(Array[i],WXK_NUMLOCK);
				break;
			case kHIDUsage_KeyboardScrollLock:
				AddCookie(Array[i],WXK_SCROLL);
				break;
					
			//Menu keys, Shift, other specials
			case kHIDUsage_KeyboardLeftControl:
				AddCookie(Array[i],WXK_CONTROL);
				break;
			case kHIDUsage_KeyboardLeftShift:
				AddCookie(Array[i],WXK_SHIFT);
				break;
			case kHIDUsage_KeyboardLeftAlt:
				AddCookie(Array[i],WXK_ALT);
				break;
			case kHIDUsage_KeyboardLeftGUI:
				AddCookie(Array[i],WXK_MENU);
				break;
			case kHIDUsage_KeyboardRightControl:
				AddCookie(Array[i],WXK_RCONTROL);
				break;
			case kHIDUsage_KeyboardRightShift:
				AddCookie(Array[i],WXK_RSHIFT);
				break;
			case kHIDUsage_KeyboardRightAlt:
				AddCookie(Array[i],WXK_RALT);
				break;
			case kHIDUsage_KeyboardRightGUI:
				AddCookie(Array[i],WXK_RMENU);
				break;
 						
			//Default	
			default:
			//not in wx keycodes - do nothing....
			break;
		}
	}
}//end buildcookies

#endif //__DARWIN__
