/*
 * Linux 2.0 joystick interface
 *
 * Copyright 1997 Andreas Mohr
 * Copyright 2002 TransGaming Technologies Inc.
 *
 * The joystick-0.8.0 kernel module seems to
 *  - return ENODEV on open() if the joystick is not plugged in
 *  - return ENODEV on read() if the joystick gets unplugged
 *  - return EOVERFLOW on read() if the data count is not sizeof(js_status)
 */

#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_JOYSTICK_H
#include <linux/joystick.h>
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif

#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);

#ifdef HAVE_LINUX_JOYSTICK_H

struct js_status {
    int buttons;
    int x;
    int y;
};

typedef struct {
  DWORD dwX;
  DWORD dwY;
  BYTE rgbButtons[4];
  struct js_status js;
  DWORD dwTimeStamp;
  int fd;
} JOY_DATA;

#define LINUX20_JOYDATA_X_AXIS_INSTANCE 0x0000
#define LINUX20_JOYDATA_Y_AXIS_INSTANCE 0x0001
#define LINUX20_JOYDATA_BUTTON_0_INSTANCE 0x0000
#define LINUX20_JOYDATA_BUTTON_1_INSTANCE 0x0001
#define LINUX20_JOYDATA_BUTTON_2_INSTANCE 0x0002
#define LINUX20_JOYDATA_BUTTON_3_INSTANCE 0x0003

#define LINUX20_JOYDATA_NUM_OBJS 6

static DIOBJECTDATAFORMAT LINUX20_JoyDataObjFormat[LINUX20_JOYDATA_NUM_OBJS] = {
  { &GUID_XAxis,   FIELD_OFFSET(JOY_DATA, dwX),
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_X_AXIS_INSTANCE) | DIDFT_ABSAXIS, DIDOI_ASPECTPOSITION },
  { &GUID_YAxis,   FIELD_OFFSET(JOY_DATA, dwY),
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_Y_AXIS_INSTANCE) | DIDFT_ABSAXIS, DIDOI_ASPECTPOSITION },
  { &GUID_Button, (FIELD_OFFSET(JOY_DATA, rgbButtons)) + 0,
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_BUTTON_0_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(JOY_DATA, rgbButtons)) + 1,
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_BUTTON_1_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(JOY_DATA, rgbButtons)) + 2,
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_BUTTON_2_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(JOY_DATA, rgbButtons)) + 3,
      DIDFT_MAKEINSTANCE(LINUX20_JOYDATA_BUTTON_3_INSTANCE) | DIDFT_PSHBUTTON, 0 }
};

static DIDATAFORMAT LINUX20_JoyDataFormat = {
  0, /* dwSize - unused */
  0, /* dwObjsize - unused */
  0, /* dwFlags - unused */
  FIELD_OFFSET(JOY_DATA, js),
  LINUX20_JOYDATA_NUM_OBJS, /* dwNumObjs */
  LINUX20_JoyDataObjFormat
};

void LINUX20_DevName(WINE_JOYSTICK* jstck)
{
    jstck->devIntf = jstck->joyIntf;
    sprintf(jstck->devname, "/dev/js%d", jstck->devIntf);
}

LONG LINUX20_GetDevCaps(WINE_JOYSTICK* jstck)
{
    LPJOYCAPSA lpCaps = &jstck->caps;
    LPDIDEVCAPS lpDICaps = &jstck->dicaps;
    LPDIDEVICEINSTANCEA lpDIInfo = &jstck->diinfo;

    lpCaps->wMid = MM_MICROSOFT;
    lpCaps->wPid = MM_PC_JOYSTICK;
    strcpy(lpCaps->szPname, "WineJoy"); /* joystick product name */
    lpCaps->wXmin = 0;
    lpCaps->wXmax = 0xFFFF;
    lpCaps->wYmin = 0;
    lpCaps->wYmax = 0xFFFF;
    lpCaps->wZmin = 0;
    lpCaps->wZmax = 0;
    lpCaps->wNumButtons = 2;
    lpCaps->wRmin = 0;
    lpCaps->wRmax = 0;
    lpCaps->wUmin = 0;
    lpCaps->wUmax = 0;
    lpCaps->wVmin = 0;
    lpCaps->wVmax = 0;
    lpCaps->wCaps = 0;
    lpCaps->wMaxAxes = 2;
    lpCaps->wNumAxes = 2;
    lpCaps->wMaxButtons = 4;
    strcpy(lpCaps->szRegKey,"");
    strcpy(lpCaps->szOEMVxD,"");

    memset(lpDICaps, 0, sizeof(DIDEVCAPS));
    lpDICaps->dwSize = sizeof(DIDEVCAPS);
    lpDICaps->dwFlags = DIDC_ATTACHED | DIDC_POLLEDDEVICE;
    lpDICaps->dwDevType = DIDEVTYPE_JOYSTICK;
    lpDICaps->dwAxes = 2;
    lpDICaps->dwButtons = 4;
    lpDICaps->dwPOVs = 0;

    memset(lpDIInfo, 0, sizeof(DIDEVICEINSTANCEA));
    /* the guidInstance is supposed to identify the joystick,
     * this is enough for that purpose */
    lpDIInfo->guidInstance.Data1 = jstck->joyIntf;
    memcpy(&lpDIInfo->guidProduct, &GUID_Joystick, sizeof(GUID));
    lpDIInfo->dwDevType = DIDEVTYPE_JOYSTICK;
    sprintf(lpDIInfo->tszInstanceName, "Joystick %d", jstck->joyIntf);
    strcpy(lpDIInfo->tszProductName, "WineJoy");

    return JOYERR_NOERROR;
}

LONG LINUX20_GetPosEx(WINE_JOYSTICK* jstck, LPJOYINFOEX lpInfo)
{
    int			dev;
    struct js_status 	js;
    int    		dev_stat;

    if ((dev = open(jstck->devname, O_RDONLY)) < 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;
    }

    dev_stat = read(dev, &js, sizeof(js));
    if (dev_stat != sizeof(js)) {
	close(dev);
	if ((dev_stat < 0) && (errno == ENODEV)) return JOYERR_UNPLUGGED;
	else return MMSYSERR_NODRIVER; /* this is bad stuff */
    }

    js.x = js.x<<8;
    js.y = js.y<<8;
    if (lpInfo->dwFlags & JOY_RETURNX)
	lpInfo->dwXpos = js.x;   /* FIXME: perhaps multiply it somehow ? */
    if (lpInfo->dwFlags & JOY_RETURNY)
	lpInfo->dwYpos = js.y;
    if (lpInfo->dwFlags & JOY_RETURNBUTTONS)
	lpInfo->dwButtons = js.buttons;

    close(dev);

    TRACE("x: %ld, y: %ld, z: %ld, r: %ld, u: %ld, v: %ld, buttons: 0x%04x, flags: 0x%04x\n",
	  lpInfo->dwXpos, lpInfo->dwYpos, lpInfo->dwZpos,
	  lpInfo->dwRpos, lpInfo->dwUpos, lpInfo->dwVpos,
	  (unsigned int)lpInfo->dwButtons,
	  (unsigned int)lpInfo->dwFlags);

    return JOYERR_NOERROR;
}

HRESULT LINUX20_OpenDIDev(WINE_JOYSTICK* jstck, DWORD dwVersion)
{
    int dev;
    JOY_DATA *pdata;
#if 0
    int dev_stat;
#endif

    TRACE("(%p,%lx)\n", jstck, dwVersion);
    if ((dev = open(jstck->devname, O_RDONLY)) < 0) {
	if (errno == ENODEV) return DIERR_UNPLUGGED;
	else if (errno == ENOENT) return DIERR_DEVICENOTREG;
	else if (errno == EBUSY) return DIERR_ACQUIRED;
	else return DIERR_GENERIC;
    }
    pdata = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(JOY_DATA));

#if 0
    /* read initial state */
    dev_stat = read(dev, &pdata->js, sizeof(pdata->js));
    if (dev_stat != sizeof(pdata->js)) {
	close(dev);
	HeapFree(GetProcessHeap(), 0, pdata);
	if ((dev_stat < 0) && (errno == ENODEV)) return DIERR_UNPLUGGED;
	else return DIERR_GENERIC;
    }
#endif

    pdata->fd = dev;
    jstck->internal = pdata;
    return DI_OK;
}

HRESULT LINUX20_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);
    jstck->internal = 0;
    return DI_OK;
}

HRESULT LINUX20_GetDIDataFormat(WINE_JOYSTICK* jstck, LPDIDATAFORMAT* pdf)
{
    TRACE("(%p,%p)\n", jstck, pdf);
    *pdf = &LINUX20_JoyDataFormat;
    return DI_OK;
}

HRESULT LINUX20_GetDIData(WINE_JOYSTICK* jstck, BOOL req, DWORD* dwObj, DWORD* dwData, DWORD* dwTimeStamp)
{
    JOY_DATA *pdata = jstck->internal;
    unsigned u;
    BYTE b;
    if (req) {
	int dev_stat;
	TRACE("(%p)\n", jstck);
	dev_stat = read(pdata->fd, &pdata->js, sizeof(pdata->js));
	if (dev_stat != sizeof(pdata->js)) {
	    if ((dev_stat < 0) && (errno == ENODEV)) return DIERR_UNPLUGGED;
	    else return DIERR_GENERIC;
	}
	pdata->dwTimeStamp = GetTickCount(); /* or timeGetTime() ? */
    }
    if (pdata->js.x != pdata->dwX) {
	pdata->dwX = pdata->js.x;
	*dwObj = 0;
	*dwData = pdata->dwX;
	*dwTimeStamp = pdata->dwTimeStamp;
	return DI_OK;
    }
    if (pdata->js.y != pdata->dwY) {
	pdata->dwY = pdata->js.y;
	*dwObj = 1;
	*dwData = pdata->dwY;
	*dwTimeStamp = pdata->dwTimeStamp;
	return DI_OK;
    }
    for (u=0; u<4; u++) {
	b = (pdata->js.buttons & (1 << u)) ? 0x80 : 0;
	if (b != pdata->rgbButtons[u]) {
	    pdata->rgbButtons[u] = b;
	    *dwObj = 2 + u;
	    *dwData = pdata->rgbButtons[u];
	    *dwTimeStamp = pdata->dwTimeStamp;
	    return DI_OK;
	}
    }
    return S_FALSE;
}

WINE_JOYSTICK_DRV jdrv_linux20 = {
    LINUX20_DevName, LINUX20_GetDevCaps, LINUX20_GetPosEx,
    NULL, LINUX20_OpenDIDev, LINUX20_CloseDIDev,
    LINUX20_GetDIDataFormat, LINUX20_GetDIData
};

#endif
