/*
    PSYCHHIDCheckInit() 
    
    Check to see if we need to create the USB-HID device list. If it has not been created then create it.   
*/
void PsychHIDVerifyInit(void)
{
    psych_bool success = TRUE;
    
    // Build HID device list if it doesn't already exist:
    if (!HIDHaveDeviceList()) success = (psych_bool) HIDBuildDeviceList(0, 0);
    
    // This check can only be made against the 64-Bit HID Utilities, as the older 32-Bit
    // version is even more crappy and can't report meaningful error status:
    #if defined(__LP64__)
    if (!success) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() failed)! There can be various reasons,\n");
        printf("PsychHID-ERROR: ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX 64 Bit Apple HID Utilities framework.");
    }
    #endif
    
    // Double-Check to protect against pathetic Apple software:
    if (!HIDHaveDeviceList()) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() success, but HIDHaveDeviceList() still failed)!\n");
        printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX Apple HID Utilities framework (II).");
    }
    
    // Verify no security sensitive application is blocking our low-level access to HID devices:
	PsychHIDWarnInputDisabled(NULL);
}
/*
    PSYCHHIDCheckInit() 
    
    Check to see if we need to create the USB-HID device list. If it has not been created then create it.   
*/
void PsychHIDVerifyInit(void)
{
    psych_bool success = TRUE;
    
    // Build HID device list if it doesn't already exist:
    if (!HIDHaveDeviceList()) success = (psych_bool) HIDBuildDeviceList(0, 0);
    
    // This check can only be made against the 64-Bit HID Utilities, as the older 32-Bit
    // version is even more crappy and can't report meaningful error status:
    #if defined(__LP64__)
    if (!success) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() failed)! There can be various reasons,\n");
        printf("PsychHID-ERROR: ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX 64 Bit Apple HID Utilities framework.");
    }
    #endif
    
    // Double-Check to protect against pathetic Apple software:
    if (!HIDHaveDeviceList()) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() success, but HIDHaveDeviceList() still failed)!\n");
        printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX Apple HID Utilities framework (II).");
    }
    
    // Verify no security sensitive application is blocking our low-level access to HID devices:
	PsychHIDWarnInputDisabled(NULL);
    
    #if defined(__LP64__)
    // Try to load all bundles from Psychtoolbox/PsychHardware/
    // This loads the HID_Utilities.framework bundle if it is present. The whole point of it is
    // to allow our statically compiled-in version of the library to find the location of
    // the XML file with the database of (vendorId, productId) -> (VendorName, ProductName) and
    // (usagePage, usage) -> (usageName) mappings.
    //
    // In practice, the XML file only serves as a fallback, and one that doesn't contain much
    // useful info for mainstream products, only for a few niche products. Given its limited
    // value, i think we can refrain from shipping the framework as part of Psychtoolbox and
    // just provide the option to use it (== its XML file) if users decide to install it themselves.
    char tmpString[1024];
    
    sprintf(tmpString, "%sPsychHardware/", PsychRuntimeGetPsychtoolboxRoot(FALSE));
    CFStringRef urlString = CFStringCreateWithCString(kCFAllocatorDefault, tmpString, kCFStringEncodingASCII);
    CFURLRef directoryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, urlString, kCFURLPOSIXPathStyle, false);
    CFRelease(urlString);
    CFArrayRef bundleArray = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, directoryURL, NULL);
    CFRelease(directoryURL);
    CFRelease(bundleArray);    
    #endif
}
/*
    PSYCHHIDCheckInit()

    Check to see if we need to create the USB-HID device list. If it has not been created then create it.
*/
void PsychHIDVerifyInit(void)
{
    // GenericDesktop for mice, keyboards, gamepads etc. vendor defined for things like MCC USB-DAQ boxes...
    UInt32 inUsagePages[2] = {kHIDPage_GenericDesktop, kHIDPage_VendorDefinedStart};
    UInt32 inUsages[2] = {0, 0};
    int inNumDeviceTypes = 2;
    psych_bool success = TRUE;

    // Build HID device list if it doesn't already exist:
    if (!HIDHaveDeviceList()) success = (psych_bool)HIDBuildDeviceList(0, 0);

    // This check can only be made against the 64-Bit HID Utilities, as the older 32-Bit
    // version is even more crappy and can't report meaningful error status:
    if (!success) {
        printf("PsychHID-ERROR: Could not enumerate and attach to all HID devices (HIDBuildDeviceList(0,0) failed)!\n");
        printf("PsychHID-ERROR: One reason could be that some HID devices are already exclusively claimed by some 3rd party device drivers\n");
        printf("PsychHID-ERROR: or applications. I will now retry to only claim control of a hopefully safe subset of devices like standard\n");
        printf("PsychHID-ERROR: keyboards, mice, gamepads and supported USB-DAQ devices and other vendor defined devices and hope this goes better...\n");

        // User override for special devices provided?
        if (getenv("PSYCHHID_HIDPAGEOVERRIDE")) {
            // Override via environment variable provided: Use it in addition to the generic desktop input devices:
            inUsagePages[1] = atoi(getenv("PSYCHHID_HIDPAGEOVERRIDE"));
            inUsages[1] = atoi(getenv("PSYCHHID_HIDUSAGEOVERRIDE"));
            printf("PsychHID-INFO: User override: Generic desktop page devices + usagePage 0x%x and usage value 0x%x ...\n", inUsagePages[1], inUsages[1]);
        }

        success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes);
        if (!success) {
            printf("PsychHID-ERROR: Nope. Excluding everything but a few known USB-DAQ devices and standard mice, keyboards etc. Retrying ...\n");
            inUsagePages[1] = kHIDPage_VendorDefinedStart;
            inUsages[1] = 1; // Known combo is usagepage 0xff00 + usage 0x1 for MCC USB 1208-FS USB HID-DAQ device.
            success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes);
        }

        if (!success) {
            printf("PsychHID-ERROR: Nope. Excluding everything but standard mice, keyboards etc. Retrying ...\n");
            success = (psych_bool)HIDBuildMultiDeviceList(inUsagePages, inUsages, 1);
            if (!success) {
                printf("PsychHID-ERROR: Could not enumerate any HID devices (HIDBuildDeviceList() still failed even for standard generic desktop input devices)!\n");
                printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
                printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
                printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
                PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX 64 Bit Apple HID Utilities framework.");
            }
        }
        else {
            printf("PsychHID-INFO: That worked. A subset of regular mouse, keyboard etc. input devices and maybe some vendor defined devices will be available at least.\n");
        }
    }

    // Double-Check to protect against pathetic Apple software:
    if (!HIDHaveDeviceList()) {
        printf("PsychHID-ERROR: Could not enumerate HID devices (HIDBuildDeviceList() success, but HIDHaveDeviceList() still failed)!\n");
        printf("PsychHID-ERROR: Reasons can be ranging from bugs in Apples HID software to a buggy HID device driver for some connected device,\n");
        printf("PsychHID-ERROR: to general operating system malfunction. A reboot or device driver update for 3rd party HID devices\n");
        printf("PsychHID-ERROR: maybe could help. Check the OSX system log for possible HID related error messages or hints. Aborting...\n");
        PsychErrorExitMsg(PsychError_system, "HID device enumeration failed due to malfunction in the OSX Apple HID Utilities framework (II).");
    }

    // Verify no security sensitive application is blocking our low-level access to HID devices:
    PsychHIDWarnInputDisabled(NULL);
}
PsychError PsychHIDOSKbCheck(int deviceIndex, double* scanList)
{
    pRecDevice			deviceRecord;
    pRecElement			currentElement, lastElement = NULL;
    int					i, debuglevel = 0;
    static int			numDeviceIndices = -1;
    int					numDeviceUsages=NUMDEVICEUSAGES;
	long				KbDeviceUsagePages[NUMDEVICEUSAGES]= {kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop, kHIDPage_GenericDesktop};
	long				KbDeviceUsages[NUMDEVICEUSAGES]={kHIDUsage_GD_Keyboard, kHIDUsage_GD_Keypad, kHIDUsage_GD_Mouse, kHIDUsage_GD_Pointer, kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController};
    static int			deviceIndices[PSYCH_HID_MAX_KEYBOARD_DEVICES]; 
    static pRecDevice	deviceRecords[PSYCH_HID_MAX_KEYBOARD_DEVICES];
    psych_bool			isDeviceSpecified, foundUserSpecifiedDevice;
    double				*timeValueOutput, *isKeyDownOutput, *keyArrayOutput;
    int					m, n, p, nout;
    double				dummyKeyDown;
    double				dummykeyArrayOutput[256];
    uint32_t            usage, usagePage;
    int                 value;

    // We query keyboard and keypad devices only on first invocation, then cache and recycle the data:
    if (numDeviceIndices == -1) {
        PsychHIDVerifyInit();
        PsychHIDGetDeviceListByUsages(numDeviceUsages, KbDeviceUsagePages, KbDeviceUsages, &numDeviceIndices, deviceIndices, deviceRecords);
    }
	
    // Choose the device index and its record
    isDeviceSpecified = (deviceIndex != INT_MAX);
    if(isDeviceSpecified){  //make sure that the device number provided by the user is really a keyboard or keypad.
        if (deviceIndex < 0) {
            debuglevel = 1;
            deviceIndex = -deviceIndex;
        }

        for(i=0;i<numDeviceIndices;i++){
            if ((foundUserSpecifiedDevice=(deviceIndices[i]==deviceIndex)))
                break;
        }
        if(!foundUserSpecifiedDevice)
            PsychErrorExitMsg(PsychError_user, "Specified device number is not a keyboard or keypad device.");
    } else { // set the keyboard or keypad device to be the first keyboard device or, if no keyboard, the first keypad
        i=0;
        if(numDeviceIndices==0)
            PsychErrorExitMsg(PsychError_user, "No keyboard or keypad devices detected.");
        else{
            deviceIndex=deviceIndices[i];
        }
    }
    deviceRecord=deviceRecords[i]; 

    // Allocate and init out return arguments.

    // Either alloc out the arguments, or redirect to
    // internal dummy variables. This to avoid mxMalloc() call overhead
    // inside the PsychAllocOutXXX() routines:
    nout = PsychGetNumNamedOutputArgs();

    // keyDown flag:
    if (nout >= 1) {
       PsychAllocOutDoubleArg(1, FALSE, &isKeyDownOutput);
    } else {
       isKeyDownOutput = &dummyKeyDown;
    }
    *isKeyDownOutput= (double) FALSE;

    // key state vector:
    if (nout >= 3) {
        PsychAllocOutDoubleMatArg(3, FALSE, 1, 256, 1, &keyArrayOutput);
    }
    else {
        keyArrayOutput = &dummykeyArrayOutput[0];
    }
    memset((void*) keyArrayOutput, 0, sizeof(double) * 256);

    // Query timestamp:
    if (nout >= 2) {
        PsychAllocOutDoubleArg(2, FALSE, &timeValueOutput);

        // Get query timestamp:
        PsychGetPrecisionTimerSeconds(timeValueOutput);
    }

    // Make sure our keyboard query mechanism is not blocked for security reasons, e.g.,
    // secure password entry field active in another process, i.e., EnableSecureEventInput() active.
    if (PsychHIDWarnInputDisabled("PsychHID('KbCheck')")) return(PsychError_none);

    //step through the elements of the device.  Set flags in the return array for down keys.
    for(currentElement=HIDGetFirstDeviceElement(deviceRecord, kHIDElementTypeInput | kHIDElementTypeCollection); 
        (currentElement != NULL) && (currentElement != lastElement);
        currentElement=HIDGetNextDeviceElement(currentElement, kHIDElementTypeInput | kHIDElementTypeCollection))
    {
        // Keep track of last queried element:
        lastElement = currentElement;
        
        #ifndef __LP64__
        usage = currentElement->usage;
        usagePage = currentElement->usagePage;
        #else
        usage = IOHIDElementGetUsage(currentElement);
        usagePage = IOHIDElementGetUsagePage(currentElement);
        // printf("PTB-DEBUG: [KbCheck]: ce %p page %d usage: %d isArray: %d\n", currentElement, usagePage, usage, IOHIDElementIsArray(currentElement));

        if (IOHIDElementGetType(currentElement) == kIOHIDElementTypeCollection) {
            CFArrayRef children = IOHIDElementGetChildren(currentElement);
            if (!children) continue;
            
            CFIndex idx, cnt = CFArrayGetCount(children);
            for ( idx = 0; idx < cnt; idx++ ) {
                IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(children, idx);
                if (tIOHIDElementRef && ((IOHIDElementGetType(tIOHIDElementRef) == kIOHIDElementTypeInput_Button) ||
                                         (IOHIDElementGetType(tIOHIDElementRef) == kIOHIDElementTypeInput_ScanCodes))) {
                    usage = IOHIDElementGetUsage(tIOHIDElementRef);
                    if ((usage <= 256) && (usage >= 1) && ( (scanList == NULL) || (scanList[usage - 1] > 0) )) {
                        value = (int) IOHIDElement_GetValue(tIOHIDElementRef, kIOHIDValueScaleTypePhysical);
                        if (debuglevel > 0) printf("PTB-DEBUG: [KbCheck]: usage: %x value: %d \n", usage, value);
                        keyArrayOutput[usage - 1] = (value || (int) keyArrayOutput[usage - 1]);
                        *isKeyDownOutput = keyArrayOutput[usage - 1] || *isKeyDownOutput;
                    }
                }
            }
            
            // Done with this currentElement, which was a collection of buttons/keys.
            // Iterate to next currentElement:
            continue;
        }
        #endif

        // Classic path, or 64-Bit path for non-collection elements:
        if(((usagePage == kHIDPage_KeyboardOrKeypad) || (usagePage == kHIDPage_Button)) && (usage <= 256) && (usage >= 1) &&
			( (scanList == NULL) || (scanList[usage - 1] > 0) ) ) {
            #ifndef __LP64__
            value = (int) HIDGetElementValue(deviceRecord, currentElement);
            #else
            value = (int) IOHIDElement_GetValue(currentElement, kIOHIDValueScaleTypePhysical);
            #endif

            if (debuglevel > 0) printf("PTB-DEBUG: [KbCheck]: usage: %x value: %d \n", usage, value);
            keyArrayOutput[usage - 1]=(value || (int) keyArrayOutput[usage - 1]);
            *isKeyDownOutput= keyArrayOutput[usage - 1] || *isKeyDownOutput; 
        }
    }

    return(PsychError_none);	
}