/*
 * Linux 2.4 joystick interface
 *
 * Copyright 2002 TransGaming Technologies Inc.
 */

#include "config.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#ifdef HAVE_LINUX_INPUT_H
#include <linux/input.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
#include <math.h>

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "mmddk.h"
#include "dinput.h"
#include "wine/debug.h"

#include "joy_drv.h"

WINE_DEFAULT_DEBUG_CHANNEL(joystick);

extern void LINUX22_DevName(WINE_JOYSTICK* jstck);
extern LONG LINUX22_GetDevCaps(WINE_JOYSTICK* jstck);
extern LONG LINUX22_GetPosEx(WINE_JOYSTICK* jstck, LPJOYINFOEX lpInfo);

#ifdef FF_RAMP /* updated linux/input.h */
#define HAVE_FORCE_FEEDBACK
#else
#warning "update /usr/include/linux/input.h if you want forcefeedback"
#endif

#ifdef HAVE_CORRECT_LINUXINPUT_H

#define test_bit(bit,arr) (((BYTE*)(arr))[(bit)>>3]&(1<<((bit)&7)))

#define FIRST_BTN BTN_MISC

#define MAX_BUTTONS 32
#define MAX_ABSAXES 8
#define MAX_HATS    4
#define MAX_EFFECTS (FF_GAIN-FF_RUMBLE)
#define MAX_RUNNING (FF_RUMBLE)

typedef struct {
    DWORD dwVersion;
    DIDEVCAPS dicaps;
    DIDEVICEINSTANCEA diinfo;
    int fd;
    int abs_axes;
    int rel_axes;
    int buttons;
    int hats;
    char abs_axis_map[ABS_MAX+1];
    char abs_axis_rmap[MAX_ABSAXES];
    char rel_axis_map[REL_MAX+1];
    char button_map[(KEY_MAX-FIRST_BTN)+1];
    short button_rmap[MAX_BUTTONS];
    char hat_map[MAX_HATS];
    char hat_rmap[MAX_HATS];
    int abs_axis_info[ABS_MAX+1][5];
    DIDATAFORMAT df;
    int init;
#ifdef HAVE_FORCE_FEEDBACK
    int effects;
    int effaxes;
    char effect_map[FF_MAX+1];
    char effstatus[MAX_RUNNING];
    int dieffects;
    DIEFFECTINFOA dieffinfo[MAX_EFFECTS];
#endif
} JOY_DATA;

#ifdef HAVE_FORCE_FEEDBACK
struct effect_entry {
    const int		ff;
    const GUID		*guid;
    const DWORD		type;
    const DWORD		parms;
    const char		*name;
};

#define T_CONSTANT  (DIEFT_CONSTANTFORCE | DIEFT_FFATTACK | DIEFT_FFFADE | \
		     DIEFT_STARTDELAY)
#define T_RAMP      (DIEFT_RAMPFORCE | DIEFT_FFATTACK | DIEFT_FFFADE | \
		     DIEFT_STARTDELAY)
#define T_PERIODIC  (DIEFT_PERIODIC | DIEFT_FFATTACK | DIEFT_FFFADE | \
		     DIEFT_STARTDELAY)
#define T_CONDITION (DIEFT_CONDITION | DIEFT_SATURATION | \
		     DIEFT_POSNEGSATURATION | DIEFT_POSNEGCOEFFICIENTS | \
		     DIEFT_DEADBAND | DIEFT_STARTDELAY)

#define P_COMMON    (DIEP_DURATION | DIEP_AXES | DIEP_DIRECTION | \
		     DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | \
		     DIEP_TYPESPECIFICPARAMS | DIEP_STARTDELAY)
#define P_CONSTANT  (P_COMMON | DIEP_ENVELOPE)
#define P_RAMP      (P_COMMON | DIEP_ENVELOPE)
#define P_PERIODIC  (P_COMMON | DIEP_ENVELOPE | DIEP_SAMPLEPERIOD)
#define P_CONDITION (P_COMMON)

static struct effect_entry effect_table[] = {
    { FF_CONSTANT, &GUID_ConstantForce, T_CONSTANT,  P_CONSTANT,  "Constant" },
    { FF_RAMP,	   &GUID_RampForce,     T_RAMP,      P_RAMP    ,  "Ramp" },
    { FF_SQUARE,   &GUID_Square,        T_PERIODIC,  P_PERIODIC,  "Square" },
    { FF_SINE,     &GUID_Sine,          T_PERIODIC,  P_PERIODIC,  "Sine" },
    { FF_TRIANGLE, &GUID_Triangle,      T_PERIODIC,  P_PERIODIC,  "Triangle" },
    { FF_SAW_UP,   &GUID_SawtoothUp,    T_PERIODIC,  P_PERIODIC,  "SawtoothUp" },
    { FF_SAW_DOWN, &GUID_SawtoothDown,  T_PERIODIC,  P_PERIODIC,  "SawtoothDown" },
    { FF_SPRING,   &GUID_Spring,        T_CONDITION, P_CONDITION, "Spring" },
    { FF_DAMPER,   &GUID_Damper,        T_CONDITION, P_CONDITION, "Damper" },
    { FF_INERTIA,  &GUID_Inertia,       T_CONDITION, P_CONDITION, "Inertia" },
    { FF_FRICTION, &GUID_Friction,      T_CONDITION, P_CONDITION, "Friction" },
    { 0,           NULL,                0,           0,           NULL }
};

static struct effect_entry *effect_by_ff(int ff)
{
    struct effect_entry *p;

    for (p = effect_table; p->ff; p++)
	if (p->ff == ff)
	    return p;

    return NULL;
}

static struct effect_entry *effect_by_guid(GUID *guid)
{
    struct effect_entry *p;

    for (p = effect_table; p->ff; p++)
	if (IsEqualGUID(p->guid, guid))
	    return p;

    return NULL;
}
#endif /* HAVE_FORCE_FEEDBACK */

/* Not all event devices are joysticks.  Try to find the appropriate
 * event device for the requested joystick instance.  For our
 * purposes, a joystick must have absolute X and Y axes or an absolute
 * wheel axis, and a trigger, a or 1 button. */

#define MAX_EVENT_DEVS 64

static int find_event_joystick(int n)
{
    int		i, fd, ndet = 0;
    char	devname[32];
    BYTE	absbits[(ABS_MAX+7)/8];
    BYTE	keybits[(KEY_MAX+7)/8];

    for (i = 0; i < MAX_EVENT_DEVS; i++) {
	sprintf(devname, "/dev/input/event%d", i);
	if ((fd = open(devname, O_RDONLY | O_NONBLOCK)) < 0)
	    continue;
	memset(absbits, 0, sizeof(absbits));
	memset(keybits, 0, sizeof(keybits));
	if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) < 0 ||
	    ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) < 0) {
	    close(fd);
	    continue;
	}

	if ((!test_bit(ABS_X, absbits) || !test_bit(ABS_Y, absbits)) &&
	    !test_bit(ABS_WHEEL, absbits))
	    continue;

	if (test_bit(BTN_MOUSE, keybits)) /* no mice */
	    continue;

	if (test_bit(BTN_DIGI, keybits)) /* no graphic tablets */
	    continue;

	if (ndet == n) {
	    TRACE("joystick%d found on %s\n", n, devname);
	    return i;
	}
	ndet++;
    }

    TRACE("joystick%d not found\n", n);
    return -1;
}

/* Because there seems to be no way to get the current button state after
 * an open() like we rely on in GetPosEx, we'll delegate ordinary winmm
 * requests like that to the linux 2.2 compatibility device /dev/input/jsX,
 * and only use the new /dev/input/eventX device in DirectInput mode */

void LINUXINPUT_DevName(WINE_JOYSTICK* jstck)
{
    TRACE("(%p)\n", jstck);
    /* the 2.2 compatibility device on 2.4 systems is /dev/input/js,
     * not /dev/js, which does not always exist */
    sprintf(jstck->devname, "/dev/input/js%d", jstck->joyIntf);
    /* Fix devIntf for event devices */
    jstck->devIntf = find_event_joystick(jstck->joyIntf);
}

LONG LINUXINPUT_GetDevCaps(WINE_JOYSTICK* jstck)
{
    LPJOYCAPSA lpCaps = &jstck->caps;
    LPDIDEVCAPS lpDICaps = &jstck->dicaps;
    LPDIDEVICEINSTANCEA lpDIInfo = &jstck->diinfo;
    char	devname[32];
    int		dev;
    int		driverVersion;
    LONG	err;

    TRACE("(%p)\n", jstck);

    /* check that the new API is available */
    sprintf(devname, "/dev/input/event%d", jstck->devIntf);
    if ((dev = open(devname, O_RDONLY | O_NONBLOCK)) < 0) {
	if (errno == ENODEV) return JOYERR_UNPLUGGED;
	else if (errno == ENOENT) return MMSYSERR_NODRIVER;
	else if (errno == EBUSY) return MMSYSERR_ALLOCATED;
	else return MMSYSERR_ERROR;
    }
    if (ioctl(dev, EVIOCGVERSION, &driverVersion) < 0) {
	close(dev);
	return MMSYSERR_NODRIVER;
    }
    close(dev);

    /* use old API for standard GetDevCaps */
    err = LINUX22_GetDevCaps(jstck);
    if (err == MMSYSERR_ALLOCATED) return err;

    if (err != JOYERR_NOERROR) {
	/* winmm joystick API can't be supported */
	memset(lpCaps, 0, sizeof(JOYCAPSA));
	memset(lpDICaps, 0, sizeof(DIDEVCAPS));
	memset(lpDIInfo, 0, sizeof(DIDEVICEINSTANCEA));

        /* the guidInstance is supposed to identify the joystick,
         * this is enough for that purpose */
        lpDIInfo->guidInstance.Data1 = jstck->joyIntf;
    }
    /* dinput caps for the 2.4 interface will be retrieved when needed */
    /* these caps are still useful for the 2.2 API (used by GetPosEx), let's not clear them */
    /* memset(lpDICaps, 0, sizeof(DIDEVCAPS)); */
    /* memset(lpDIInfo, 0, sizeof(DIDEVICEINSTANCEA)); */

    /* return success */
    return JOYERR_NOERROR;
}

static LONG LINUXINPUT_OpenDev(WINE_JOYSTICK* jstck, DWORD dwVersion, JOY_DATA* data)
{
    char	devname[32];
    LPDIDEVCAPS lpDICaps;
    LPDIDEVICEINSTANCEA lpDIInfo;
    JMAP	*jmap, *cmap;
    int		dev;
    char	identString[MAX_PATH];
    BYTE	absbits[(ABS_MAX+7)/8];
    BYTE	keybits[(KEY_MAX+7)/8];
    BYTE	relbits[(REL_MAX+7)/8];
#ifdef HAVE_FORCE_FEEDBACK
    BYTE	ffbits[(FF_MAX+7)/8];
#endif
    int		driverVersion;
    unsigned	u;
    int		rw;

    TRACE("(%p,%lx,%p)\n", jstck, dwVersion, data);
    sprintf(devname, "/dev/input/event%d", jstck->devIntf);
    /* Try read-write mode first -- force-feedback needs it */
    rw = 1;
    if (dwVersion < 0x500 || 
	(dev = open(devname, O_RDWR | O_NONBLOCK)) < 0) {
	/* Fallback to read-only mode -- disables force-feedback */
	rw = 0;
	if ((dev = open(devname, O_RDONLY | O_NONBLOCK)) < 0) {
	    if (errno == ENODEV) return JOYERR_UNPLUGGED;
	    else if (errno == ENOENT) return MMSYSERR_NODRIVER;
	    else if (errno == EBUSY) return MMSYSERR_ALLOCATED;
	    else return MMSYSERR_ERROR;
	}
    }
    if (ioctl(dev, EVIOCGVERSION, &driverVersion) < 0) {
	close(dev);
	return MMSYSERR_NODRIVER;
    }
    /* EVIOCGNAME doesn't nul terminate if the buffer is too small so
     * do it here just in case. */
    identString[sizeof(identString)-1] = 0;
    if (ioctl(dev, EVIOCGNAME(sizeof(identString)-1), &identString) < 0) strcpy(identString, "");
    TRACE("Driver: 0x%06x, Name: %s\n", driverVersion, identString);

    if (!data) {
	if (!jstck->internal)
	    jstck->internal = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(JOY_DATA));
	data = jstck->internal;
    }
    lpDICaps = &data->dicaps;
    lpDIInfo = &data->diinfo;

    data->dwVersion = dwVersion;

    /* get user-defined axis mapping */
    jmap = JSTCK_get_joy_map(identString);

    /* count and map buttons */
    data->buttons = 0;
    memset(keybits, 0, sizeof(keybits));
    if (ioctl(dev, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) >= 0) {
	for (u=BTN_JOYSTICK; u<=KEY_MAX && data->buttons<MAX_BUTTONS; u++) {
	    if (test_bit(u, keybits)) {
		TRACE("button[%d] = %d\n", data->buttons, u);
		data->button_rmap[data->buttons] = u;
		data->button_map[u-FIRST_BTN] = data->buttons++;
	    } else
		data->button_map[u-FIRST_BTN] = -1;
	}
	for (u=FIRST_BTN; u<BTN_JOYSTICK && data->buttons<MAX_BUTTONS; u++) {
	    if (test_bit(u, keybits)) {
		TRACE("button[%d] = %d\n", data->buttons, u);
		data->button_rmap[data->buttons] = u;
		data->button_map[u-FIRST_BTN] = data->buttons++;
	    } else
		data->button_map[u-FIRST_BTN] = -1;
	}
    }

    /* count and map axes and hats */
    data->abs_axes = 0;
    data->hats = 0;
    cmap = jmap;
    memset(absbits, 0, sizeof(absbits));
    if (ioctl(dev, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits) >= 0) {
	for (u=0; u<MAX_HATS; u++) {
	    data->hat_map[u] = -1;
	}
	for (u=0; u<=ABS_MAX; u++) {
	    JMAP jm = ((u >= ABS_HAT0X) && (u <= ABS_HAT3Y)) ? JMAP_HAT : JMAP_SLIDER;
	    if (cmap && (*cmap == JMAP_END)) cmap = NULL;
	    if (cmap) jm = *cmap;

	    /* we cannot yet convert non-hat axes to hats */
	    if ((jm == JMAP_HAT) && (u >= ABS_HAT0X) && (u <= ABS_HAT3Y) &&
		(((u - ABS_HAT0X) & 1) == 0) &&
		test_bit(u, absbits) && test_bit(u+1, absbits) &&
		(data->hats < MAX_HATS)) {
		/* detected a hat */
		TRACE("hat[%d] = %d,%d\n", data->hats, u, u+1);
		data->hat_rmap[data->hats] = u;
		data->hat_map[(u-ABS_HAT0X)/2] = data->hats++;
		data->abs_axis_map[u] = -1;
		data->abs_axis_map[u+1] = -1;
		u++;
		if (cmap) cmap++;
		continue;
	    }

	    if (test_bit(u, absbits) && (data->abs_axes < MAX_ABSAXES)) {
		/* detected an ordinary axis */
		if (jm == JMAP_DISABLE) {
		    data->abs_axis_map[u] = -1;
		    if (cmap) cmap++;
		    continue;
		}
		TRACE("abs_axis[%d] = %d\n", data->abs_axes, u);
		data->abs_axis_rmap[data->abs_axes] = u;
		data->abs_axis_map[u] = data->abs_axes++;
		ioctl(dev, EVIOCGABS(u), data->abs_axis_info[u]);
		if (cmap) cmap++;
	    } else
		data->abs_axis_map[u] = -1;
	}
    }
    data->rel_axes = 0;
    memset(relbits, 0, sizeof(relbits));
    if (ioctl(dev, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits) >= 0) {
	for (u=0; u<=REL_MAX; u++) {
	    if (test_bit(u, relbits)) {
		TRACE("rel_axis[%d] = %d\n", data->rel_axes, u);
		data->rel_axis_map[u] = data->rel_axes++;
	    } else
		data->rel_axis_map[u] = -1;
	}
    }

    TRACE("detected: abs axes=%d, rel axes=%d, hats=%d, buttons=%d\n",
	  data->abs_axes, data->rel_axes, data->hats, data->buttons);

    /* FIXME: handle relative axes somehow */

    memset(lpDICaps, 0, sizeof(DIDEVCAPS));
    lpDICaps->dwSize = sizeof(DIDEVCAPS);
    lpDICaps->dwFlags = DIDC_ATTACHED;
    /* FIXME: we might be able to figure out controller type
     * by checking what kind of axes and buttons it has */
    if (HIBYTE(dwVersion) >= 8)
	lpDICaps->dwDevType = DI8DEVTYPE_JOYSTICK |
			     (DI8DEVTYPEJOYSTICK_STANDARD<<8);
    else
	lpDICaps->dwDevType = DIDEVTYPE_JOYSTICK |
			     (DIDEVTYPEJOYSTICK_TRADITIONAL<<8);
    lpDICaps->dwAxes = data->abs_axes;
    lpDICaps->dwButtons = data->buttons;
    lpDICaps->dwPOVs = data->hats;

    memset(lpDIInfo, 0, sizeof(DIDEVICEINSTANCEA));
    lpDIInfo->dwSize = sizeof(DIDEVICEINSTANCEA);
    memcpy(&lpDIInfo->guidInstance, &jstck->diinfo.guidInstance, sizeof(GUID));
    memcpy(&lpDIInfo->guidProduct, &GUID_Joystick, sizeof(GUID));
    lpDIInfo->dwDevType = lpDICaps->dwDevType;
    sprintf(lpDIInfo->tszInstanceName, "Joystick %d", jstck->joyIntf+1);
    strncpy(lpDIInfo->tszProductName, identString, MAX_PATH);
    lpDIInfo->tszProductName[MAX_PATH-1] = '\0';

#ifdef HAVE_FORCE_FEEDBACK
    /* count and map effects */
    data->effects = 0;
    data->effaxes = 0;
    memset(ffbits, 0, sizeof(ffbits));
    if (rw && ioctl(dev, EVIOCGBIT(EV_FF, sizeof(ffbits)), ffbits) >= 0) {
	for (u = 0; u <= FF_MAX; u++) {
	    if (test_bit(u, ffbits)) {
		TRACE("effaxis[%d] = %d\n", u, 1);
		data->effect_map[u] = 1;
		if (u < FF_RUMBLE)
		    data->effaxes++;
		else if (u < FF_GAIN)
		    data->effects++;
	    }
	}
    }

    TRACE("          effects=%d, effaxes=%d, gain=%d, center=%d\n",
	  data->effects, data->effaxes, data->effect_map[FF_GAIN],
	  data->effect_map[FF_AUTOCENTER]);

    if (data->effects > 0) {
	lpDICaps->dwFlags |= DIDC_FORCEFEEDBACK;
	memcpy(&lpDIInfo->guidFFDriver, &GUID_Joystick, sizeof(GUID));
    }
#endif

    data->fd = dev;
    data->init = 0;

    JSTCK_free_joy_map(jmap);

    return JOYERR_NOERROR;
}

HRESULT LINUXINPUT_GetDIInfo(WINE_JOYSTICK* jstck, DWORD dwVersion, LPDIDEVCAPS lpCaps, LPDIDEVICEINSTANCEA lpInfo)
{
    /* since, in the equation /dev/input/jsX = /dev/input/eventY,
     * it is not necessarily such that X = Y, we'll ignore the dicaps
     * from the js device and compute our own here */
    JOY_DATA data;
    JOY_DATA *pdata = &data;
    TRACE("(%p,%lx,%p,%p)\n", jstck, dwVersion, lpCaps, lpInfo);
    if (jstck->internal) pdata = jstck->internal;
    else {
	LONG err = LINUXINPUT_OpenDev(jstck, dwVersion, pdata);
	if (err == MMSYSERR_ALLOCATED) return DIERR_ACQUIRED;
	if (err != JOYERR_NOERROR) return DIERR_GENERIC;
	close(data.fd);
    }
    if (lpCaps) {
	DWORD dwSize = (lpCaps->dwSize > sizeof(pdata->dicaps)) ? sizeof(pdata->dicaps) : lpCaps->dwSize;
	memcpy(lpCaps, &pdata->dicaps, dwSize);
    }
    if (lpInfo) {
	DWORD dwSize = (lpInfo->dwSize > sizeof(pdata->diinfo)) ? sizeof(pdata->diinfo) : lpInfo->dwSize;
	memcpy(lpInfo, &pdata->diinfo, dwSize);
    }
    return DI_OK;
}

HRESULT LINUXINPUT_OpenDIDev(WINE_JOYSTICK* jstck, DWORD dwVersion)
{
    LONG err;
    JOY_DATA *pdata;
    JMAP *jmap, *cmap;
    LPDIOBJECTDATAFORMAT obj;
    unsigned u, o;
#ifdef HAVE_FORCE_FEEDBACK
    LPDIEFFECTINFOA eobj;
#endif

    TRACE("(%p,%lx)\n", jstck, dwVersion);
    err = LINUXINPUT_OpenDev(jstck, dwVersion, NULL);
    if (err == MMSYSERR_ALLOCATED) return DIERR_ACQUIRED;
    if (err != JOYERR_NOERROR) return DIERR_GENERIC;

    pdata = jstck->internal;

    /* get user-defined axis mapping */
    jmap = JSTCK_get_joy_map(pdata->diinfo.tszProductName);

    /* build data format */
    pdata->df.dwNumObjs = pdata->dicaps.dwAxes + pdata->dicaps.dwPOVs + pdata->dicaps.dwButtons;
    pdata->df.rgodf = HeapAlloc(GetProcessHeap(), 0, sizeof(DIOBJECTDATAFORMAT) * pdata->df.dwNumObjs);
    obj = pdata->df.rgodf;
    o = 0;
    /* add axes */
    cmap = jmap;
    for (u=0; u<=ABS_MAX; u++) if (pdata->abs_axis_map[u] != -1) {
	/* skip joy map hats since they don't apply here */
	while (cmap && (*cmap == JMAP_DISABLE || *cmap == JMAP_HAT || *cmap == JMAP_BALL)) cmap++;
	/* check for end of joy map */
	if (cmap && (*cmap == JMAP_END)) cmap = NULL;

	/* assign GUID to axis */
	if (cmap) obj->pguid = JSTCK_get_map_guid(*cmap++);
	else switch (u) {
	case ABS_X:
	case ABS_WHEEL:
	    obj->pguid = &GUID_XAxis;
	    break;
	case ABS_Y:
	case ABS_GAS:
	    obj->pguid = &GUID_YAxis;
	    break;
	case ABS_Z:
	case ABS_BRAKE:
	    obj->pguid = &GUID_ZAxis;
	    break;
	case ABS_RX:
	    obj->pguid = &GUID_RxAxis;
	    break;
	case ABS_RY:
	    obj->pguid = &GUID_RyAxis;
	    break;
	case ABS_RZ:
	case ABS_RUDDER:
	    obj->pguid = &GUID_RzAxis;
	    break;
	default:
	    obj->pguid = &GUID_Slider;
	    break;
	}
	obj->dwOfs = o;
	obj->dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE(pdata->abs_axis_map[u]);
	obj->dwFlags = DIDOI_ASPECTPOSITION;
#ifdef HAVE_FORCE_FEEDBACK
	if (pdata->effect_map[u] > 0)
	    obj->dwFlags |= DIDOI_FFACTUATOR;
#endif
	o += sizeof(LONG);
	obj++;
    }
    /* add hats */
    for (u=0; u<pdata->dicaps.dwPOVs; u++,obj++) {
	obj->pguid = &GUID_POV;
	obj->dwOfs = o;
	obj->dwType = DIDFT_POV | DIDFT_MAKEINSTANCE(u);
	obj->dwFlags = 0;
	o += sizeof(DWORD);
    }
    /* add buttons */
    for (u=0; u<pdata->dicaps.dwButtons; u++,obj++) {
	obj->pguid = &GUID_Button;
	obj->dwOfs = o;
	obj->dwType = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE(u);
	obj->dwFlags = 0;
	o += sizeof(BYTE);
    }
    pdata->df.dwDataSize = (o+3)&~3; /* dword-aligned data size */

#ifdef HAVE_FORCE_FEEDBACK
    /* add effects */
    pdata->dieffects = 0;
    eobj = pdata->dieffinfo;
    for (u = FF_RUMBLE; u < FF_GAIN; u++) {
	struct effect_entry *ep;
	if (!pdata->effect_map[u])
	    continue;
	ep = effect_by_ff(u);
	if (!ep)
	    continue;
	eobj->dwSize = sizeof(*eobj);
	eobj->guid = *ep->guid;
	eobj->dwEffType = ep->type;
	eobj->dwStaticParams = ep->parms;
	eobj->dwDynamicParams = ep->parms;
	if (dwVersion < 0x600) {
	    eobj->dwStaticParams &= ~DIEP_STARTDELAY;
	    eobj->dwDynamicParams &= ~DIEP_STARTDELAY;
	}
	strcpy(eobj->tszName, ep->name);
	pdata->dieffects++;
	eobj++;
    }
#endif

    JSTCK_free_joy_map(jmap);

    return DI_OK;
}

HRESULT LINUXINPUT_CloseDIDev(WINE_JOYSTICK* jstck)
{
    JOY_DATA *pdata = jstck->internal;
    TRACE("(%p)\n", jstck);
    if (!pdata) return DI_OK;
    close(pdata->fd);
    HeapFree(GetProcessHeap(), 0, pdata->df.rgodf);
    HeapFree(GetProcessHeap(), 0, pdata);
    jstck->internal = 0;
    return DI_OK;
}

HRESULT LINUXINPUT_GetDIDataFormat(WINE_JOYSTICK* jstck, LPDIDATAFORMAT* pdf)
{
    JOY_DATA *pdata = jstck->internal;
    TRACE("(%p,%p)\n", jstck, pdf);
    *pdf = &pdata->df;
    return DI_OK;
}

#define AXE_ABS		0
#define AXE_ABSMIN	1
#define AXE_ABSMAX	2
#define AXE_ABSFUZZ	3
#define AXE_ABSFLAT	4

/* expand axis values to the full range expected by dinput,
 * and let dinput deal with the rest, like dead zones */
static DWORD map_abs_axis(JOY_DATA *pdata, unsigned axis, int val)
{
    int xmin = pdata->abs_axis_info[axis][AXE_ABSMIN];
    int xmax = pdata->abs_axis_info[axis][AXE_ABSMAX];

    if (xmin == xmax) return val; /* avoid division by zero */

    /* clamp just in case */
    if (val < xmin) val = xmin;
    if (val > xmax) val = xmax;

    return (val-xmin)*65534/(xmax-xmin);
}

HRESULT LINUXINPUT_GetDIData(WINE_JOYSTICK* jstck, BOOL req, DWORD* dwObj, DWORD* dwData, DWORD* dwTimeStamp)
{
    JOY_DATA *pdata = jstck->internal;
    struct input_event ev;
    DWORD Time;
    unsigned u;
    char map;

    if (pdata->init < pdata->dicaps.dwAxes) {
	unsigned u, i;
	*dwObj = pdata->init;
	*dwTimeStamp = GetTickCount();
	for (u=0,i=0; u<=ABS_MAX; u++) if (pdata->abs_axis_map[u] != -1) {
	    if (i++ != pdata->init) continue;
	    *dwData = map_abs_axis(pdata, u, pdata->abs_axis_info[u][AXE_ABS]);
	    pdata->init++;
	    return DI_OK;
	}
	/* shouldn't get here */
	pdata->init = pdata->dicaps.dwAxes;
    }

    if (pdata->init < pdata->dicaps.dwAxes + pdata->dicaps.dwPOVs) {
	unsigned u, i;
	*dwObj = pdata->init;
	*dwTimeStamp = GetTickCount();
	for (u=0,i=pdata->dicaps.dwAxes; u<4; u++) {
	    if (i++ != pdata->init) continue;
	    *dwData = JSTCK_calc_povcts(pdata->abs_axis_info[pdata->hat_rmap[u]+0][AXE_ABS],
					pdata->abs_axis_info[pdata->hat_rmap[u]+1][AXE_ABS]);
	    pdata->init++;
	    return DI_OK;
	}
	/* shouldn't get here */
	pdata->init = pdata->dicaps.dwAxes + pdata->dicaps.dwPOVs;
    }

    while (TRUE) {
	if (read(pdata->fd, &ev, sizeof(struct input_event)) < 0)
	    return S_FALSE;

	/* FIXME: derive timestamp from ev.time */
	Time = GetTickCount();

	switch (ev.type) {
	case EV_KEY:
	    TRACE("key %d -> %d\n", ev.code, ev.value);
	    if (ev.code < FIRST_BTN) break;
	    map = pdata->button_map[ev.code-FIRST_BTN];
	    if (map == -1) break;
	    *dwObj = pdata->dicaps.dwAxes + pdata->dicaps.dwPOVs + map;
	    *dwData = ev.value ? 0x80 : 0x00;
	    *dwTimeStamp = Time;
	    return DI_OK;
	case EV_ABS:
	    TRACE("abs axis %d -> %d\n", ev.code, ev.value);
	    pdata->abs_axis_info[ev.code][AXE_ABS] = ev.value;
	    map = pdata->abs_axis_map[ev.code];
	    if (map != -1) {
		*dwObj = map;
		*dwData = map_abs_axis(pdata, ev.code, ev.value);
		*dwTimeStamp = Time;
		return DI_OK;
	    }
	    if (ev.code < ABS_HAT0X || ev.code > ABS_HAT3Y) break;
	    /* handle hat */
	    u = (ev.code - ABS_HAT0X) / 2;
	    map = pdata->hat_map[u];
	    if (map == -1) break;
	    *dwObj = pdata->dicaps.dwAxes + map;
	    *dwData = JSTCK_calc_povcts(pdata->abs_axis_info[ABS_HAT0X + u*2][AXE_ABS],
					pdata->abs_axis_info[ABS_HAT0Y + u*2][AXE_ABS]);
	    *dwTimeStamp = Time;
	    return DI_OK;
#ifdef HAVE_FORCE_FEEDBACK
	case EV_FF_STATUS:
	    TRACE("effect %d -> %d\n", ev.code, ev.value);
	    if (ev.code < MAX_RUNNING)
		pdata->effstatus[ev.code] = (ev.value > 0);
	    break;
#endif
	}
    }
}

#ifdef HAVE_FORCE_FEEDBACK
HRESULT LINUXINPUT_GetEffectInfo(WINE_JOYSTICK* jstck, DWORD *dwNumEffs, LPDIEFFECTINFOA *pEffInfo)
{
    JOY_DATA *pdata = jstck->internal;
    HRESULT res;

    TRACE("(%p,%p,%p)\n", jstck, dwNumEffs, pEffInfo);

    if (!pdata) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }

    *dwNumEffs = pdata->dieffects;
    *pEffInfo = pdata->dieffinfo;
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

HRESULT LINUXINPUT_GetEffectStatus(WINE_JOYSTICK* jstck, DWORD dwEffID, DWORD *pdwFlags)
{
    JOY_DATA *pdata = jstck->internal;
    HRESULT res;
    
    TRACE("(%p,%ld,%p)\n", jstck, dwEffID, pdwFlags);

    if (!pdata) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (dwEffID == -1) {
	res = DIERR_NOTDOWNLOADED;
	goto done;
    }
    if (dwEffID >= MAX_RUNNING) {
	res = DIERR_INVALIDPARAM;
	goto done;
    }

    *pdwFlags = pdata->effstatus[dwEffID] ? DIEGES_PLAYING : 0;
    res = DI_OK;

 done:
    TRACE("returning 0x%lx,%lx\n", res, *pdwFlags);
    return res;
}

HRESULT LINUXINPUT_SetAutoCenter(WINE_JOYSTICK* jstck, DWORD dwValue)
{
    JOY_DATA *pdata = jstck->internal;
    struct input_event ie;
    HRESULT res;

    TRACE("(%p,%ld)\n", jstck, dwValue);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (!pdata->effect_map[FF_AUTOCENTER]) {
	res = DI_OK;
	goto done;
    }

    ie.type = EV_FF;
    ie.code = FF_AUTOCENTER;
    /* FIXME: use 25% for lack of a better value */
    ie.value = (dwValue) ? 0x4000 : 0;
    if (write(pdata->fd, &ie, sizeof(ie)) < 0) {
	/* FIXME: return what? */
	res = DIERR_GENERIC;
	goto done;
    }
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

HRESULT LINUXINPUT_SetGain(WINE_JOYSTICK* jstck, DWORD dwValue)
{
    JOY_DATA *pdata = jstck->internal;
    struct input_event ie;
    HRESULT res;

    TRACE("(%p,%ld)\n", jstck, dwValue);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (!pdata->effect_map[FF_GAIN]) {
	res = DI_OK;
	goto done;
    }

    ie.type = EV_FF;
    ie.code = FF_GAIN;
    ie.value = 0xffff * dwValue / 10000;
    if (write(pdata->fd, &ie, sizeof(ie)) < 0) {
	/* FIXME: return what? */
	res = DIERR_GENERIC;
	goto done;
    }
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

HRESULT LINUXINPUT_StopEffect(WINE_JOYSTICK* jstck, DWORD dwEffID)
{
    JOY_DATA *pdata = jstck->internal;
    struct input_event ie;
    HRESULT res;

    TRACE("(%p,%ld)\n", jstck, dwEffID);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (dwEffID == -1) {
	res = DIERR_NOTDOWNLOADED;
	goto done;
    }
    if (dwEffID >= MAX_RUNNING) {
	res = DIERR_INVALIDPARAM;
	goto done;
    }

    ie.type = EV_FF;
    ie.code = dwEffID;
    ie.value = 0;
    if (write(pdata->fd, &ie, sizeof(ie)) < 0) {
	/* FIXME: return what? */
	res = DIERR_GENERIC;
	goto done;
    }
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

HRESULT LINUXINPUT_StartEffect(WINE_JOYSTICK* jstck, DWORD dwEffID, DWORD dwCount)
{
    JOY_DATA *pdata = jstck->internal;
    struct input_event ie;
    HRESULT res;

    TRACE("(%p,%ld,%ld)\n", jstck, dwEffID, dwCount);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (dwEffID == -1) {
	res = DIERR_NOTDOWNLOADED;
	goto done;
    }
    if (dwEffID >= MAX_RUNNING) {
	res = DIERR_INVALIDPARAM;
	goto done;
    }

    ie.type = EV_FF;
    ie.code = dwEffID;
    ie.value = dwCount;
    if (write(pdata->fd, &ie, sizeof(ie)) < 0) {
	/* FIXME: return what? */
	res = DIERR_GENERIC;
	goto done;
    }
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

HRESULT LINUXINPUT_UnloadEffect(WINE_JOYSTICK *jstck, DWORD dwEffID)
{
    JOY_DATA *pdata = jstck->internal;
    HRESULT res;

    TRACE("(%p,%ld)\n", jstck, dwEffID);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (dwEffID == -1) {
	res = DIERR_NOTDOWNLOADED;
	goto done;
    }
    if (dwEffID >= MAX_RUNNING) {
	res = DIERR_INVALIDPARAM;
	goto done;
    }

    if (ioctl(pdata->fd, EVIOCRMFF, dwEffID) < 0) {
	/* FIXME: return what? */
	res = DIERR_GENERIC;
	goto done;
    }
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}

/* us to ms (with rounding) */
#define convert_time(t) (((t) + 500) / 1000)
/* +/- 10000 to +/- 32767 */
#define convert_level(l) (32767 * (l) / 10000)
/* 0-3600 to 0-160 */
#define convert_phase(p) (160 * (p) / 36000)

static int convert_dir(DWORD dwFlags, DIEFFECT *pEff)
{
    int dir;

    if (pEff->cAxes < 2)
	dir = 49152;		/* FIXME: always right? */
    else if (pEff->dwFlags & DIEFF_POLAR)
	dir = 65536 * pEff->rglDirection[0] / 36000;
    else {
	double d = atan2(pEff->rglDirection[1], pEff->rglDirection[0]);
	dir = (32768 * d / M_PI) - 16384;
    }

    return dir;
}

static void convert_envelope(struct ff_envelope *p, DWORD dwFlags, DIEFFECT *pEff)
{
    if (dwFlags & DIEP_ENVELOPE) {
	p->attack_length = convert_time(pEff->lpEnvelope->dwAttackTime);
	p->attack_level = convert_level(pEff->lpEnvelope->dwAttackLevel);
	p->fade_length = convert_time(pEff->lpEnvelope->dwFadeTime);
	p->fade_level = convert_level(pEff->lpEnvelope->dwFadeLevel);
    } else {
	p->attack_length = 0;
	p->attack_level = 0;
	p->fade_length = 0;
	p->fade_level = 0;
    }
}

HRESULT LINUXINPUT_LoadEffect(WINE_JOYSTICK *jstck, DWORD *pdwEffID, DWORD dwFlags, DIEFFECT *pEff, DIEFFECTINFOA *pEffInfo)
{
    JOY_DATA *pdata = jstck->internal;
    struct effect_entry *ep;
    struct ff_effect ffe;
    DICONSTANTFORCE *cp;
    DIRAMPFORCE *rp;
    DIPERIODIC *pp;
    DICONDITION *np;
    HRESULT res;

    TRACE("(%p,%ld,%lx,%p,%p)\n", jstck, *pdwEffID, dwFlags, pEff, pEffInfo);

    if (!pdata || pdata->fd < 0) {
	res = DIERR_NOTACQUIRED;
	goto done;
    }
    if (*pdwEffID != -1 && *pdwEffID >= MAX_RUNNING) {
	res = DIERR_INVALIDPARAM;
	goto done;
    }
    ep = effect_by_guid(&pEffInfo->guid);
    if (!ep || !pdata->effect_map[ep->ff]) {
	res = DIERR_UNSUPPORTED;
	goto done;
    }

    if (!(dwFlags & DIEP_DURATION) ||
	!(dwFlags & DIEP_TYPESPECIFICPARAMS) ||
	((dwFlags & DIEP_AXES) && !(dwFlags & DIEP_DIRECTION)) ||
	((dwFlags & DIEP_DIRECTION) && !(dwFlags & DIEP_AXES))) {
	res = DIERR_INCOMPLETEEFFECT;
	goto done;
    }

    ffe.id = *pdwEffID;
    ffe.replay.length = (pEff->dwDuration < 0) ? 0 :
	((pEff->dwDuration < 1000) ? 1 : convert_time(pEff->dwDuration));
    ffe.replay.delay = (dwFlags & DIEP_STARTDELAY) ?
	convert_time(pEff->dwStartDelay) : 0;
    ffe.trigger.button = ((dwFlags & DIEP_TRIGGERBUTTON) && pEff->dwTriggerButton != -1) ?
	pdata->button_rmap[DIDFT_GETINSTANCE(pEff->dwTriggerButton)] : 0;
    ffe.trigger.interval = (dwFlags & DIEP_TRIGGERREPEATINTERVAL) ?
	convert_time(pEff->dwTriggerRepeatInterval) : 0;
    ffe.direction = convert_dir(dwFlags, pEff);

    switch (ep->ff) {
    case FF_CONSTANT:
	ffe.type = ep->ff;
	cp = pEff->lpvTypeSpecificParams;
	ffe.u.constant.level = convert_level(cp->lMagnitude);
	convert_envelope(&ffe.u.constant.envelope, dwFlags, pEff);
	break;

    case FF_RAMP:
	ffe.type = ep->ff;
	rp = pEff->lpvTypeSpecificParams;
	ffe.u.ramp.start_level = convert_level(rp->lStart);
	ffe.u.ramp.end_level = convert_level(rp->lEnd);
	convert_envelope(&ffe.u.ramp.envelope, dwFlags, pEff);
	break;

    case FF_SQUARE:
    case FF_SINE:
    case FF_TRIANGLE:
    case FF_SAW_UP:
    case FF_SAW_DOWN:
	ffe.type = FF_PERIODIC;
	pp = pEff->lpvTypeSpecificParams;
	ffe.u.periodic.waveform = ep->ff;
	ffe.u.periodic.period = convert_time(pp->dwPeriod);
	ffe.u.periodic.magnitude = convert_level(pp->dwMagnitude);
	ffe.u.periodic.offset = convert_level(pp->lOffset);
	ffe.u.periodic.phase = convert_phase(pp->dwPhase);
	break;

    case FF_SPRING:
    case FF_DAMPER:
    case FF_INERTIA:
    case FF_FRICTION:
	ffe.type = ep->ff;
	np = pEff->lpvTypeSpecificParams;
	ffe.u.condition[0].right_saturation = convert_level(np->dwPositiveSaturation);
	ffe.u.condition[0].left_saturation = convert_level(np->dwNegativeSaturation);
	ffe.u.condition[0].right_coeff = convert_level(np->lPositiveCoefficient);
	ffe.u.condition[0].left_coeff = convert_level(np->lNegativeCoefficient);
	ffe.u.condition[0].deadband = convert_level(np->lDeadBand);
	ffe.u.condition[0].center = convert_level(np->lOffset);
	ffe.u.condition[1] = ffe.u.condition[0];
	break;

    default:
	FIXME("Unsupported effect type %d\n", ep->ff);
	res = DIERR_UNSUPPORTED;
	goto done;
    }

    while (ioctl(pdata->fd, EVIOCSFF, &ffe) < 0) {
	if (errno == EAGAIN) {
	    /*break; */
	    continue;
	} else if (errno == ENOMEM)
	    res = DIERR_DEVICEFULL;
	else
	    /* FIXME: return what? */
	    res = DIERR_GENERIC;
	goto done;
    }
    *pdwEffID = ffe.id;
    res = DI_OK;

 done:
    TRACE("returning 0x%lx\n", res);
    return res;
}
#endif /* HAVE_FORCE_FEEDBACK */

WINE_JOYSTICK_DRV jdrv_linuxinput = {
    LINUXINPUT_DevName, LINUXINPUT_GetDevCaps, LINUX22_GetPosEx,
    LINUXINPUT_GetDIInfo, LINUXINPUT_OpenDIDev, LINUXINPUT_CloseDIDev,
    LINUXINPUT_GetDIDataFormat, LINUXINPUT_GetDIData,
#ifdef HAVE_FORCE_FEEDBACK
    LINUXINPUT_GetEffectInfo,
    LINUXINPUT_GetEffectStatus,
    LINUXINPUT_SetAutoCenter,
    LINUXINPUT_SetGain,
    LINUXINPUT_UnloadEffect,
    LINUXINPUT_LoadEffect,
    LINUXINPUT_StopEffect,
    LINUXINPUT_StartEffect
#else
    NULL, NULL, NULL, NULL,
    NULL, NULL, NULL, NULL
#endif
};

#endif
