/*         ______   ___    ___
 *        /\  _  \ /\_ \  /\_ \
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      HID Joystick driver routines for MacOS X.
 *
 *      By Angelo Mottola.
 *      New API (Leopard) support and hotplugging by Trent Gamblin.
 *
 *      See readme.txt for copyright information.
 */

#include "allegro5/allegro.h"
#include "allegro5/platform/aintosx.h"

#import <CoreFoundation/CoreFoundation.h>
#import <IOKit/hid/IOHIDKeys.h>

#ifndef ALLEGRO_MACOSX
#error something is wrong with the makefile
#endif

#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050

#import <IOKit/hid/IOHIDBase.h>

/* State transitions:
 *    unused -> born
 *    born -> alive
 *    born -> dying
 *    active -> dying
 *    dying -> unused
 */
typedef enum {
   JOY_STATE_UNUSED,
   JOY_STATE_BORN,
   JOY_STATE_ALIVE,
   JOY_STATE_DYING
} CONFIG_STATE;

// These values can be found in the USB HID Usage Tables:
// http://www.usb.org/developers/hidpage
#define GENERIC_DESKTOP_USAGE_PAGE 0x01
#define JOYSTICK_USAGE_NUMBER      0x04
#define GAMEPAD_USAGE_NUMBER       0x05

typedef struct {
   ALLEGRO_JOYSTICK parent;
   IOHIDElementRef buttons[_AL_MAX_JOYSTICK_BUTTONS];
   IOHIDElementRef axes[_AL_MAX_JOYSTICK_STICKS][_AL_MAX_JOYSTICK_AXES];
   IOHIDElementRef dpad;
   int dpad_stick;
   int dpad_axis_vert;
   int dpad_axis_horiz;
   long min[_AL_MAX_JOYSTICK_STICKS][_AL_MAX_JOYSTICK_AXES];
   long max[_AL_MAX_JOYSTICK_STICKS][_AL_MAX_JOYSTICK_AXES];
   CONFIG_STATE cfg_state;
   ALLEGRO_JOYSTICK_STATE state;
   IOHIDDeviceRef ident;
   char *name;
} ALLEGRO_JOYSTICK_OSX;

static IOHIDManagerRef hidManagerRef;
static _AL_VECTOR joysticks;
static CONFIG_STATE new_joystick_state = JOY_STATE_ALIVE;
static bool initialized = false;
static ALLEGRO_MUTEX *add_mutex;

ALLEGRO_DEBUG_CHANNEL("MacOSX")

// function to create matching dictionary (for devices)
static CFMutableDictionaryRef CreateDeviceMatchingDictionary(
   UInt32 inUsagePage,
   UInt32 inUsage
) {
   // create a dictionary to add usage page/usages to
   CFMutableDictionaryRef result = CFDictionaryCreateMutable(
      kCFAllocatorDefault,
      0,
      &kCFTypeDictionaryKeyCallBacks,
      &kCFTypeDictionaryValueCallBacks
   );

   if (result) {
      // Add key for device type to refine the matching dictionary.
      CFNumberRef pageCFNumberRef = CFNumberCreate(
         kCFAllocatorDefault,
         kCFNumberIntType,
         &inUsagePage
      );

      if (pageCFNumberRef) {
         CFStringRef usage_page = CFSTR(kIOHIDDeviceUsagePageKey);

         CFDictionarySetValue(
            result,
            usage_page,
            pageCFNumberRef
         );

         CFRelease(pageCFNumberRef);

         // note: the usage is only valid if the usage page is also defined

         CFNumberRef usageCFNumberRef = CFNumberCreate(
            kCFAllocatorDefault,
            kCFNumberIntType,
            &inUsage
         );

         if (usageCFNumberRef) {
            CFStringRef usage_key = CFSTR(kIOHIDDeviceUsageKey);

            CFDictionarySetValue(
               result,
               usage_key,
               usageCFNumberRef
            );

            CFRelease(usageCFNumberRef);
         }
      }
   }

   return result;
}

static ALLEGRO_JOYSTICK_OSX *find_joystick(IOHIDDeviceRef ident)
{
   int i;
   for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
      ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
      if (ident == joy->ident) {
         return joy;
      }
   }

   return NULL;
}

static const char *get_element_name(IOHIDElementRef elem, const char *default_name)
{
   CFStringRef name = IOHIDElementGetName(elem);
   if (name) {
      return CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
   }
   else
      return default_name;
}

static void joy_null(ALLEGRO_JOYSTICK_OSX *joy)
{
   int i, j;

   // NULL the parent
   for (i = 0; i < _AL_MAX_JOYSTICK_BUTTONS; i++) {
   	joy->parent.info.button[i].name = NULL;
   }
   for (i = 0; i < _AL_MAX_JOYSTICK_STICKS; i++) {
   	joy->parent.info.stick[i].name = NULL;
	for (j = 0; j < _AL_MAX_JOYSTICK_AXES; j++) {
		joy->parent.info.stick[i].axis[j].name = NULL;
	}
   }
}

static void add_axis(ALLEGRO_JOYSTICK_OSX *joy, int stick_index, int axis_index, int min, int max, char *name, IOHIDElementRef elem)
{
   if (axis_index >= _AL_MAX_JOYSTICK_AXES)
      return;

   joy->min[stick_index][axis_index] = min;
   joy->max[stick_index][axis_index] = max;

   joy->parent.info.stick[stick_index].axis[axis_index].name = name;
   joy->parent.info.stick[stick_index].num_axes++;
   joy->axes[stick_index][axis_index] = elem;
}

static void add_elements(CFArrayRef elements, ALLEGRO_JOYSTICK_OSX *joy)
{
   int i;
   char default_name[100];
   int stick_class = -1;
   int axis_index = 0;
   bool old_style = _al_get_joystick_compat_version() < AL_ID(5, 2, 10, 0);

   joy_null(joy);

   for (i = 0; i < CFArrayGetCount(elements); i++) {
      IOHIDElementRef elem = (IOHIDElementRef)CFArrayGetValueAtIndex(
         elements,
         i
      );

      int usage = IOHIDElementGetUsage(elem);
      int usage_page = IOHIDElementGetUsagePage(elem);
      if (IOHIDElementGetType(elem) == kIOHIDElementTypeInput_Button) {
         int idx;
         if (old_style)
            idx = usage - 1;
         else {
            idx = joy->parent.info.num_buttons;
            // 0x09 is the Button Page.
            if (usage_page != 0x09)
               continue;
         }
         if (idx >= 0 && idx < _AL_MAX_JOYSTICK_BUTTONS &&
            !joy->buttons[idx]) {
            joy->buttons[idx] = elem;
            sprintf(default_name, "Button %d", idx);
            const char *name = get_element_name(elem, default_name);
            char *str = al_malloc(strlen(name)+1);
            strcpy(str, name);
            joy->parent.info.button[idx].name = str;
            joy->parent.info.num_buttons++;
         }
      }
      else if (
         IOHIDElementGetType(elem) == kIOHIDElementTypeInput_Misc) {
         long min = IOHIDElementGetLogicalMin(elem);
         long max = IOHIDElementGetLogicalMax(elem);
         int new_stick_class = -1;
         int stick_index = joy->parent.info.num_sticks - 1;

         switch (usage) {
            case kHIDUsage_GD_X:
            case kHIDUsage_GD_Y:
            case kHIDUsage_GD_Z:
               new_stick_class = 1;
               break;

            case kHIDUsage_GD_Rx:
            case kHIDUsage_GD_Ry:
            case kHIDUsage_GD_Rz:
               new_stick_class = 2;
               break;

            case kHIDUsage_GD_Hatswitch:
               new_stick_class = 3;
               break;
         }

         if (new_stick_class < 0)
            continue;

         if (stick_class != new_stick_class) {
            if (joy->parent.info.num_sticks >= _AL_MAX_JOYSTICK_STICKS-1)
               break;

            joy->parent.info.num_sticks++;

            stick_index++;
            axis_index = 0;

            stick_class = new_stick_class;

            char *buf = al_malloc(20);
            sprintf(buf, "Stick %d", stick_index);
            joy->parent.info.stick[stick_index].name = buf;
         }
         else
            axis_index++;

         if (stick_class == 3) {
            joy->dpad_stick = stick_index;
            joy->dpad = elem;

            joy->dpad_axis_horiz = axis_index;
            sprintf(default_name, "Axis %i", axis_index);
            char *str = al_malloc(strlen(default_name)+1);
            strcpy(str, default_name);
            joy->parent.info.stick[stick_index].axis[axis_index].name = str;

            ++axis_index;
            joy->dpad_axis_vert = axis_index;
            sprintf(default_name, "Axis %i", axis_index);
            str = al_malloc(strlen(default_name)+1);
            strcpy(str, default_name);
            add_axis(joy, stick_index, axis_index, min, max, str, elem);
            joy->parent.info.stick[stick_index].axis[axis_index].name = str;

            joy->parent.info.stick[stick_index].num_axes = 2;
         }
         else {
            sprintf(default_name, "Axis %i", axis_index);
            const char *name = get_element_name(elem, default_name);
            char *str = al_malloc(strlen(name)+1);
            strcpy(str, name);
            add_axis(joy, stick_index, axis_index, min, max, str, elem);
         }
      }
   }
}

static void osx_joy_generate_configure_event(void)
{
   if (!initialized) return;

   ALLEGRO_EVENT event;
   event.joystick.type = ALLEGRO_EVENT_JOYSTICK_CONFIGURATION;
   event.joystick.timestamp = al_current_time();

   _al_generate_joystick_event(&event);
}

static void add_joystick_device(IOHIDDeviceRef ref, bool emit_reconfigure_event)
{
   al_lock_mutex(add_mutex);

   ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref);

   if (joy && (joy->cfg_state == JOY_STATE_BORN || joy->cfg_state == JOY_STATE_ALIVE))
   {
     al_unlock_mutex(add_mutex);
     return;
   }

   if (joy == NULL) {
      joy = al_calloc(1, sizeof(ALLEGRO_JOYSTICK_OSX));
      joy->ident = ref;
      ALLEGRO_JOYSTICK_OSX **back = _al_vector_alloc_back(&joysticks);
      *back = joy;
   }

   joy->cfg_state = new_joystick_state;

   CFArrayRef elements = IOHIDDeviceCopyMatchingElements(
      ref,
      NULL,
      kIOHIDOptionsTypeNone
   );

   add_elements(elements, joy);

   CFRelease(elements);

   al_unlock_mutex(add_mutex);

   if (emit_reconfigure_event) osx_joy_generate_configure_event();

   ALLEGRO_INFO("Found joystick (%d buttons, %d sticks)\n",
      joy->parent.info.num_buttons, joy->parent.info.num_sticks);
}

static int enumerate_and_create_initial_joystick_devices(IOHIDManagerRef manager)
{
   int i;
   int num_joysticks_enumerated = 0;

   CFSetRef devices = IOHIDManagerCopyDevices(manager);
   if (devices == NULL)
   {
      // There are no devices to enumerate
   }
   else
   {
      CFIndex num_devices = CFSetGetCount(devices);
      IOHIDDeviceRef *device_arr = al_calloc(num_devices, sizeof(IOHIDDeviceRef));
      CFSetGetValues(devices, (const void **) device_arr);

      for (i = 0; i < num_devices; i++) {
         IOHIDDeviceRef dev = device_arr[i];
         add_joystick_device(dev, false);
         num_joysticks_enumerated++;
      }
      al_free(device_arr);
      CFRelease(devices);
   }

   return num_joysticks_enumerated;
}

static void device_add_callback(
   void *context,
   IOReturn result,
   void *sender,
   IOHIDDeviceRef ref
) {
   (void)context;
   (void)result;
   (void)sender;

   add_joystick_device(ref, true);
}

static void device_remove_callback(
   void *context,
   IOReturn result,
   void *sender,
   IOHIDDeviceRef ref
) {
   (void)context;
   (void)result;
   (void)sender;

   int i;
   for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
      ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
      if (joy->ident == ref) {
         joy->cfg_state = JOY_STATE_DYING;
         osx_joy_generate_configure_event();
         return;
      }
   }
}

static void osx_joy_generate_axis_event(ALLEGRO_JOYSTICK_OSX *joy, int stick, int axis, float pos)
{
   joy->state.stick[stick].axis[axis] = pos;

   ALLEGRO_EVENT event;
   ALLEGRO_EVENT_SOURCE *es = al_get_joystick_event_source();

   if (!_al_event_source_needs_to_generate_event(es))
      return;

   event.joystick.type = ALLEGRO_EVENT_JOYSTICK_AXIS;
   event.joystick.timestamp = al_current_time();
   event.joystick.id = (ALLEGRO_JOYSTICK *)joy;
   event.joystick.stick = stick;
   event.joystick.axis = axis;
   event.joystick.pos = pos;
   event.joystick.button = 0;

   _al_event_source_emit_event(es, &event);
}

static void osx_joy_generate_button_event(ALLEGRO_JOYSTICK_OSX *joy, int button, ALLEGRO_EVENT_TYPE event_type)
{
   joy->state.button[button] = event_type == ALLEGRO_EVENT_JOYSTICK_BUTTON_UP ?
      0 : 1;;

   ALLEGRO_EVENT event;
   ALLEGRO_EVENT_SOURCE *es = al_get_joystick_event_source();

   if (!_al_event_source_needs_to_generate_event(es))
      return;

   event.joystick.type = event_type;
   event.joystick.timestamp = al_current_time();
   event.joystick.id = (ALLEGRO_JOYSTICK *)joy;
   event.joystick.stick = 0;
   event.joystick.axis = 0;
   event.joystick.pos = 0.0;
   event.joystick.button = button;

   _al_event_source_emit_event(es, &event);
}

#define MAX_HAT_DIRECTIONS 8
struct HAT_MAPPING {
   int axisV;
   int axisH;
} hat_mapping[MAX_HAT_DIRECTIONS] = {
   { -1,  0 }, // 0
   { -1,  1 }, // 1
   {  0,  1 }, // 2
   {  1,  1 }, // 3
   {  1,  0 }, // 4
   {  1, -1 }, // 5
   {  0, -1 }, // 6
   { -1, -1 }, // 7
};

static void value_callback(
   void *context,
   IOReturn result,
   void *sender,
   IOHIDValueRef value
) {
   if (!initialized) return;

   (void)context;
   (void)result;
   (void)sender;

   IOHIDElementRef elem = IOHIDValueGetElement(value);
   IOHIDDeviceRef ref = IOHIDElementGetDevice(elem);
   ALLEGRO_JOYSTICK_OSX *joy = find_joystick(ref);

   if (!joy) return;

   ALLEGRO_EVENT_SOURCE *es = al_get_joystick_event_source();
   _al_event_source_lock(es);

   int i;
   for (i = 0; i < joy->parent.info.num_buttons; i++) {
      if (joy->buttons[i] == elem) {
         ALLEGRO_EVENT_TYPE type;
         if (IOHIDValueGetIntegerValue(value) == 0)
            type = ALLEGRO_EVENT_JOYSTICK_BUTTON_UP;
         else
            type = ALLEGRO_EVENT_JOYSTICK_BUTTON_DOWN;
         osx_joy_generate_button_event(joy, i, type);
         goto done;
      }
   }

   int int_value = IOHIDValueGetIntegerValue(value);
   int min = joy->min[joy->dpad_stick][1];
   int max = joy->max[joy->dpad_stick][1];

   if (joy->dpad == elem){
      if (int_value >= min && int_value <= max) {
         int index = int_value - min;
         if (index < MAX_HAT_DIRECTIONS) {
            osx_joy_generate_axis_event(joy, joy->dpad_stick, joy->dpad_axis_vert,  (float)hat_mapping[index].axisV);
            osx_joy_generate_axis_event(joy, joy->dpad_stick, joy->dpad_axis_horiz, (float)hat_mapping[index].axisH);
         }
      } else {
         osx_joy_generate_axis_event(joy, joy->dpad_stick, joy->dpad_axis_vert,  0);
         osx_joy_generate_axis_event(joy, joy->dpad_stick, joy->dpad_axis_horiz, 0);
      }
      goto done;
   }

   int stick = -1;
   int axis = -1;
   for (stick = 0; stick < joy->parent.info.num_sticks; stick++) {
      for(axis = 0; axis < joy->parent.info.stick[stick].num_axes; ++axis) {
         if (joy->axes[stick][axis] == elem) {
            goto gen_axis_event;
         }
      }
   }

   // Unknown event
   goto done;

gen_axis_event:
   {
      float pos;
      long min = joy->min[stick][axis];
      long max = joy->max[stick][axis];
      if (min < 0) {
         if (int_value < 0)
            pos = -(float)int_value/min;
         else
            pos = (float)int_value/max;
      }
      else {
         pos = ((float)int_value/max*2) - 1;
      }

      osx_joy_generate_axis_event(joy, stick, axis, pos);
   }

done:
   _al_event_source_unlock(es);
}

/* init_joystick:
 *  Initializes the HID joystick driver.
 */
static bool init_joystick(void)
{
   add_mutex = al_create_mutex();

   hidManagerRef = IOHIDManagerCreate(
      kCFAllocatorDefault,
      kIOHIDOptionsTypeNone
   );

   if (CFGetTypeID(hidManagerRef) != IOHIDManagerGetTypeID()) {
      ALLEGRO_ERROR("Unable to create HID Manager\n");
      return false;
   }

   // Set which devices we want to match
   CFMutableDictionaryRef criteria0 = CreateDeviceMatchingDictionary(
      GENERIC_DESKTOP_USAGE_PAGE,
      JOYSTICK_USAGE_NUMBER
   );
   CFMutableDictionaryRef criteria1 = CreateDeviceMatchingDictionary(
      GENERIC_DESKTOP_USAGE_PAGE,
      GAMEPAD_USAGE_NUMBER
   );
   CFMutableDictionaryRef criteria_list[] = {
      criteria0,
      criteria1
   };
   CFArrayRef criteria = CFArrayCreate(
      kCFAllocatorDefault,
      (const void **)criteria_list,
      2, NULL
   );

   IOHIDManagerSetDeviceMatchingMultiple(
      hidManagerRef,
      criteria
   );

   CFRelease(criteria0);
   CFRelease(criteria1);
   CFRelease(criteria);

   /* Register for plug/unplug notifications */
   IOHIDManagerRegisterDeviceMatchingCallback(
      hidManagerRef,
      device_add_callback,
      NULL
   );
   IOHIDManagerRegisterDeviceRemovalCallback(
      hidManagerRef,
      device_remove_callback,
      NULL
   );

   // Register for value changes
   IOHIDManagerRegisterInputValueCallback(
      hidManagerRef,
      value_callback,
      NULL
   );

   IOHIDManagerScheduleWithRunLoop(
      hidManagerRef,
      CFRunLoopGetMain(),
      kCFRunLoopDefaultMode
   );

   _al_vector_init(&joysticks, sizeof(ALLEGRO_JOYSTICK_OSX *));

   al_lock_mutex(add_mutex);

   IOReturn ret = IOHIDManagerOpen(
      hidManagerRef,
      kIOHIDOptionsTypeSeizeDevice
   );

   al_unlock_mutex(add_mutex);

   if (ret != kIOReturnSuccess) {
      return false;
   }

   int num_joysticks_created = enumerate_and_create_initial_joystick_devices(hidManagerRef);
   if (num_joysticks_created > 0) osx_joy_generate_configure_event();

   new_joystick_state = JOY_STATE_BORN;

   initialized = true;

   return true;
}

/* exit_joystick:
 *  Shuts down the HID joystick driver.
 */
static void exit_joystick(void)
{
   al_destroy_mutex(add_mutex);

   IOHIDManagerUnscheduleFromRunLoop(
      hidManagerRef,
      CFRunLoopGetCurrent(),
      kCFRunLoopDefaultMode
   );
   // Unregister from value changes
   IOHIDManagerRegisterInputValueCallback(
      hidManagerRef,
      NULL,
      NULL
   );
   IOHIDManagerClose(
      hidManagerRef,
      kIOHIDOptionsTypeNone
   );
   CFRelease(hidManagerRef);

   _al_vector_free(&joysticks);

   initialized = false;
}

/* num_joysticks:
 *  Return number of active joysticks
 */
static int num_joysticks(void)
{
   int i;
   int count = 0;
   for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
      ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
      if (joy->cfg_state == JOY_STATE_ALIVE) {
         count++;
      }
   }

   return count;
}

/* get_joystick:
 * Get a pointer to a joystick structure
 */
static ALLEGRO_JOYSTICK* get_joystick(int index)
{
   ASSERT(index >= 0 && index < (int)_al_vector_size(&joysticks));

   int i;
   int count = 0;
   for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
      ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
      if (joy->cfg_state == JOY_STATE_ALIVE ||
         joy->cfg_state == JOY_STATE_DYING) {
            if (count == index) {
               return (ALLEGRO_JOYSTICK *)joy;
            }
            count++;
      }
   }

   return NULL;
}

/* release_joystick:
 * Release a pointer that has been obtained
 */
static void release_joystick(ALLEGRO_JOYSTICK* joy __attribute__((unused)) )
{
   // No-op
}

/* get_joystick_state:
 * Get the current status of a joystick
 */
static void get_joystick_state(ALLEGRO_JOYSTICK *joy_, ALLEGRO_JOYSTICK_STATE *ret_state)
{
   ALLEGRO_JOYSTICK_OSX *joy = (ALLEGRO_JOYSTICK_OSX *) joy_;
   ALLEGRO_EVENT_SOURCE *es = al_get_joystick_event_source();

   _al_event_source_lock(es);
   {
      *ret_state = joy->state;
   }
   _al_event_source_unlock(es);
}

static bool reconfigure_joysticks(void)
{
   int i;
   bool ret = false;
   for (i = 0; i < (int)_al_vector_size(&joysticks); i++) {
      ALLEGRO_JOYSTICK_OSX *joy = *(ALLEGRO_JOYSTICK_OSX **)_al_vector_ref(&joysticks, i);
      if (joy->cfg_state == JOY_STATE_DYING) {
         joy->cfg_state = JOY_STATE_UNUSED;
         for (i = 0; i < _AL_MAX_JOYSTICK_BUTTONS; i++) {
            al_free((char *)joy->parent.info.button[i].name);
         }
         for (i = 0; i < _AL_MAX_JOYSTICK_STICKS; i++) {
            int j;
            al_free(joy->parent.info.stick[i].name);
            for (j = 0; j < _AL_MAX_JOYSTICK_AXES; j++) {
               al_free(joy->parent.info.stick[i].axis[j].name);
            }
         }
	      joy_null(joy);
         memset(joy->buttons, 0, _AL_MAX_JOYSTICK_BUTTONS*sizeof(IOHIDElementRef));
         memset(&joy->state, 0, sizeof(ALLEGRO_JOYSTICK_STATE));
         joy->dpad=0;
      }
      else if (joy->cfg_state == JOY_STATE_BORN)
         joy->cfg_state = JOY_STATE_ALIVE;
      else
         continue;
      ret = true;
   }

   return ret;
}

static const char *get_joystick_name(ALLEGRO_JOYSTICK *joy_)
{
   ALLEGRO_JOYSTICK_OSX *joy = (ALLEGRO_JOYSTICK_OSX *)joy_;
   CFStringRef str;

   str = IOHIDDeviceGetProperty(joy->ident, CFSTR(kIOHIDProductKey));
   if (str) {
      CFIndex length = CFStringGetLength(str);
      CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
      if (joy->name) {
         al_free(joy->name);
      }
      joy->name = (char *)al_malloc(maxSize);
      if (joy->name) {
         if (CFStringGetCString(str, joy->name, maxSize, kCFStringEncodingUTF8)) {
            return joy->name;
         }
      }
   }
   return "Joystick";
}

static bool get_joystick_active(ALLEGRO_JOYSTICK *joy_)
{
   ALLEGRO_JOYSTICK_OSX *joy = (ALLEGRO_JOYSTICK_OSX *)joy_;
   return joy->cfg_state == JOY_STATE_ALIVE || joy->cfg_state == JOY_STATE_DYING;
}

ALLEGRO_JOYSTICK_DRIVER* _al_osx_get_joystick_driver_10_5(void)
{
   static ALLEGRO_JOYSTICK_DRIVER* vt = NULL;
   if (vt == NULL) {
      vt = al_malloc(sizeof(*vt));
      memset(vt, 0, sizeof(*vt));
      vt->joydrv_ascii_name = "OSX HID Driver";
      vt->init_joystick = init_joystick;
      vt->exit_joystick = exit_joystick;
      vt->reconfigure_joysticks = reconfigure_joysticks;
      vt->num_joysticks = num_joysticks;
      vt->get_joystick = get_joystick;
      vt->release_joystick = release_joystick;
      vt->get_joystick_state = get_joystick_state;
      vt->get_name = get_joystick_name;
      vt->get_active = get_joystick_active;
   }
   return vt;
}

#endif // Leopard+

#ifndef NSAppKitVersionNumber10_5
#define NSAppKitVersionNumber10_5 949
#endif



ALLEGRO_JOYSTICK_DRIVER* _al_osx_get_joystick_driver_10_4(void);
ALLEGRO_JOYSTICK_DRIVER* _al_osx_get_joystick_driver_10_5(void);

ALLEGRO_JOYSTICK_DRIVER* _al_osx_get_joystick_driver(void)
{
   if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_5) {
   	return _al_osx_get_joystick_driver_10_5();
   }
   else {
   	return _al_osx_get_joystick_driver_10_4();
   }
}

/* Local variables:       */
/* c-basic-offset: 3      */
/* indent-tabs-mode: nil  */
/* End:                   */
