/* -*- tab-width: 8; c-basic-offset: 4 -*- */
/*
 * Joystick interface
 *
 * Copyright 1997 Andreas Mohr
 * Copyright 2000 Wolfgang Schwotzer
 * 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_JOYSTICK_H
#include <linux/joystick.h>
#define JOYDEV "/dev/js%d"
#endif
#ifdef HAVE_SYS_ERRNO_H
#include <sys/errno.h>
#endif
#include <math.h> /* atan2 */

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

#include "joy_drv.h"

WINE_DEFAULT_DEBUG_CHANNEL(joystick);

#define MAXJOYSTICK	4

static	WINE_JOYSTICK	JSTCK_Data[MAXJOYSTICK];

extern WINE_JOYSTICK_DRV jdrv_linux20;
extern WINE_JOYSTICK_DRV jdrv_linux22;
extern WINE_JOYSTICK_DRV jdrv_linuxinput22;
extern WINE_JOYSTICK_DRV jdrv_linuxinput;

/**************************************************************************
 * 				JSTCK_drvDetect			[internal]
 */
static	BOOL	JSTCK_drvDetect(DWORD dwIntf)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];
    JOYINFOEX	ji;
    WINE_JOYSTICK_DRV *drv = NULL;
    LONG err;
    unsigned u;

    if (jstck->drv) return TRUE;

    memset(&ji, 0, sizeof(ji));
    ji.dwSize = sizeof(ji);
    ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNBUTTONS;

#ifdef HAVE_CORRECT_LINUXINPUT_H
    jdrv_linuxinput.DevName(jstck);
    err = jdrv_linuxinput.GetDevCaps(jstck);
    if (err == MMSYSERR_ALLOCATED) return FALSE;
#if defined(HAVE_LINUX_JOYSTICK_H) && defined(HAVE_LINUX_22_JOYSTICK_API)
    if (err == JOYERR_UNPLUGGED || err == MMSYSERR_NODRIVER) {
	/* the evdev module might not be loaded */
	err = jdrv_linuxinput22.GetDevCaps(jstck);
	if (err == MMSYSERR_ALLOCATED || err == JOYERR_UNPLUGGED) return FALSE;
	if (err == JOYERR_NOERROR) {
	    jstck->detected = 1;
	    drv = &jdrv_linuxinput22;
	    goto finish;
	}
    }
#else
    if (err == JOYERR_UNPLUGGED) return FALSE;
#endif
    if (err == JOYERR_NOERROR) {
	jstck->detected = 1;
	drv = &jdrv_linuxinput;
	goto finish;
    }
#endif
#ifdef HAVE_LINUX_JOYSTICK_H
#ifdef HAVE_LINUX_22_JOYSTICK_API
    jdrv_linux22.DevName(jstck);
    err = jdrv_linux22.GetDevCaps(jstck);
    if (err == MMSYSERR_ALLOCATED || err == JOYERR_UNPLUGGED) return FALSE;
    if (err == JOYERR_NOERROR) {
	jstck->detected = 1;
	drv = &jdrv_linux22;
	goto finish;
    }
#endif
    jdrv_linux20.DevName(jstck);
    err = jdrv_linux20.GetPosEx(jstck, &ji);
    if (err == MMSYSERR_ALLOCATED || err == JOYERR_UNPLUGGED) return FALSE;
    if (err == JOYERR_NOERROR) {
	drv = &jdrv_linux20;
	goto finish;
    }
#endif

    return FALSE;

finish:
    /* make all joysticks use the same driver, at least for now */
    for (u=0; u<MAXJOYSTICK; u++)
	JSTCK_Data[u].drv = drv;

    return TRUE;
}

/**************************************************************************
 * 				JSTCK_drvGet			[internal]
 */
static	WINE_JOYSTICK*	JSTCK_drvGet(DWORD dwDevID)
{
    int	p;

    if ((dwDevID - (DWORD)JSTCK_Data) % sizeof(JSTCK_Data[0]) != 0)
	return NULL;
    p = (dwDevID - (DWORD)JSTCK_Data) / sizeof(JSTCK_Data[0]);
    if (p < 0 || p >= MAXJOYSTICK || !((WINE_JOYSTICK*)dwDevID)->in_use)
	return NULL;

    return (WINE_JOYSTICK*)dwDevID;
}

/**************************************************************************
 * 				JSTCK_drvOpen			[internal]
 */
static	DWORD	JSTCK_drvOpen(LPSTR str, DWORD dwIntf)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (dwIntf >= MAXJOYSTICK || jstck->in_use)
	return 0;

    jstck->joyIntf = dwIntf;

    if (!JSTCK_drvDetect(dwIntf))
	return 0;

    if (!jstck->detected) {
	LONG err;
	jstck->drv->DevName(jstck);
	err = jstck->drv->GetDevCaps(jstck);
	if (err == JOYERR_NOERROR) jstck->detected = 1;
	else return 0;
    }

    jstck->in_use = 1;
    return (DWORD)jstck;
}

/**************************************************************************
 * 				JSTCK_drvClose			[internal]
 */
static	DWORD	JSTCK_drvClose(DWORD dwDevID)
{
    WINE_JOYSTICK*	jstck = JSTCK_drvGet(dwDevID);

    if (jstck == NULL)
	return 0;
    jstck->in_use = 0;
    return 1;
}

/**************************************************************************
 * 				JoyGetDevCaps		[MMSYSTEM.102]
 */
static	LONG	JSTCK_GetDevCaps(DWORD dwDevID, LPJOYCAPSA lpCaps, DWORD dwSize)
{
    WINE_JOYSTICK*	jstck;

    if ((jstck = JSTCK_drvGet(dwDevID)) == NULL)
	return MMSYSERR_NODRIVER;

    if (!jstck->drv || !jstck->detected)
	return JOYERR_UNPLUGGED; /* not quite right */

    if (dwSize > sizeof(jstck->caps)) dwSize = sizeof(jstck->caps);
    memcpy(lpCaps, &jstck->caps, dwSize);
    return JOYERR_NOERROR;
}

/**************************************************************************
 * 				JSTCK_GetPos			[internal]
 */
static LONG	JSTCK_GetPosEx(DWORD dwDevID, LPJOYINFOEX lpInfo)
{
    WINE_JOYSTICK*		jstck;

    if ((jstck = JSTCK_drvGet(dwDevID)) == NULL)
	return MMSYSERR_NODRIVER;

    if (!jstck->drv || !jstck->detected)
	return JOYERR_UNPLUGGED;

    return jstck->drv->GetPosEx(jstck, lpInfo);
}

/**************************************************************************
 * 				JSTCK_GetPos			[internal]
 */
static LONG	JSTCK_GetPos(DWORD dwDevID, LPJOYINFO lpInfo)
{
    JOYINFOEX	ji;
    LONG	ret;

    memset(&ji, 0, sizeof(ji));

    ji.dwSize = sizeof(ji);
    ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNBUTTONS;
    ret = JSTCK_GetPosEx(dwDevID, &ji);
    if (ret == JOYERR_NOERROR)	{
	lpInfo->wXpos    = ji.dwXpos;
	lpInfo->wYpos    = ji.dwYpos;
	lpInfo->wZpos    = ji.dwZpos;
	lpInfo->wButtons = ji.dwButtons;
    }

    return ret;
}

/**************************************************************************
 * 				DriverProc (JOYSTICK.@)
 */
LONG CALLBACK	JSTCK_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg,
				 DWORD dwParam1, DWORD dwParam2)
{
    /* EPP     TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n",  */
    /* EPP 	  dwDevID, hDriv, wMsg, dwParam1, dwParam2); */

    switch(wMsg) {
    case DRV_LOAD:		return 1;
    case DRV_FREE:		return 1;
    case DRV_OPEN:		return JSTCK_drvOpen((LPSTR)dwParam1, dwParam2);
    case DRV_CLOSE:		return JSTCK_drvClose(dwDevID);
    case DRV_ENABLE:		return 1;
    case DRV_DISABLE:		return 1;
    case DRV_QUERYCONFIGURE:	return 1;
    case DRV_CONFIGURE:		MessageBoxA(0, "JoyStick MultiMedia Driver !", "JoyStick Driver", MB_OK);	return 1;
    case DRV_INSTALL:		return DRVCNF_RESTART;
    case DRV_REMOVE:		return DRVCNF_RESTART;

    case JDD_GETNUMDEVS:	return 1;
    case JDD_GETDEVCAPS:	return JSTCK_GetDevCaps(dwDevID, (LPJOYCAPSA)dwParam1, dwParam2);
    case JDD_GETPOS:		return JSTCK_GetPos(dwDevID, (LPJOYINFO)dwParam1);
    case JDD_SETCALIBRATION:
    case JDD_CONFIGCHANGED:	return JOYERR_NOCANDO;
    case JDD_GETPOSEX:		return JSTCK_GetPosEx(dwDevID, (LPJOYINFOEX)dwParam1);
    default:
	return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
    }
}

/***********************************************************************
 *		get_config_key
 *
 * Get a config key from either the app-specific or the default config
 */
inline static DWORD get_config_key( HKEY defkey, HKEY appkey, const char *name,
                                    char *buffer, DWORD size )
{
    if (appkey && !RegQueryValueExA( appkey, name, 0, NULL, buffer, &size )) return 0;
    return RegQueryValueExA( defkey, name, 0, NULL, buffer, &size );
}

static JMAP get_map_val(char *p)
{
    if (!p[0]) return JMAP_DISABLE;
    if (!strcasecmp(p, "none")) return JMAP_DISABLE;
    if (!strcasecmp(p, "X")) return JMAP_X;
    if (!strcasecmp(p, "Y")) return JMAP_Y;
    if (!strcasecmp(p, "Z")) return JMAP_Z;
    if (!strcasecmp(p, "RX")) return JMAP_RX;
    if (!strcasecmp(p, "RY")) return JMAP_RY;
    if (!strcasecmp(p, "RZ")) return JMAP_RZ;
    if (!strcasecmp(p, "slider")) return JMAP_SLIDER;
    if (!strcasecmp(p, "hat")) return JMAP_HAT;
    if (!strcasecmp(p, "POV")) return JMAP_HAT;
    if (!strcasecmp(p, "ball")) return JMAP_BALL;
    WARN("Unknown joystick axis type: '%s'.\n", p);
    return JMAP_DISABLE;
}

/***********************************************************************
 *		JSTCK_get_joy_map
 */
JMAP* JSTCK_get_joy_map(char *name)
{
    char buffer[MAX_PATH+16];
    HKEY hkey, appkey = 0;
    JMAP* map = NULL;

    if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\joystick", 0, NULL,
                         REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL ))
    {
        ERR("Cannot create config registry key\n" );
        ExitProcess(1);
    }

    /* open the app-specific key */

    if (GetModuleFileName16( GetCurrentTask(), buffer, MAX_PATH ) ||
        GetModuleFileNameA( 0, buffer, MAX_PATH ))
    {
        HKEY tmpkey;
        char *p, *appname = buffer;
        if ((p = strrchr( appname, '/' ))) appname = p + 1;
        if ((p = strrchr( appname, '\\' ))) appname = p + 1;
        strcat( appname, "\\joystick" );
        if (!RegOpenKeyA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\AppDefaults", &tmpkey ))
        {
            if (RegOpenKeyA( tmpkey, appname, &appkey )) appkey = 0;
            RegCloseKey( tmpkey );
        }
    }

    if (!get_config_key( hkey, appkey, name, buffer, sizeof(buffer) ))
    {
        unsigned c, i;
        char *p;
        if (buffer[0]) {
            for (c=1,i=0; buffer[i]; i++)
                if (buffer[i] == ',') c++;
            map = HeapAlloc(GetProcessHeap(), 0, sizeof(JMAP)*(c+1));
            for (c=0,i=0,p=buffer; buffer[i]; i++)
                if (buffer[i] == ',') {
                    buffer[i] = 0;
                    map[c++] = get_map_val(p);
                    p = &buffer[i+1];
                }
            map[c++] = get_map_val(p);
            map[c] = JMAP_END;
        } else {
            map = HeapAlloc(GetProcessHeap(), 0, sizeof(JMAP));
            map[0] = JMAP_END;
        }
    }

    if (appkey) RegCloseKey( appkey );
    RegCloseKey( hkey );

    return map;
}

REFGUID JSTCK_get_map_guid(JMAP map)
{
    switch (map) {
    case JMAP_END: return NULL;
    case JMAP_DISABLE: return &GUID_NULL;
    case JMAP_X: return &GUID_XAxis;
    case JMAP_Y: return &GUID_YAxis;
    case JMAP_Z: return &GUID_ZAxis;
    case JMAP_RX: return &GUID_RxAxis;
    case JMAP_RY: return &GUID_RyAxis;
    case JMAP_RZ: return &GUID_RzAxis;
    case JMAP_SLIDER: return &GUID_Slider;
    case JMAP_HAT: return &GUID_POV;
    default: return &GUID_Unknown;
    }
}

/**************************************************************************
 * 	Point-Of-View Hat calculation
 */
DWORD JSTCK_calc_pov4(int x, int y)
{
    if (y < 0) return JOY_POVFORWARD;
    if (y > 0) return JOY_POVBACKWARD;
    if (x < 0) return JOY_POVLEFT;
    if (x > 0) return JOY_POVRIGHT;
    return JOY_POVCENTERED;
}

DWORD JSTCK_calc_povcts(int x, int y)
{
    if (x == 0 && y == 0) return JOY_POVCENTERED;
    /* is atan2 overkill here? */
    return 18000 - atan2(x, y)*18000/M_PI;
}

/**************************************************************************
 * 				dijoy_GetDevInfo (JOYSTICK.@)
 */
static DWORD DX8Type[] = {
   DI8DEVTYPE_JOYSTICK,
   DI8DEVTYPE_JOYSTICK     | (DI8DEVTYPEJOYSTICK_STANDARD         << 8), /* UNKNOWN */
   DI8DEVTYPE_JOYSTICK     | (DI8DEVTYPEJOYSTICK_STANDARD         << 8), /* TRADITIONAL */
   DI8DEVTYPE_FLIGHT       | (DI8DEVTYPEFLIGHT_STICK              << 8), /* FLIGHTSTICK */
   DI8DEVTYPE_GAMEPAD      | (DI8DEVTYPEGAMEPAD_STANDARD          << 8), /* GAMEPAD */
   DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_RUDDERPEDALS << 8), /* RUDDER */
/*FIXME*/ DI8DEVTYPE_DRIVING      | (DI8DEVTYPEDRIVING_LIMITED           << 8), /* WHEEL */
   DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_HEADTRACKER  << 8), /* HEADTRACKER */
};

HRESULT WINAPI DIJOY_GetGuid(DWORD dwIntf, LPGUID lpGuid)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (dwIntf >= MAXJOYSTICK)
	return DIERR_DEVICENOTREG;

    /* initialize if necessary */
    JSTCK_drvClose(JSTCK_drvOpen(NULL, dwIntf));

    memcpy(lpGuid, &jstck->diinfo.guidInstance, sizeof(GUID));
    return DI_OK;
}

HRESULT WINAPI DIJOY_GetDevInfo(DWORD dwIntf, DWORD dwVersion, LPDIDEVCAPS lpCaps, LPDIDEVICEINSTANCEA lpInfo)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (dwIntf >= MAXJOYSTICK)
	return DIERR_DEVICENOTREG;

    /* initialize if necessary */
    JSTCK_drvClose(JSTCK_drvOpen(NULL, dwIntf));

    if (jstck->drv->GetDIInfo)
	return jstck->drv->GetDIInfo(jstck, dwVersion, lpCaps, lpInfo);

    if (lpCaps) {
	DWORD dwSize = (lpCaps->dwSize > sizeof(jstck->dicaps)) ? sizeof(jstck->dicaps) : lpCaps->dwSize;
	memcpy(lpCaps, &jstck->dicaps, dwSize);
	if (HIBYTE(dwVersion) >= 8)
	    lpCaps->dwDevType = DX8Type[GET_DIDEVICE_SUBTYPE(lpCaps->dwDevType)];
    }
    if (lpInfo) {
	DWORD dwSize = (lpInfo->dwSize > sizeof(jstck->diinfo)) ? sizeof(jstck->diinfo) : lpInfo->dwSize;
	memcpy(lpInfo, &jstck->diinfo, dwSize);
	if (HIBYTE(dwVersion) >= 8)
	    lpInfo->dwDevType = DX8Type[GET_DIDEVICE_SUBTYPE(lpInfo->dwDevType)];
    }
    return DI_OK;
}

HRESULT WINAPI DIJOY_OpenDIDev(DWORD dwIntf, DWORD dwVersion)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (dwIntf >= MAXJOYSTICK)
	return 0;

    /* initialize if necessary */
    JSTCK_drvClose(JSTCK_drvOpen(NULL, dwIntf));

    if (jstck->drv->OpenDIDev)
	return jstck->drv->OpenDIDev(jstck, dwVersion);

    return DIERR_DEVICENOTREG;
}

HRESULT WINAPI DIJOY_CloseDIDev(DWORD dwIntf)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->CloseDIDev)
	return jstck->drv->CloseDIDev(jstck);

    return DI_OK;
}

HRESULT WINAPI DIJOY_GetDIDataFormat(DWORD dwIntf, LPDIDATAFORMAT* pdf)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->GetDIDataFormat)
	return jstck->drv->GetDIDataFormat(jstck, pdf);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_GetDIData(DWORD dwIntf, BOOL req, DWORD* dwObj, DWORD* dwData, DWORD* dwTimeStamp)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->GetDIData)
	return jstck->drv->GetDIData(jstck, req, dwObj, dwData, dwTimeStamp);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_GetEffectInfo(DWORD dwIntf, DWORD *dwNumEffs, LPDIEFFECTINFOA *pEffInfo)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->GetEffectInfo)
	return jstck->drv->GetEffectInfo(jstck, dwNumEffs, pEffInfo);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_GetEffectStatus(DWORD dwIntf, DWORD dwEffID, DWORD *pdwFlags)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->GetEffectStatus)
	return jstck->drv->GetEffectStatus(jstck, dwEffID, pdwFlags);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_SetAutoCenter(DWORD dwIntf, DWORD dwValue)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->SetAutoCenter)
	return jstck->drv->SetAutoCenter(jstck, dwValue);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_SetGain(DWORD dwIntf, DWORD dwValue)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->SetGain)
	return jstck->drv->SetGain(jstck, dwValue);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_UnloadEffect(DWORD dwIntf, DWORD dwEffID)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->UnloadEffect)
	return jstck->drv->UnloadEffect(jstck, dwEffID);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_LoadEffect(DWORD dwIntf, DWORD *pdwEffID, DWORD dwFlags, DIEFFECT *pEff, DIEFFECTINFOA *pEffInfo)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->LoadEffect)
	return jstck->drv->LoadEffect(jstck, pdwEffID, dwFlags, pEff, pEffInfo);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_StopEffect(DWORD dwIntf, DWORD dwEffID)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->StopEffect)
	return jstck->drv->StopEffect(jstck, dwEffID);

    return DIERR_UNSUPPORTED;
}

HRESULT WINAPI DIJOY_StartEffect(DWORD dwIntf, DWORD dwEffID, DWORD dwCount)
{
    WINE_JOYSTICK*	jstck = &JSTCK_Data[dwIntf];

    if (jstck->drv->StartEffect)
	return jstck->drv->StartEffect(jstck, dwEffID, dwCount);

    return DIERR_UNSUPPORTED;
}

