/***************************************************************************

  DIJoystick.c

 ***************************************************************************/

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdlib.h>
#include <dinput.h>
#include <assert.h>
#include <stdio.h>
#include <math.h>
#include "win32.h"
#include "misc.h"
#include "input.h"
#include "mouse.h"
#include "DirectInput.h"
#include "DIJoystick.h"


#define MAX_JOY                256
#define MAX_JOY_NAME_LEN       20

#define MAX_PHYSICAL_JOYSTICKS 8
#define MAX_AXES               20

#define MAX_PLAYERS            4

/***************************************************************************
    Function prototypes
 ***************************************************************************/

static int              DIJoystick_init(options_type *osd_options);
static void             DIJoystick_exit(void);
static void             DIJoystick_poll_joysticks(void);
static const struct JoystickInfo *DIJoystick_get_joy_list(void);
static int              DIJoystick_is_joy_pressed(int joycode);
static void             DIJoystick_analogjoy_read(int player, int *analog_x, int *analog_y);
static int              DIJoystick_standard_analog_read(int player, int axis);
static void             DIJoystick_get_device_numbers(int *buf);
static void             DIJoystick_set_device_numbers(int *buf);
static const char       *DIJoystick_get_device_name(int number);
static BOOL             DIJoystick_Available(void);
static BOOL             DIJoystick_OnMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT* pResult);

static BOOL CALLBACK    DIJoystick_EnumDeviceProc(LPDIDEVICEINSTANCE pdidi, LPVOID pv);
static void             DIJoystick_InitJoyList(void);

static BOOL CALLBACK    DIJoystick_EnumAxisObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
static BOOL CALLBACK    DIJoystick_EnumPOVObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
static BOOL CALLBACK    DIJoystick_EnumButtonObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
static void             ClearJoyState(DIJOYSTATE *pdijs);

static void             InitJoystick(void);
static void             ExitJoystick(void);

/***************************************************************************
    External variables
 ***************************************************************************/

struct OSDJoystick  DIJoystick =
{
    DIJoystick_init,                  /* init                 */
    DIJoystick_exit,                  /* exit                 */
    DIJoystick_get_joy_list,          /* get_joy_list         */
    DIJoystick_is_joy_pressed,        /* joy_pressed          */
    DIJoystick_poll_joysticks,        /* poll_joysticks       */
    DIJoystick_analogjoy_read,        /* analogjoy_read       */
    DIJoystick_standard_analog_read,  /* standard_analog_read */
    DIJoystick_get_device_numbers,    /* get_devoice_numbers  */
    DIJoystick_set_device_numbers,    /* set_devoice_numbers  */
    DIJoystick_get_device_name,       /* get_devoice_name     */

    DIJoystick_Available,             /* Available            */
    DIJoystick_OnMessage,             /* OnMessage            */
};

/***************************************************************************
    Internal structures
 ***************************************************************************/

typedef struct
{
    char *name;
    int  offset;
} axis_type;


typedef struct
{
    LPDIRECTINPUTDEVICE2 did;
    DIJOYSTATE           dijs;
    GUID                 guid;
    char                 *name;
    BOOL                 is_light_gun;
    axis_type            axes[MAX_AXES];
    int                  num_axes;
    int                  num_pov;
    int                  num_buttons;
} dijoystick;


/***************************************************************************
    Internal variables
 ***************************************************************************/

static int                 use_count;
static int                 num_joysticks;
static dijoystick          *joysticks[MAX_PLAYERS];
static dijoystick          dijoysticks[MAX_PHYSICAL_JOYSTICKS];

static const GUID          guidNULL = {0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0}};
static struct JoystickInfo joylist[256] = {{ 0, 0, 0 }};

/***************************************************************************
    External OSD functions
 ***************************************************************************/

static int DIJoystick_init(options_type *osd_options)
{
    int i;

    if (use_count == 0)
    {
        HRESULT hr;

        num_joysticks = 0;

        if (di == NULL)
            return 0;

        memset(&dijoysticks, 0, sizeof(dijoystick) * MAX_PHYSICAL_JOYSTICKS);

        hr = IDirectInput_EnumDevices(di, DIDEVTYPE_JOYSTICK,
                                      (LPDIENUMDEVICESCALLBACK)DIJoystick_EnumDeviceProc,
                                      NULL,
                                      DIEDFL_ATTACHEDONLY);

        if (hr != DI_OK || num_joysticks < 1)
            return 0;

        InitJoystick();
    }

    use_count++;

    for (i = 0; i < MAX_PLAYERS; i++)
        joysticks[i] = NULL;

    if (osd_options)
    {
        for (i = 0; i < MAX_PLAYERS; i++)
        {
            if (dijoysticks[osd_options->joyid[i]].did != NULL)
                joysticks[i] = &dijoysticks[osd_options->joyid[i]];
        }
    }
    else
    {
        if (dijoysticks[0].did != NULL)
            joysticks[0] = &dijoysticks[0];
    }

    DIJoystick_InitJoyList();

    return 0;
}


static void DIJoystick_exit(void)
{
    int i;

    if (--use_count == 0)
    {
        for (i = 0; i < MAX_PLAYERS; i++)
            joysticks[i] = NULL;
        ExitJoystick();
        num_joysticks = 0;
    }
}


static const struct JoystickInfo *DIJoystick_get_joy_list(void)
{
    return joylist;
}


static void DIJoystick_poll_joysticks(void)
{
    HRESULT hr;
    DWORD   i;

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        if (joysticks[i] != NULL)
        {
            ClearJoyState(&joysticks[i]->dijs);

            IDirectInputDevice2_Poll(joysticks[i]->did);

            hr = IDirectInputDevice2_GetDeviceState(joysticks[i]->did,
                                                    sizeof(DIJOYSTATE),
                                                    &joysticks[i]->dijs);
            if (FAILED(hr))
            {
                if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
                    hr = IDirectInputDevice2_Acquire(joysticks[i]->did);
                continue;
            }
        }
    }
}


static int DIJoystick_is_joy_pressed(int joycode)
{
    DIJOYSTATE dijs;
    int joy_num;
    int stick;
    int axis;
    int dir;
    int value;
    int dz = 60;

    joy_num = GET_JOYCODE_JOY(joycode);

    /* do we have as many sticks? */
    if (joy_num == 0 || num_joysticks < joy_num)
        return 0;
    joy_num--;

    if (joysticks[joy_num] == NULL)
        return 0;

    dijs  = joysticks[joy_num]->dijs;
    stick = GET_JOYCODE_STICK(joycode);

    if (stick == JOYCODE_STICK_BTN)
    {
        /* buttons */
        int button;

        button = GET_JOYCODE_BUTTON(joycode);
        button--;

        if (button >= joysticks[joy_num]->num_buttons
        ||  GET_JOYCODE_DIR(joycode) != JOYCODE_DIR_BTN)
            return 0;

        return joysticks[joy_num]->dijs.rgbButtons[button] != 0;
    }

    if (stick == JOYCODE_STICK_POV)
    {
        /* POV */
        int pov_value;
        int angle;
        int axis_value;

        int num_pov = GET_JOYCODE_BUTTON(joycode) / 4;
        int code    = GET_JOYCODE_BUTTON(joycode) % 4;
        axis = code / 2;
        dir  = code % 2;

        if (num_pov >= joysticks[joy_num]->num_pov)
            return 0;

        pov_value = joysticks[joy_num]->dijs.rgdwPOV[num_pov];
        if (LOWORD(pov_value) == 0xffff)
            return 0;

        angle = (pov_value + 27000) % 36000;
        angle = (36000 - angle) % 36000;
        angle /= 100;

        /* angle is now in degrees counterclockwise from x axis*/
        if (axis == 1)
            axis_value = 128 + (int)(127 * cos(2 * PI * angle / 360.0)); /* x */
        else
            axis_value = 128 + (int)(127 * sin(2 * PI * angle / 360.0)); /* y */

        if (dir == 1)
            return axis_value <= (128 - 128 * dz / 100);
        else
            return axis_value >= (128 + 128 * dz / 100);
    }

    /* sticks */
    axis = GET_JOYCODE_AXIS(joycode);
    dir  = GET_JOYCODE_DIR(joycode);

    if (axis == 0 || joysticks[joy_num]->num_axes < axis)
        return 0;
    axis--;

    value = *(int *)(((byte *)&dijs) + joysticks[joy_num]->axes[axis].offset);

    if (dir == JOYCODE_DIR_NEG)
        return value <= (128 - 128 * dz / 100);
    else
        return value >= (128 + 128 * dz / 100);
}


static void DIJoystick_analogjoy_read(int player, int *analog_x, int *analog_y)
{
    int i;
    int light_gun_index = -1;


    assert(player >= 0 && player < MAX_PLAYERS);

    *analog_x = *analog_y = 0;

    for (i = 0; i < num_joysticks; i++)
    {
        if (dijoysticks[i].is_light_gun)
        {
            light_gun_index = i;
            break;
        }
    }

    if (light_gun_index == -1)
    {
        /* standard analog joy reading */

        if (num_joysticks <= player)
            return;

        *analog_x = joysticks[player]->dijs.lX - 128;
        *analog_y = joysticks[player]->dijs.lY - 128;
        return;
    }

    /* light gun reading */

    switch (player)
    {
    case 0:
        {
            int x, y;
            POINT pt = { 0, 0 };
            ClientToScreen(MAME32App.m_hWnd, &pt);

            x = dijoysticks[light_gun_index].dijs.lX;
            y = dijoysticks[light_gun_index].dijs.lY;

            /* need to go from screen pixel x,y to -128 to 128 scale */
            *analog_x = (x - pt.x) * 257 / Machine->drv->screen_width - 128;
            if (*analog_x > 128)
                *analog_x = 128;

            *analog_y = (y - pt.y) * 257 / Machine->drv->screen_height - 128;
            if (*analog_y > 128)
                *analog_y = 128;
        }
        break;

    case 1:
        {
            int x, y;
            POINT pt = { 0, 0 };
            ClientToScreen(MAME32App.m_hWnd, &pt);

            x = dijoysticks[light_gun_index].dijs.lZ;
            y = dijoysticks[light_gun_index].dijs.lRz;

            /* need to go from screen pixel x,y to -128 to 128 scale */
            *analog_x = (x - pt.x) * 257 / Machine->drv->screen_width - 128;
            if (*analog_x > 128)
                *analog_x = 128;

            *analog_y = (y - pt.y) * 257 / Machine->drv->screen_height - 128;
            if (*analog_y > 128)
                *analog_y = 128;
        }
        break;

    default:
        /* only support up to 2 light guns (in one joystick device) right now */
        break;
    }
}

static int DIJoystick_standard_analog_read(int player, int axis)
{
    int retval;

    if (num_joysticks <= player || joysticks[player] == NULL)
       return 0;

    switch (axis)
    {
    case X_AXIS:
        retval = (joysticks[player]->dijs.lRz - 128) / (128 / (TRAK_MAXX_RES / 2));
        if (retval > TRAK_MAXX_RES)
            retval = TRAK_MAXX_RES;
        if (retval < -TRAK_MAXX_RES)
            retval = -TRAK_MAXX_RES;
        return retval;

    case Y_AXIS:
        retval = 0;
        return retval;
    }
    return 0;
}

static void DIJoystick_get_device_numbers(int *buf)
{
    int i, j;

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        buf[i] = -1;

        if (joysticks[i] == NULL)
            continue;

        for (j = 0; j < num_joysticks; j++)
        {
            if (joysticks[i] == &dijoysticks[j])
            {
                buf[i] = j;
                break;
            }
        }
    }
}

static void DIJoystick_set_device_numbers(int *buf)
{
    int i;

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        if (buf[i] == -1)
        {
            joysticks[i] = NULL;
            continue;
        }

        if (dijoysticks[buf[i]].did != NULL)
            joysticks[i] = &dijoysticks[buf[i]];
        else
            joysticks[i] = NULL;
    }

    DIJoystick_InitJoyList();
}

static const char *DIJoystick_get_device_name(int number)
{
#ifdef JAPANESE
    static char *not_found = "なし";
#else
    static char *not_found = "Not Found";
#endif

    if (number < 0 || number >= MAX_PHYSICAL_JOYSTICKS)
        return not_found;

    if (dijoysticks[number].did)
        return dijoysticks[number].name;
    else
        return not_found;
}

static BOOL DIJoystick_Available(void)
{
    static BOOL bBeenHere = FALSE;
    static BOOL bAvailable = FALSE;
    HRESULT     hr;
    GUID        guid = guidNULL;
    LPDIRECTINPUTDEVICE didTemp;
    LPDIRECTINPUTDEVICE didJoystick;

    if (di == NULL)
        return FALSE;

    if (bBeenHere == FALSE)
        bBeenHere = TRUE;
    else
        return bAvailable;

    /* enumerate for joystick devices */
    hr = IDirectInput_EnumDevices(di, DIDEVTYPE_JOYSTICK,
                                  inputEnumDeviceProc,
                                  &guid,
                                  DIEDFL_ATTACHEDONLY);
    if (FAILED(hr))
       return FALSE;

    /* Are there any joysticks attached? */
    if (IsEqualGUID(&guid, &guidNULL))
        return FALSE;

    hr = IDirectInput_CreateDevice(di, &guid, &didTemp, NULL);
    if (FAILED(hr))
        return FALSE;

    /* Determine if DX5 is available by a QI for a DX5 interface. */
    hr = IDirectInputDevice_QueryInterface(didTemp,
                                           &IID_IDirectInputDevice2,
                                           (void**)&didJoystick);
    if (FAILED(hr))
    {
        bAvailable = FALSE;
    }
    else
    {
        bAvailable = TRUE;
        IDirectInputDevice_Release(didJoystick);
    }

    /* dispose of the temp interface */
    IDirectInputDevice_Release(didTemp);

    return bAvailable;
}

static BOOL DIJoystick_OnMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
{
    return FALSE;
}

/***************************************************************************
    Internal functions
 ***************************************************************************/

BOOL CALLBACK DIJoystick_EnumDeviceProc(LPDIDEVICEINSTANCE pdidi, LPVOID pv)
{
    char buffer[1024];

    dijoysticks[num_joysticks].guid = pdidi->guidInstance;

//  sprintf(buffer, "%s (%s)", pdidi->tszProductName, pdidi->tszInstanceName);
    sprintf(buffer, "%s", pdidi->tszProductName);

    dijoysticks[num_joysticks].name = (char *)malloc(strlen(buffer) + 1);
    strcpy(dijoysticks[num_joysticks].name, buffer);

    num_joysticks++;

    return DIENUM_CONTINUE;
}


static BOOL CALLBACK DIJoystick_EnumAxisObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi,
                                                    LPVOID pvRef)
{
    dijoystick *joystick = (dijoystick *)pvRef;
    DIPROPRANGE diprg;

    joystick->axes[joystick->num_axes].offset = lpddoi->dwOfs;
    joystick->axes[joystick->num_axes].name   = (char *)malloc(strlen(lpddoi->tszName) + 1);
    strcpy(joystick->axes[joystick->num_axes].name, lpddoi->tszName);

    memset(&diprg, 0, sizeof(diprg));
    diprg.diph.dwSize       = sizeof(diprg);
    diprg.diph.dwHeaderSize = sizeof(diprg.diph);
    diprg.diph.dwObj        = lpddoi->dwOfs;
    diprg.diph.dwHow        = DIPH_BYOFFSET;
    diprg.lMin              = 0;
    diprg.lMax              = 255;

    if (IDirectInputDevice2_SetProperty(joystick->did, DIPROP_RANGE, &diprg.diph) != DI_OK)
        goto error_axes;

    if (SetDIDwordProperty(joystick->did, DIPROP_DEADZONE, lpddoi->dwOfs, DIPH_BYOFFSET, 0) != DI_OK)
        goto error_axes;

    joystick->num_axes++;

    return DIENUM_CONTINUE;

error_axes:
    free(joystick->axes[joystick->num_axes].name);
    joystick->axes[joystick->num_axes].name = NULL;
    return DIENUM_CONTINUE;
}

static BOOL CALLBACK DIJoystick_EnumPOVObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
    dijoystick *joystick = (dijoystick *)pvRef;
    joystick->num_pov++;

    return DIENUM_CONTINUE;
}

static BOOL CALLBACK DIJoystick_EnumButtonObjectsProc(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
    dijoystick *joystick = (dijoystick *)pvRef;
    joystick->num_buttons++;

    return DIENUM_CONTINUE;
}

static void ClearJoyState(DIJOYSTATE *pdijs)
{
    memset(pdijs, 0, sizeof(DIJOYSTATE));
    pdijs->lX           = 128;
    pdijs->lY           = 128;
    pdijs->lZ           = 128;
    pdijs->lRx          = 128;
    pdijs->lRy          = 128;
    pdijs->lRz          = 128;
    pdijs->rglSlider[0] = 128;
    pdijs->rglSlider[1] = 128;
    pdijs->rgdwPOV[0]   = -1;
    pdijs->rgdwPOV[1]   = -1;
    pdijs->rgdwPOV[2]   = -1;
    pdijs->rgdwPOV[3]   = -1;
}


static void ReleaseJoystick(dijoystick *dijoy)
{
    int i;

    if (dijoy->did != NULL)
    {
        for (i = 0; i < dijoy->num_axes; i++)
        {
            if (dijoy->axes[i].name)
                free(dijoy->axes[i].name);
            dijoy->axes[i].name = NULL;
        }

        IDirectInputDevice_Unacquire(dijoy->did);
        IDirectInputDevice_Release(dijoy->did);
        dijoy->did = NULL;

        if (dijoy->name != NULL)
        {
            free(dijoy->name);
            dijoy->name = NULL;
        }
    }
}

static void CreateJoystick(dijoystick *dijoy)
{
    LPDIRECTINPUTDEVICE didTemp;
    HRESULT hr;

    if (IDirectInput_CreateDevice(di, &dijoy->guid, &didTemp, NULL) != DI_OK)
        return;

    hr = IDirectInputDevice_QueryInterface(didTemp, &IID_IDirectInputDevice2, (void **)&dijoy->did);
    IDirectInputDevice_Release(didTemp);
    if (hr != DI_OK)
    {
        dijoy->did = NULL;
        return;
    }

    if (IDirectInputDevice2_SetCooperativeLevel(dijoy->did, MAME32App.m_hWnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND) != DI_OK)
        goto release;

    if (IDirectInputDevice2_SetDataFormat(dijoy->did, &c_dfDIJoystick) != DI_OK)
        goto release;

    if (dijoy->is_light_gun)
    {
        DIPROPDWORD diprop;
        memset(&diprop, 0, sizeof(diprop));
        diprop.diph.dwSize       = sizeof(DIPROPDWORD);
        diprop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        diprop.diph.dwObj        = 0;
        diprop.diph.dwHow        = DIPH_DEVICE;
        diprop.dwData            = DIPROPCALIBRATIONMODE_RAW;

        if (IDirectInputDevice2_SetProperty(dijoy->did, DIPROP_CALIBRATIONMODE, &diprop.diph) != DI_OK)
            goto release;
    }
    else
    {
        int i, j;

        if (IDirectInputDevice_EnumObjects(dijoy->did, DIJoystick_EnumAxisObjectsProc, dijoy, DIDFT_AXIS) != DI_OK)
            goto release;

        for (i = 0; i < dijoy->num_axes - 1; i++)
        {
            for (j = i + 1; j < dijoy->num_axes; j++)
            {
                if (dijoy->axes[i].offset > dijoy->axes[j].offset)
                {
                    axis_type tmpaxes;

                    memcpy(&tmpaxes,        &dijoy->axes[i], sizeof(axis_type));
                    memcpy(&dijoy->axes[i], &dijoy->axes[j], sizeof(axis_type));
                    memcpy(&dijoy->axes[j], &tmpaxes,        sizeof(axis_type));
                }
            }
        }

        if (IDirectInputDevice_EnumObjects(dijoy->did, DIJoystick_EnumPOVObjectsProc, dijoy, DIDFT_POV) != DI_OK)
            goto release;
    }

    if (IDirectInputDevice_EnumObjects(dijoy->did, DIJoystick_EnumButtonObjectsProc, dijoy, DIDFT_BUTTON) != DI_OK)
        goto release;

    if (IDirectInputDevice2_Acquire(dijoy->did) != DI_OK)
        goto release;

    ClearJoyState(&dijoy->dijs);
    return;

release:
    ReleaseJoystick(dijoy);
}

static void InitJoystick(void)
{
    int i;

    for (i = 0; i < num_joysticks; i++)
    {
        dijoysticks[i].did          = NULL;
        dijoysticks[i].num_axes     = 0;
        dijoysticks[i].num_pov      = 0;
        dijoysticks[i].num_buttons  = 0;
        dijoysticks[i].is_light_gun = (strcmp(dijoysticks[i].name, "ACT LABS GS (ACT LABS GS)") == 0);

        CreateJoystick(&dijoysticks[i]);
    }
}

static void ExitJoystick(void)
{
    int i;

    for (i = 0; i < num_joysticks; i++)
         ReleaseJoystick(&dijoysticks[i]);
}

static void DIJoystick_InitJoyList(void)
{
    static int joyequiv[][2] =
    {
        { JOYCODE(1, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_NEG), JOYCODE_1_LEFT    },
        { JOYCODE(1, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_POS), JOYCODE_1_RIGHT   },
        { JOYCODE(1, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_NEG), JOYCODE_1_UP      },
        { JOYCODE(1, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_POS), JOYCODE_1_DOWN    },

        { JOYCODE(1, JOYCODE_STICK_BTN,  1, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON1 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  2, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON2 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  3, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON3 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  4, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON4 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  5, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON5 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  6, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON6 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  7, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON7 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  8, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON8 },
        { JOYCODE(1, JOYCODE_STICK_BTN,  9, JOYCODE_DIR_BTN), JOYCODE_1_BUTTON9 },

        { JOYCODE(2, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_NEG), JOYCODE_2_LEFT    },
        { JOYCODE(2, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_POS), JOYCODE_2_RIGHT   },
        { JOYCODE(2, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_NEG), JOYCODE_2_UP      },
        { JOYCODE(2, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_POS), JOYCODE_2_DOWN    },

        { JOYCODE(2, JOYCODE_STICK_BTN,  1, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON1 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  2, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON2 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  3, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON3 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  4, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON4 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  5, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON5 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  6, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON6 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  7, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON7 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  8, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON8 },
        { JOYCODE(2, JOYCODE_STICK_BTN,  9, JOYCODE_DIR_BTN), JOYCODE_2_BUTTON9 },


        { JOYCODE(3, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_NEG), JOYCODE_3_LEFT    },
        { JOYCODE(3, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_POS), JOYCODE_3_RIGHT   },
        { JOYCODE(3, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_NEG), JOYCODE_3_UP      },
        { JOYCODE(3, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_POS), JOYCODE_3_DOWN    },

        { JOYCODE(3, JOYCODE_STICK_BTN,  1, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON1 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  2, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON2 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  3, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON3 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  4, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON4 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  5, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON5 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  6, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON6 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  7, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON7 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  8, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON8 },
        { JOYCODE(3, JOYCODE_STICK_BTN,  9, JOYCODE_DIR_BTN), JOYCODE_3_BUTTON9 },


        { JOYCODE(4, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_NEG), JOYCODE_4_LEFT    },
        { JOYCODE(4, JOYCODE_STICK_AXIS, 1, JOYCODE_DIR_POS), JOYCODE_4_RIGHT   },
        { JOYCODE(4, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_NEG), JOYCODE_4_UP      },
        { JOYCODE(4, JOYCODE_STICK_AXIS, 2, JOYCODE_DIR_POS), JOYCODE_4_DOWN    },

        { JOYCODE(4, JOYCODE_STICK_BTN,  1, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON1 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  2, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON2 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  3, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON3 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  4, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON4 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  5, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON5 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  6, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON6 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  7, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON7 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  8, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON8 },
        { JOYCODE(4, JOYCODE_STICK_BTN,  9, JOYCODE_DIR_BTN), JOYCODE_4_BUTTON9 },

        { 0, 0 }
    };
    static char joynames[MAX_JOY][MAX_JOY_NAME_LEN + 1]; /* will be used to store names */
#ifdef JAPANESE
    static const char *JoyPOVName[] = { "前", "後", "右", "左" };
#else
    static const char *JoyPOVName[] = { "Forward", "Backward", "Right", "Left"};
#endif
    int tot, i, j, pov;
    char buf[256];

    tot = 0;

    /* first of all, map mouse buttons */
    for (j = 0; j < 5; j++)
    {
#ifdef JAPANESE
        sprintf(buf, "マウス ボタン%d", j + 1);
#else
        sprintf(buf, "Mouse B%d", j + 1);
#endif
        strncpy(joynames[tot], buf, MAX_JOY_NAME_LEN);
        joynames[tot][MAX_JOY_NAME_LEN] = 0;
        joylist[tot].name = joynames[tot];
        joylist[tot].code = MOUSE_BUTTON(j + 1);
        tot++;
    }

    for (i = 0; i < MAX_PLAYERS; i++)
    {
        if (joysticks[i] == NULL)
            continue;

        for (j = 0; j < joysticks[i]->num_axes; j++)
        {
            sprintf(buf, "J%d %.7s %s -",
                    i + 1,
                    joysticks[i]->name,
                    joysticks[i]->axes[j].name);
            strncpy(joynames[tot], buf, MAX_JOY_NAME_LEN);
            joynames[tot][MAX_JOY_NAME_LEN] = 0;
            joylist[tot].name = joynames[tot];
            joylist[tot].code = JOYCODE(i + 1, JOYCODE_STICK_AXIS, j + 1, JOYCODE_DIR_NEG);
            tot++;

            sprintf(buf, "J%d %.7s %s +",
                    i + 1,
                    joysticks[i]->name,
                    joysticks[i]->axes[j].name);
            strncpy(joynames[tot], buf, MAX_JOY_NAME_LEN);
            joynames[tot][MAX_JOY_NAME_LEN] = 0;
            joylist[tot].name = joynames[tot];
            joylist[tot].code = JOYCODE(i + 1, JOYCODE_STICK_AXIS, j + 1, JOYCODE_DIR_POS);
            tot++;
        }

        for (j = 0; j < joysticks[i]->num_buttons; j++)
        {
#ifdef JAPANESE
            sprintf(buf, "J%d ボタン %d", i + 1, j);
#else
            sprintf(buf, "J%d Button %d", i + 1, j);
#endif
            strncpy(joynames[tot], buf, MAX_JOY_NAME_LEN);
            joynames[tot][MAX_JOY_NAME_LEN] = 0;
            joylist[tot].name = joynames[tot];
            joylist[tot].code = JOYCODE(i + 1, JOYCODE_STICK_BTN, j + 1, JOYCODE_DIR_BTN);
            tot++;
        }

        for (pov = 0; pov < joysticks[i]->num_pov; pov++)
        {
            for (j = 0; j < 4; j++)
            {
                sprintf(buf, "J%i POV%i %s", i + 1, pov + 1, JoyPOVName[j]);
                strncpy(joynames[tot], buf, MAX_JOY_NAME_LEN);
                joynames[tot][MAX_JOY_NAME_LEN] = 0;
                joylist[tot].name = joynames[tot];
                joylist[tot].code = JOYCODE(i + 1, JOYCODE_STICK_POV, 4 * pov + j, JOYCODE_DIR_BTN);
                tot++;
            }
        }
    }

    /* terminate array */
    joylist[tot].name = 0;
    joylist[tot].code = 0;
    joylist[tot].standardcode = 0;

    /* fill in equivalences */
    for (i = 0; i < tot; i++)
    {
        joylist[i].standardcode = JOYCODE_OTHER;

        j = 0;
        while (joyequiv[j][0] != 0)
        {
            if (joyequiv[j][0] == joylist[i].code)
            {
                joylist[i].standardcode = joyequiv[j][1];
                break;
            }
            j++;
        }
    }
}
