/*		DirectInput Joystick device
 *
 * Copyright 1998 Marcus Meissner
 * Copyright 1998,1999 Lionel Ulmer
 * Copyright 2000-2002 TransGaming Technologies Inc.
 *
 */

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#include <sys/fcntl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#include <errno.h>
#ifdef HAVE_SYS_ERRNO_H
# include <sys/errno.h>
#endif

#include "wine/debug.h"
#include "winbase.h"
#include "winerror.h"
#include "windef.h"
#include "mmsystem.h"
#include "wine/winbase16.h"
#include "winreg.h"
#include "dinput.h"

#include "dinput_private.h"
#include "device_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dinput);

/* Wine joystick driver object instances */
#define WINE_JOYSTICK_AXIS_BASE   0
#define WINE_JOYSTICK_POV_BASE    8
#define WINE_JOYSTICK_BUTTON_BASE 16

typedef struct {
	LONG lMin;
	LONG lMax;
	LONG lDeadZone;
	LONG lSaturation;
} ObjProps;

#define MAX_EFFECTS 8		/* FIXME: arbitrary */
typedef struct EffectImpl EffectImpl;
static HRESULT WINAPI 
JoystickAImpl_CreateEffect(LPDIRECTINPUTDEVICE8A iface, 
			   REFGUID rguid, LPCDIEFFECT peff, 
			   LPDIRECTINPUTEFFECT *ppdef, 
			   LPUNKNOWN pUnkOuter);
static HRESULT WINAPI 
JoystickAImpl_EnumEffects(LPDIRECTINPUTDEVICE8A iface,
			  LPDIENUMEFFECTSCALLBACKA lpCallback,
			  LPVOID lpvRef, DWORD dwEffType);
static HRESULT WINAPI 
JoystickAImpl_GetEffectInfo(LPDIRECTINPUTDEVICE8A iface,
			    LPDIEFFECTINFOA pdei, REFGUID rguid);
static HRESULT WINAPI 
JoystickAImpl_EnumCreatedEffectObjects(LPDIRECTINPUTDEVICE8A iface,
				       LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback,
				       LPVOID pvRef, DWORD fl);

typedef struct JoystickAImpl JoystickAImpl;
static ICOM_VTABLE(IDirectInputDevice8A) JoystickAvt;
struct JoystickAImpl
{
        LPVOID                          lpVtbl;
        DWORD                           ref;

	/* The 'parent' DInput */
	IDirectInputImpl *dinput;

	/* joystick private */
	DWORD				dwID;
	LPDIDATAFORMAT			drvdf;
	int*				offset_array;
	BYTE*				buffer;
	int				acquired;
	LPDIDATAFORMAT			df;
	DataFormat*			wine_df;
	ObjProps*			props;
        HANDLE				hEvent;
        CRITICAL_SECTION                crit;
        LPDIDEVICEOBJECTDATA 		data_queue;
        int				queue_head, queue_tail, queue_len;
        BOOL				overflow;

	/* effects */
	DWORD				dwAutoCenter;
	DWORD				dwGain;
	EffectImpl*			effects[MAX_EFFECTS];
	DWORD				drvNumEffs;
	LPDIEFFECTINFOA			drvEffInfo;
};

static DWORD js_count;
static HMODULE hJS;
static HRESULT (WINAPI *DIJOY_GetGuid)(DWORD dwID, LPGUID lpGuid);
static HRESULT (WINAPI *DIJOY_GetDevInfo)(DWORD dwID, DWORD dwVersion, LPDIDEVCAPS lpCaps, LPDIDEVICEINSTANCEA lpInfo);
static HRESULT (WINAPI *DIJOY_OpenDIDev)(DWORD dwID, DWORD dwVersion);
static HRESULT (WINAPI *DIJOY_CloseDIDev)(DWORD dwID);
static HRESULT (WINAPI *DIJOY_GetDIDataFormat)(DWORD dwID, LPDIDATAFORMAT* pdf);
static HRESULT (WINAPI *DIJOY_GetDIData)(DWORD dwID, BOOL req, DWORD* dwObj, DWORD* dwData, DWORD* dwTimeStamp);
static HRESULT (WINAPI *DIJOY_GetEffectInfo)(DWORD dwID, DWORD *dwNumEffs, LPDIEFFECTINFOA *pEffInfo);
static HRESULT (WINAPI *DIJOY_GetEffectStatus)(DWORD dwID, DWORD dwEffID, DWORD* pdwFlags);
static HRESULT (WINAPI *DIJOY_SetAutoCenter)(DWORD dwID, DWORD dwValue);
static HRESULT (WINAPI *DIJOY_SetGain)(DWORD dwID, DWORD dwValue);
static HRESULT (WINAPI *DIJOY_UnloadEffect)(DWORD dwID, DWORD dwEffID);
static HRESULT (WINAPI *DIJOY_LoadEffect)(DWORD dwID, DWORD *pdwEffID, DWORD dwFlags, DIEFFECT *pEff, DIEFFECTINFOA *pEffInfo);
static HRESULT (WINAPI *DIJOY_StopEffect)(DWORD dwID, DWORD dwEffID);
static HRESULT (WINAPI *DIJOY_StartEffect)(DWORD dwID, DWORD dwEffID, DWORD dwCount);

/***********************************************************************
 * 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 );
}

/***********************************************************************
 * setup_options
 *
 * read specific options
 */

static int  defaultDeadZone = 1000;/* 10 % of the device range*/
static void setup_dinput_joystickoptions(void)
{
	static char joystickoptionsread = 0;
	
  char buffer[MAX_PATH+16];
  HKEY hkey, appkey = 0;
  if (joystickoptionsread)
	return;
  if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config\\dinput", 0, NULL,
                       REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL )) {
    ERR("Cannot create config registry key\n");
    return;
  }
  
  /* 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, "\\dinput" );
    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, "DefaultDeadZone", buffer, sizeof(buffer) ))
  {
    int configDeadZone = atoi( buffer );
	if ((configDeadZone >= 0) && (configDeadZone <= 10000))
		defaultDeadZone = configDeadZone;
  }
  TRACE("DefaultDeadZone=(%d)\n", defaultDeadZone);
  joystickoptionsread = 1;
}


static BOOL joydev_load(void)
{
  if (!hJS) {
    /* this will load the winmm joystick driver */
    js_count = joyGetNumDevs();
    TRACE("js_count=%ld\n", js_count);
    /* grab handle to driver */
    hJS = GetModuleHandleA("joystick.drv");
    if (!hJS) {
      ERR("couldn't load joystick driver\n");
      return FALSE;
    }
    /* load dinput entry points */
    DIJOY_GetGuid = (LPVOID)GetProcAddress(hJS, "dijoy_GetGuid");
    DIJOY_GetDevInfo = (LPVOID)GetProcAddress(hJS, "dijoy_GetDevInfo");
    DIJOY_OpenDIDev = (LPVOID)GetProcAddress(hJS, "dijoy_OpenDIDev");
    DIJOY_CloseDIDev = (LPVOID)GetProcAddress(hJS, "dijoy_CloseDIDev");
    DIJOY_GetDIDataFormat = (LPVOID)GetProcAddress(hJS, "dijoy_GetDIDataFormat");
    DIJOY_GetDIData = (LPVOID)GetProcAddress(hJS, "dijoy_GetDIData");
    DIJOY_GetEffectInfo = (LPVOID)GetProcAddress(hJS, "dijoy_GetEffectInfo");
    DIJOY_GetEffectStatus = (LPVOID)GetProcAddress(hJS, "dijoy_GetEffectStatus");
    DIJOY_SetAutoCenter = (LPVOID)GetProcAddress(hJS, "dijoy_SetAutoCenter");
    DIJOY_SetGain = (LPVOID)GetProcAddress(hJS, "dijoy_SetGain");
    DIJOY_UnloadEffect = (LPVOID)GetProcAddress(hJS, "dijoy_UnloadEffect");
    DIJOY_LoadEffect = (LPVOID)GetProcAddress(hJS, "dijoy_LoadEffect");
    DIJOY_StopEffect = (LPVOID)GetProcAddress(hJS, "dijoy_StopEffect");
    DIJOY_StartEffect = (LPVOID)GetProcAddress(hJS, "dijoy_StartEffect");
    if (!DIJOY_GetGuid) return FALSE;
  }
  return TRUE;
}

static int joydev_enum_device(IDirectInputImpl *dinput, DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEA lpddi, DWORD dwID)
{
  DIDEVCAPS diCaps;

  if ((dwDevType!=0) && (GET_DIDEVICE_TYPE(dwDevType)!=DIDEVTYPE_JOYSTICK) &&
      (GET_DIDEVICE_TYPE(dwDevType)!=DI8DEVTYPE_JOYSTICK))
    return 0;

  if (!joydev_load())
    return 0;

  if (dwID >= js_count)
    return 0;

  diCaps.dwSize = sizeof(diCaps);

  if (FAILED(DIJOY_GetDevInfo(dwID, dinput->ver, &diCaps, lpddi)))
    return -1;

  if ((dwFlags & DIEDFL_FORCEFEEDBACK) && 
      !(diCaps.dwFlags & DIDC_FORCEFEEDBACK))
    return -1;

  TRACE("Enumerating the Joystick%ld device\n", dwID);

  return 1;
}

static JoystickAImpl *alloc_device(DWORD dwID, REFGUID rguid, LPVOID jvt, IDirectInputImpl *dinput)
{
  JoystickAImpl* newDevice;
  unsigned u;

  newDevice = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(JoystickAImpl));
  newDevice->lpVtbl = jvt;
  newDevice->ref = 1;
  /* Add a reference to our parent dinput */
  IDirectInputDevice2AImpl_AddRef((LPDIRECTINPUTDEVICE8A)dinput);
  newDevice->dinput = dinput;
  newDevice->dwID = dwID;

  DIJOY_GetDIDataFormat(newDevice->dwID, &newDevice->drvdf);

  TRACE("(drvdf.dwDataSize=%ld)\n",newDevice->drvdf->dwDataSize);
  TRACE("(drvdf.dwNumObjs=%ld)\n",newDevice->drvdf->dwNumObjs);
  for (u=0; u<newDevice->drvdf->dwNumObjs; u++) {
    TRACE("drvdf.rgodf[%d].guid %s (%p)\n",u,debugstr_guid(newDevice->drvdf->rgodf[u].pguid), newDevice->drvdf->rgodf[u].pguid);
    TRACE("drvdf.rgodf[%d].dwOfs %ld\n",u,newDevice->drvdf->rgodf[u].dwOfs);
    TRACE("dwType 0x%02x,dwInstance %d\n",DIDFT_GETTYPE(newDevice->drvdf->rgodf[u].dwType),DIDFT_GETINSTANCE(newDevice->drvdf->rgodf[u].dwType));
    TRACE("drvdf.rgodf[%d].dwFlags 0x%08lx\n",u,newDevice->drvdf->rgodf[u].dwFlags);
  }

  newDevice->offset_array = HeapAlloc(GetProcessHeap(), 0, sizeof(int)*newDevice->drvdf->dwNumObjs);
  for (u=0; u<newDevice->drvdf->dwNumObjs; u++)
    newDevice->offset_array[u] = newDevice->drvdf->rgodf[u].dwOfs;
  newDevice->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newDevice->drvdf->dwDataSize);
  newDevice->df = newDevice->drvdf;

  setup_dinput_joystickoptions();
  newDevice->props = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ObjProps)*newDevice->drvdf->dwNumObjs);
  for (u=0; u<newDevice->drvdf->dwNumObjs; u++) {
    if (newDevice->drvdf->rgodf[u].dwType & DIDFT_AXIS) {
      newDevice->props[u].lMin = 0x0000;
      newDevice->props[u].lMax = 0xFFFF;
      newDevice->props[u].lDeadZone = defaultDeadZone; /* 10% or [dinput]:DefaultDeadZone in config*/
      newDevice->props[u].lSaturation = 0; /* 0% */
    }
  }
  newDevice->queue_len = -1; /* unbuffered */
  newDevice->overflow = FALSE;

  InitializeCriticalSection(&(newDevice->crit));

  newDevice->dwGain = 10000;
  newDevice->dwAutoCenter = 0;
  for (u=0; u<MAX_EFFECTS; u++)
      newDevice->effects[u] = NULL;

  DIJOY_GetEffectInfo(newDevice->dwID, &newDevice->drvNumEffs, 
			&newDevice->drvEffInfo);

  TRACE("drvNumEffs=%ld\n", newDevice->drvNumEffs);
  for (u = 0; u < newDevice->drvNumEffs; u++) {
    TRACE("drvEffInfo[%d].guid %s (%p)\n", u,  debugstr_guid(&newDevice->drvEffInfo[u].guid), 
	  &newDevice->drvEffInfo[u].guid);
    TRACE("drvEffInfo[%d].dwEffType %lx\n", u, newDevice->drvEffInfo[u].dwEffType);
    TRACE("drvEffInfo[%d].dwStaticParams %lx\n", u, newDevice->drvEffInfo[u].dwStaticParams);
    TRACE("drvEffInfo[%d].dwDynamicParams %lx\n", u, newDevice->drvEffInfo[u].dwDynamicParams);
    TRACE("drvEffInfo[%d].tszName %s\n", u, newDevice->drvEffInfo[u].tszName);
  }

  return newDevice;
}

static HRESULT joydev_create_device(IDirectInputImpl *dinput, REFGUID rguid, REFIID riid, LPDIRECTINPUTDEVICEA* pdev)
{
  int i;
  GUID joyGuid;

  if (!joydev_load())
    return DIERR_DEVICENOTREG;

  for (i = 0; i < js_count; i++) {
    if (DIJOY_GetGuid(i, &joyGuid) != DI_OK)
      continue;
    if ((IsEqualGUID(&GUID_Joystick,rguid)) ||
	(IsEqualGUID(&joyGuid,rguid))) {
      if ((riid == NULL) ||
	  IsEqualGUID(&IID_IDirectInputDeviceA,riid) ||
	  IsEqualGUID(&IID_IDirectInputDevice2A,riid) ||
	  IsEqualGUID(&IID_IDirectInputDevice7A,riid) ||
	  IsEqualGUID(&IID_IDirectInputDevice8A,riid)) {
	HRESULT hr = DIJOY_OpenDIDev(i, dinput->ver);
	if (FAILED(hr)) return hr;

	*pdev=(IDirectInputDeviceA*) alloc_device(i, rguid, &JoystickAvt, dinput);

	TRACE("Creating the Joystick%d device (%p)\n", i, *pdev);
	return DI_OK;
      } else
	return DIERR_NOINTERFACE;
    }
  }

  return DIERR_DEVICENOTREG;
}

static dinput_device joydev = {
  30,
  joydev_enum_device,
  joydev_create_device
};

void init_joystick(void)
{
  dinput_register_device(&joydev);
}

void deinit_joystick(void)
{
}

/******************************************************************************
 *	Joystick
 */
static ULONG WINAPI JoystickAImpl_Release(LPDIRECTINPUTDEVICE8A iface)
{
  ICOM_THIS(JoystickAImpl,iface);

  TRACE("(this=%p,%ld)\n", This, This->ref);

  This->ref--;
  if (This->ref)
    return This->ref;

  DIJOY_CloseDIDev(This->dwID);

  /* Free the data queue */
  if (This->data_queue != NULL)
    HeapFree(GetProcessHeap(),0,This->data_queue);

  DeleteCriticalSection(&(This->crit));

  /* Free the DataFormat */
  HeapFree(GetProcessHeap(), 0, This->offset_array);
  HeapFree(GetProcessHeap(), 0, This->buffer);
  if (This->df != This->drvdf)
    HeapFree(GetProcessHeap(), 0, This->df);

  /* Release our reference to our parent dinput */
  IDirectInputDevice2AImpl_Release((LPDIRECTINPUTDEVICE8A)This->dinput);

  HeapFree(GetProcessHeap(),0,This);
  return 0;
}

/******************************************************************************
  *   SetDataFormat : the application can choose the format of the data
  *   the device driver sends back with GetDeviceState.
  */
static HRESULT WINAPI JoystickAImpl_SetDataFormat(
	LPDIRECTINPUTDEVICE8A iface,LPCDIDATAFORMAT df
)
{
  ICOM_THIS(JoystickAImpl,iface);
  int i;

  TRACE("(this=%p,%p)\n",This,df);

  TRACE("(df.dwSize=%ld)\n",df->dwSize);
  TRACE("(df.dwObjsize=%ld)\n",df->dwObjSize);
  TRACE("(df.dwFlags=0x%08lx)\n",df->dwFlags);
  TRACE("(df.dwDataSize=%ld)\n",df->dwDataSize);
  TRACE("(df.dwNumObjs=%ld)\n",df->dwNumObjs);

  for (i=0;i<df->dwNumObjs;i++) {
    TRACE("df.rgodf[%d].guid %s (%p)\n",i,debugstr_guid(df->rgodf[i].pguid), df->rgodf[i].pguid);
    TRACE("df.rgodf[%d].dwOfs %ld\n",i,df->rgodf[i].dwOfs);
    TRACE("dwType 0x%02x,dwInstance %d\n",DIDFT_GETTYPE(df->rgodf[i].dwType),DIDFT_GETINSTANCE(df->rgodf[i].dwType));
    TRACE("df.rgodf[%d].dwFlags 0x%08lx\n",i,df->rgodf[i].dwFlags);
  }

  /* Store the new data format */
  This->df = HeapAlloc(GetProcessHeap(),0,df->dwSize);
  memcpy(This->df, df, df->dwSize);
  This->df->rgodf = HeapAlloc(GetProcessHeap(),0,df->dwNumObjs*df->dwObjSize);
  memcpy(This->df->rgodf,df->rgodf,df->dwNumObjs*df->dwObjSize);

  /* Prepare all the data-conversion filters */
  This->wine_df = create_DataFormat(This->drvdf, df, This->offset_array, This->dinput->ver);

  return 0;
}

#define map_axis(val) (This->lMax == 65535 && This->lMin == 0) ? val+32768 : \
			((val+32768)*(This->lMax-This->lMin)/65536+This->lMin)

static void joy_polldev(JoystickAImpl *This, BOOL req, BOOL gen_event) {
    HRESULT hr;
    DWORD dwObj;
    DWORD dwData;
    DWORD dwEvent;
    DWORD dwTimeStamp;

    do {
	LPDIOBJECTDATAFORMAT odf;
	hr = DIJOY_GetDIData(This->dwID, req, &dwObj, &dwData, &dwTimeStamp);
	if (FAILED(hr)) return;
	if (hr == S_FALSE) return;
	odf = &This->drvdf->rgodf[dwObj];

	/* update state buffer */
	if (odf->dwType & DIDFT_BUTTON) {
	    *(BYTE*)(This->buffer+odf->dwOfs) = dwData;
	    dwEvent = dwData;
	} else if (odf->dwType & DIDFT_AXIS) {
	    /* FIXME: cache these */
	    LONG pmin = This->props[dwObj].lMin;
	    LONG pmax = This->props[dwObj].lMax;
	    LONG h_prange = (pmax - pmin) / 2;
	    /* we assume a physical range of 0-65534 */
	    LONG h_range = 32767 - This->props[dwObj].lDeadZone*65534/20000;
	    DWORD dwOut;
	    /* FIXME: saturation */

	    if (dwData > 65534) dwData = 65534;

	    if (dwData < h_range)
		dwOut = pmin + dwData*h_prange/h_range;
	    else if (dwData > (65534-h_range))
		dwOut = pmax - (65534-dwData)*h_prange/h_range;
	    else
		dwOut = (pmin + pmax) / 2;

	    *(DWORD*)(This->buffer+odf->dwOfs) = dwData = dwOut;
	    dwEvent = dwOut;
	} else {
	    *(DWORD*)(This->buffer+odf->dwOfs) = dwData;
	    dwEvent = dwData;
	}

	/* generate event */
	if (gen_event) {
	    GEN_EVENT(This->offset_array[dwObj],dwEvent,dwTimeStamp,(This->dinput->evsequence)++);
	}
    } while (TRUE);
}

/******************************************************************************
  *     Acquire : gets exclusive control of the joystick
  */
static HRESULT WINAPI JoystickAImpl_Acquire(LPDIRECTINPUTDEVICE8A iface)
{
    ICOM_THIS(JoystickAImpl,iface);

    TRACE("(this=%p)\n",This);
    if (This->acquired) return S_FALSE;
    /* grab initial state */
    joy_polldev(This, TRUE, FALSE);
    This->acquired = TRUE;
    return DI_OK;
}

/******************************************************************************
  *     Unacquire : frees the joystick
  */
static HRESULT WINAPI JoystickAImpl_Unacquire(LPDIRECTINPUTDEVICE8A iface)
{
    ICOM_THIS(JoystickAImpl,iface);

    TRACE("(this=%p)\n",This);
    if (!This->acquired) return S_FALSE;
    This->acquired = FALSE;
    return DI_OK;
}

/******************************************************************************
  *     GetDeviceState : returns the "state" of the joystick.
  *
  */
static HRESULT WINAPI JoystickAImpl_GetDeviceState(
	LPDIRECTINPUTDEVICE8A iface,DWORD len,LPVOID ptr
) {
    ICOM_THIS(JoystickAImpl,iface);

    TRACE("(this=%p,0x%08lx,%p)\n",This,len,ptr);
    if (!This->acquired)
        return DIERR_NOTACQUIRED;

    joy_polldev(This, FALSE, TRUE);
    fill_DataFormat(ptr, This->buffer, This->wine_df);
    return 0;
}

/******************************************************************************
  *     GetDeviceData : gets buffered input data.
  */
static HRESULT WINAPI JoystickAImpl_GetDeviceData(LPDIRECTINPUTDEVICE8A iface,
					      DWORD dodsize,
					      LPDIDEVICEOBJECTDATA dod,
					      LPDWORD entries,
					      DWORD flags
) {
  ICOM_THIS(JoystickAImpl,iface);
  DWORD len, count, nqtail;
  int ret = DI_OK;

  TRACE("(%p)->(dods=%ld,entries=%ld,fl=0x%08lx)\n",This,dodsize,*entries,flags);

  if (!This->acquired) {
      WARN(" application tries to get data from an unacquired device !\n");
      return DIERR_NOTACQUIRED;
  }

  if (This->queue_len == -1)
    return DIERR_NOTBUFFERED;

  if (dodsize < sizeof(DIDEVICEOBJECTDATA_DX3))
    return DIERR_INVALIDPARAM;

  /* Check for any events */
  joy_polldev(This, FALSE, TRUE);

  EnterCriticalSection(&(This->crit));

  len = ((This->queue_head < This->queue_tail) ? This->queue_len : 0)
      + (This->queue_head - This->queue_tail);
  if (len > *entries) len = *entries;

  count = 0;
  nqtail = This->queue_tail;
  while ((count < *entries || *entries == INFINITE) && count < len) {
    if (dod != NULL) {
      LPDIDEVICEOBJECTDATA pd = (LPDIDEVICEOBJECTDATA)((BYTE *)dod + dodsize * count);
      pd->dwOfs       = This->data_queue[nqtail].dwOfs;
      pd->dwData      = This->data_queue[nqtail].dwData;
      pd->dwTimeStamp = This->data_queue[nqtail].dwTimeStamp;
      pd->dwSequence  = This->data_queue[nqtail].dwSequence;
    }
    nqtail++;
    if (nqtail >= This->queue_len) nqtail -= This->queue_len;
    count++;
  }

  if (This->overflow) {
    ret = DI_BUFFEROVERFLOW;
    if (!(flags & DIGDD_PEEK)) {
      This->overflow = FALSE;
    }
  }

  if (!(flags & DIGDD_PEEK)) {
    This->queue_tail = nqtail;
  }

  TRACE("Application %s %ld event(s). %s\n", *entries == INFINITE ? "flushing" : "retrieving", count, ret == DI_OK ? "" : "BUFFEROVERFLOW");

  *entries = count;

  LeaveCriticalSection(&(This->crit));

  return ret;
}

int find_object(JoystickAImpl *This, DWORD dwHow, DWORD dwObj)
{
    int obj;

    switch (dwHow) {
    case DIPH_DEVICE:
	TRACE(" - DEVICE\n");
	return -1;
    case DIPH_BYID:
        for (obj = 0; obj < This->drvdf->dwNumObjs; obj++)
            if (This->drvdf->rgodf[obj].dwType == dwObj)
                break;
        break;
    case DIPH_BYOFFSET:
        for (obj = 0; obj < This->drvdf->dwNumObjs; obj++)
            if (This->offset_array[obj] == dwObj)
                break;
        break;
    default:
	FIXME("unhandled dwHow=%ld\n", dwHow);
	obj = This->drvdf->dwNumObjs;
        break;
    }

    if (obj == This->drvdf->dwNumObjs) {
	TRACE(" - obj not found\n");
	return -1;
    }

    TRACE(" - obj %d\n", obj);
    return obj;
}

/******************************************************************************
  *     SetProperty : change input device properties
  */
static HRESULT WINAPI JoystickAImpl_SetProperty(LPDIRECTINPUTDEVICE8A iface,
					    REFGUID rguid,
					    LPCDIPROPHEADER ph)
{
  ICOM_THIS(JoystickAImpl,iface);
  int obj;

  TRACE("(this=%p,%s,%p)\n",This,debugstr_guid(rguid),ph);
  TRACE("ph.dwSize = %ld, ph.dwHeaderSize =%ld, ph.dwObj = %ld, ph.dwHow= %ld\n",ph->dwSize, ph->dwHeaderSize,ph->dwObj,ph->dwHow);

  obj = find_object(This, ph->dwHow, ph->dwObj);
  if (ph->dwHow != DIPH_DEVICE && obj < 0) return DIERR_OBJECTNOTFOUND;

  if (!HIWORD(rguid)) {
    switch ((DWORD)rguid) {
    case (DWORD) DIPROP_BUFFERSIZE: {
      LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;

      TRACE("buffersize(%ld)\n", pd->dwData);
     
      This->data_queue = (LPDIDEVICEOBJECTDATA)HeapAlloc(GetProcessHeap(),0,
                                                          pd->dwData * sizeof(DIDEVICEOBJECTDATA));
      This->queue_head = 0;
      This->queue_tail = 0;
      This->queue_len = pd->dwData;
      break;
    }
    case (DWORD)DIPROP_RANGE: {
      LPCDIPROPRANGE	pr = (LPCDIPROPRANGE)ph;

      TRACE("proprange(%ld,%ld)\n",pr->lMin,pr->lMax);
      if (obj < 0) return DIERR_INVALIDPARAM;
      This->props[obj].lMin = pr->lMin;
      This->props[obj].lMax = pr->lMax;
      break;
    }
    case (DWORD)DIPROP_DEADZONE: {
      LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;

      TRACE("deadzone(%ld)\n",pd->dwData);
      if (obj < 0) return DIERR_INVALIDPARAM;
      This->props[obj].lDeadZone = pd->dwData;
      break;
    }
    case (DWORD)DIPROP_AUTOCENTER: {
      LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;
      HRESULT res;

      TRACE("autocenter(%lu)\n", pd->dwData);
      if (obj >= 0) return DIERR_INVALIDPARAM;
      res = DIJOY_SetAutoCenter(This->dwID, pd->dwData);
      if (SUCCEEDED(res))
	  This->dwAutoCenter = pd->dwData;
      return res;
    }
    case (DWORD)DIPROP_FFGAIN: {
      LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;
      HRESULT res;

      TRACE("ffgain(%lu)\n", pd->dwData);
      if (obj >= 0) return DIERR_INVALIDPARAM;
      res = DIJOY_SetGain(This->dwID, pd->dwData);
      if (SUCCEEDED(res))
	  This->dwGain = pd->dwData;
      return res;
    }
    default:
      FIXME("Unknown type %ld (%s)\n",(DWORD)rguid,debugstr_guid(rguid));
      break;
    }
  }
  return 0;
}

/******************************************************************************
  *     SetEventNotification : specifies event to be sent on state change
  */
static HRESULT WINAPI JoystickAImpl_SetEventNotification(
	LPDIRECTINPUTDEVICE8A iface, HANDLE hnd
) {
    ICOM_THIS(JoystickAImpl,iface);

    TRACE("(this=%p,0x%08lx)\n",This,(DWORD)hnd);
    This->hEvent = hnd;
    return DI_OK;
}

static HRESULT WINAPI JoystickAImpl_GetCapabilities(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVCAPS lpDIDevCaps)
{
    ICOM_THIS(JoystickAImpl,iface);

    TRACE("(this=%p,%p)\n",This,lpDIDevCaps);
    return DIJOY_GetDevInfo(This->dwID, This->dinput->ver, lpDIDevCaps, NULL);
}

static HRESULT WINAPI JoystickAImpl_Poll(LPDIRECTINPUTDEVICE8A iface)
{
    ICOM_THIS(JoystickAImpl,iface);
    TRACE("(this=%p)\n",This);

    if (!This->acquired)
	return DIERR_NOTACQUIRED;
    joy_polldev(This, TRUE, TRUE);
    return DI_OK;
}

/******************************************************************************
  *     EnumObjects : enumerate the different buttons and axis...
  */
static HRESULT WINAPI JoystickAImpl_EnumObjects(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIENUMDEVICEOBJECTSCALLBACKA lpCallback,
	LPVOID lpvRef,
	DWORD dwFlags)
{
  ICOM_THIS(JoystickAImpl,iface);
  DIDEVICEOBJECTINSTANCEA ddoi;
  unsigned u;

  TRACE("(this=%p,%p,%p,%08lx)\n", This, lpCallback, lpvRef, dwFlags);
  if (TRACE_ON(dinput)) {
    DPRINTF("  - flags = ");
    _dump_EnumObjects_flags(dwFlags);
    DPRINTF("\n");
  }

  /* Only the fields till dwFFMaxForce are relevant */
  ddoi.dwSize = FIELD_OFFSET(DIDEVICEOBJECTINSTANCEA, dwFFMaxForce);

  for (u=0; u<This->drvdf->dwNumObjs; u++) {
    LPDIOBJECTDATAFORMAT odf = &This->drvdf->rgodf[u];
    if ((odf->dwType & dwFlags) || (dwFlags == DIDFT_ALL)) {
      memcpy(&ddoi.guidType, odf->pguid, sizeof(GUID));
      ddoi.dwOfs = odf->dwOfs;
      ddoi.dwType = odf->dwType;
      ddoi.dwFlags = odf->dwFlags;
      if (ddoi.dwType & DIDFT_AXIS)
	sprintf(ddoi.tszName, "Axis %d", DIDFT_GETINSTANCE(ddoi.dwType));
      else if (ddoi.dwType & DIDFT_BUTTON)
	sprintf(ddoi.tszName, "Button %d", DIDFT_GETINSTANCE(ddoi.dwType));
      else if (ddoi.dwType & DIDFT_POV)
	sprintf(ddoi.tszName, "POV Hat %d", DIDFT_GETINSTANCE(ddoi.dwType));
      else
	sprintf(ddoi.tszName, "Unknown");
      _dump_OBJECTINSTANCEA(&ddoi);
      if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
    }
  }

  return DI_OK;
}

/******************************************************************************
  *     GetObjectInfo : Get details for a given object...
  */
static HRESULT WINAPI JoystickAImpl_GetObjectInfo(LPDIRECTINPUTDEVICE8A iface,
                                                  LPDIDEVICEOBJECTINSTANCEA pdidoi,
						  DWORD dwObj,
						  DWORD dwHow)
{
  ICOM_THIS(JoystickAImpl,iface);
  LPDIOBJECTDATAFORMAT odf=NULL;
  int i;

  TRACE("(this=%p,%p,0x%08lx,0x%08lx)\n",
	This, pdidoi, dwObj, dwHow);

  if (TRACE_ON(dinput)) {
    DPRINTF("  - dwObj = 0x%08lx\n", dwObj);
    DPRINTF("  - dwHow = %s\n",
	    ((dwHow == DIPH_DEVICE) ? "DIPH_DEVICE" :
	     ((dwHow == DIPH_BYOFFSET) ? "DIPH_BYOFFSET" :
	      ((dwHow == DIPH_BYID)) ? "DIPH_BYID" : "unknown")));
  }
  
  switch( dwHow ) {
    case DIPH_BYID:
      for( i=0; i<This->drvdf->dwNumObjs; i++ ) {
        odf = &This->drvdf->rgodf[i];
	if( odf->dwType == dwObj ) {
	  break;
	}  
      }
      if( odf == NULL || odf->dwType != dwObj ) {
        TRACE("id=0x%08lx not found\n", dwObj);
	return DIERR_OBJECTNOTFOUND;
      }
      memcpy(&pdidoi->guidType, odf->pguid, sizeof(GUID));
      pdidoi->dwOfs = odf->dwOfs;
      pdidoi->dwType = odf->dwType;
      pdidoi->dwFlags = odf->dwFlags;
      if (pdidoi->dwType & DIDFT_AXIS)
	sprintf(pdidoi->tszName, "Axis %d", DIDFT_GETINSTANCE(pdidoi->dwType));
      else if (pdidoi->dwType & DIDFT_BUTTON)
	sprintf(pdidoi->tszName, "Button %d", DIDFT_GETINSTANCE(pdidoi->dwType));
      else if (pdidoi->dwType & DIDFT_POV)
	sprintf(pdidoi->tszName, "POV Hat %d", DIDFT_GETINSTANCE(pdidoi->dwType));
      else
	sprintf(pdidoi->tszName, "Unknown");
      
      break;
    case DIPH_DEVICE:
    case DIPH_BYOFFSET:
    default:
      FIXME("dwHow = 0x%08lx - unsupported!\n", dwHow);
  }
  
  if (TRACE_ON(dinput))
    _dump_OBJECTINSTANCEA(pdidoi);

  return DI_OK;
}

/******************************************************************************
  *     GetProperty : get input device properties
  */
static HRESULT WINAPI JoystickAImpl_GetProperty(LPDIRECTINPUTDEVICE8A iface,
						REFGUID rguid,
						LPDIPROPHEADER pdiph)
{
  ICOM_THIS(JoystickAImpl,iface);
  int obj;

  TRACE("(this=%p,%s,%p): stub!\n",
	iface, debugstr_guid(rguid), pdiph);

  if (TRACE_ON(dinput))
    _dump_DIPROPHEADER(pdiph);

  obj = find_object(This, pdiph->dwHow, pdiph->dwObj);
  if (pdiph->dwHow != DIPH_DEVICE && obj < 0) return DIERR_OBJECTNOTFOUND;

  if (!HIWORD(rguid)) {
    switch ((DWORD)rguid) {
    case (DWORD) DIPROP_BUFFERSIZE: {
      LPDIPROPDWORD	pd = (LPDIPROPDWORD)pdiph;

      TRACE(" return buffersize = %d\n",This->queue_len);
      pd->dwData = This->queue_len;
      break;
    }

    case (DWORD) DIPROP_RANGE: {
      LPDIPROPRANGE pr = (LPDIPROPRANGE) pdiph;

      if (obj < 0) return DIERR_INVALIDPARAM;
      pr->lMin = This->props[obj].lMin;
      pr->lMax = This->props[obj].lMax;
      break;
    }

    case (DWORD) DIPROP_DEADZONE: {
      LPDIPROPDWORD pd = (LPDIPROPDWORD) pdiph;

      if (obj < 0) return DIERR_INVALIDPARAM;
      pd->dwData = This->props[obj].lDeadZone;
      break;
    }

    case (DWORD) DIPROP_AUTOCENTER: {
      LPDIPROPDWORD pd = (LPDIPROPDWORD) pdiph;

      if (obj >= 0) return DIERR_INVALIDPARAM;
      FIXME("autocenter(0)\n");
      pd->dwData = This->dwAutoCenter;
      break;
    }

    case (DWORD) DIPROP_FFGAIN: {
      LPDIPROPDWORD pd = (LPDIPROPDWORD) pdiph;

      if (obj >= 0) return DIERR_INVALIDPARAM;
      pd->dwData = This->dwGain;
      break;
    }

    default:
      FIXME("Unknown type %ld (%s)\n",(DWORD)rguid,debugstr_guid(rguid));
      break;
    }
  }


  return DI_OK;
}

static HRESULT WINAPI JoystickAImpl_GetDeviceInfo(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVICEINSTANCEA lpddi)
{
	ICOM_THIS(JoystickAImpl,iface);

	TRACE("(this=%p,%p)\n",
	      iface, lpddi);

	return DIJOY_GetDevInfo(This->dwID, This->dinput->ver, NULL, lpddi);
}

static ICOM_VTABLE(IDirectInputDevice8A) JoystickAvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectInputDevice2AImpl_QueryInterface,
	IDirectInputDevice2AImpl_AddRef,
	JoystickAImpl_Release,
	JoystickAImpl_GetCapabilities,
	JoystickAImpl_EnumObjects,
	JoystickAImpl_GetProperty,
	JoystickAImpl_SetProperty,
	JoystickAImpl_Acquire,
	JoystickAImpl_Unacquire,
	JoystickAImpl_GetDeviceState,
	JoystickAImpl_GetDeviceData,
	JoystickAImpl_SetDataFormat,
	JoystickAImpl_SetEventNotification,
	IDirectInputDevice2AImpl_SetCooperativeLevel,
	JoystickAImpl_GetObjectInfo,
	JoystickAImpl_GetDeviceInfo,
	IDirectInputDevice2AImpl_RunControlPanel,
	IDirectInputDevice2AImpl_Initialize,
	JoystickAImpl_CreateEffect,
	JoystickAImpl_EnumEffects,
	JoystickAImpl_GetEffectInfo,
	IDirectInputDevice2AImpl_GetForceFeedbackState,
	IDirectInputDevice2AImpl_SendForceFeedbackCommand,
	JoystickAImpl_EnumCreatedEffectObjects,
	IDirectInputDevice2AImpl_Escape,
	JoystickAImpl_Poll,
	IDirectInputDevice2AImpl_SendDeviceData,
	IDirectInputDevice7AImpl_EnumEffectsInFile,
	IDirectInputDevice7AImpl_WriteEffectToFile,
	IDirectInputDevice8AImpl_BuildActionMap,
	IDirectInputDevice8AImpl_SetActionMap,
	IDirectInputDevice8AImpl_GetImageInfo
};

/* ===================================================================== */

static ICOM_VTABLE(IDirectInputEffect) Effectvt;

struct EffectImpl
{
    LPVOID		lpVtbl;
    DWORD		ref;

    JoystickAImpl*	joy;
    DWORD		dwID;
    DWORD		drvID;
    DWORD		dwFlags;
    DIEFFECTINFOA*	pdei;
    int			loaded;

    DIEFFECT		eff;		/* FIXME: dynamic? */
    DWORD		axes[32];	/* FIXME: dynamic? */
    LONG		dirs[32];	/* FIXME: dynamic? */
    DIENVELOPE		env;		/* FIXME: dynamic? */
    DWORD		parms[6];	/* FIXME: dynamic? */
};

struct effect_entry {
    const GUID		*guid;
    const DWORD		type;
    const char		*name;
};

static struct effect_entry effect_table[] = {
    { &GUID_ConstantForce, DIEFT_CONSTANTFORCE, "Constant" },
    { &GUID_RampForce,     DIEFT_RAMPFORCE,     "Ramp" },
    { &GUID_Square,        DIEFT_PERIODIC,      "Square" },
    { &GUID_Sine,          DIEFT_PERIODIC,      "Sine" },
    { &GUID_Triangle,      DIEFT_PERIODIC,      "Triangle" },
    { &GUID_SawtoothUp,    DIEFT_PERIODIC,      "SawtoothUp" },
    { &GUID_SawtoothDown,  DIEFT_PERIODIC,      "SawtoothDown" },
    { &GUID_Spring,        DIEFT_CONDITION,     "Spring" },
    { &GUID_Damper,        DIEFT_CONDITION,     "Damper" },
    { &GUID_Inertia,       DIEFT_CONDITION,     "Inertia" },
    { &GUID_Friction,      DIEFT_CONDITION,     "Friction" },
    { &GUID_CustomForce,   DIEFT_CUSTOMFORCE,   "Custom" },
    { NULL,                0,                   "Unknown" }
};

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

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

    return NULL;
}

static void _dump_DIEFFECT(DIEFFECT *p, DWORD dwEffType)
{
    int i;

    DPRINTF("    Flags = 0x%lx,  Duration = %ld,  Period = %ld,  Delay = %ld\n",
	    p->dwFlags, p->dwDuration, p->dwSamplePeriod, p->dwStartDelay);
    DPRINTF("    Gain = %ld,  Trigger = %ld,  Repeat = %lu\n", 
	    p->dwGain, p->dwTriggerButton, p->dwTriggerRepeatInterval);

    if (p->rgdwAxes) {
	DPRINTF("    Axes      = %p  ", p->rgdwAxes);
	if (p->rgdwAxes) {
	    for (i = 0; i < p->cAxes; i++)
		DPRINTF("%lu ", p->rgdwAxes[i]);
	    DPRINTF("\n");
	}
    }
    if (p->rglDirection) {
	DPRINTF("    Direction = %p  ", p->rglDirection);
	if (p->rglDirection) {
	    for (i = 0; i < p->cAxes; i++)
		DPRINTF("%ld ", p->rglDirection[i]);
	    DPRINTF("\n");
	}
    }

    if (p->lpEnvelope) {
	DPRINTF("    Envelope = %p\n", p->lpEnvelope);
	if (p->lpEnvelope) {
	    DPRINTF("        AttackLevel = %ld,  AttackTime = %ld\n",
		    p->lpEnvelope->dwAttackLevel, p->lpEnvelope->dwAttackTime);
	    DPRINTF("        FadeLevel   = %ld,  FadeTime   = %ld\n", 
		    p->lpEnvelope->dwFadeLevel, p->lpEnvelope->dwFadeTime);
	}
    }

    if (p->lpvTypeSpecificParams) {
	DPRINTF("    SpecificParams = %p\n", p->lpvTypeSpecificParams);
	if (p->lpvTypeSpecificParams) {
	    DICONSTANTFORCE *cp = p->lpvTypeSpecificParams;
	    DIRAMPFORCE *rp = p->lpvTypeSpecificParams;
	    DIPERIODIC *pp = p->lpvTypeSpecificParams;
	    DICONDITION *np = p->lpvTypeSpecificParams;
	    DICUSTOMFORCE *up = p->lpvTypeSpecificParams;

	    switch (DIEFT_GETTYPE(dwEffType)) {
	    case DIEFT_CONSTANTFORCE:
		DPRINTF("        Magnitude = %ld\n", cp->lMagnitude);
		break;
	    case DIEFT_RAMPFORCE: 
		DPRINTF("        Start = %ld,  End = %ld\n", 
			rp->lStart, rp->lEnd);
		break;
	    case DIEFT_PERIODIC: 
		DPRINTF("        Magnitude = %lu,  Offset = %ld\n", 
			pp->dwMagnitude, pp->lOffset);
		DPRINTF("        Phase     = %lu,  Period = %lu\n", 
			pp->dwPhase, pp->dwPeriod);
		break;
	    case DIEFT_CONDITION: 
		DPRINTF("        Offset = %ld,  DeadBand = %ld\n", 
			np->lOffset, np->lDeadBand);
		DPRINTF("        PosCoef = %ld,  NegCoef = %ld\n", 
			np->lPositiveCoefficient, np->lNegativeCoefficient);
		DPRINTF("        PosSat  = %lu,  NegSat  = %lu\n", 
			np->dwPositiveSaturation, np->dwNegativeSaturation);
		break;
	    case DIEFT_CUSTOMFORCE:
		DPRINTF("        Channels = %ld,  Period = %lu\n", 
			up->cChannels, up->dwSamplePeriod);
		DPRINTF("        Samples  = %ld,  ForceData = %p\n", 
			up->cSamples, up->rglForceData);
		break;
	    }
	}
    }
}

static void _dump_EffectImpl(EffectImpl *p)
{
    DPRINTF("    ID = %lu,  drvID = %ld,  Loaded = %d,  Type = 0x%lx\n", 
	    p->dwID, p->drvID, p->loaded, p->pdei->dwEffType);
    DPRINTF("    GUID = %s (%s)\n", 
	    debugstr_guid(&p->pdei->guid), effect_by_guid(&p->pdei->guid)->name);

    _dump_DIEFFECT(&p->eff, p->pdei->dwEffType);
}

static HRESULT WINAPI 
EffectImpl_GetEffectGuid(LPDIRECTINPUTEFFECT iface, 
			 LPGUID pguid)
{
    ICOM_THIS(EffectImpl, iface);

    TRACE("(this=%p,%p)\n", This, pguid);
    *pguid = This->pdei->guid;

    return DI_OK;
}

static HRESULT WINAPI 
EffectImpl_Unload(LPDIRECTINPUTEFFECT iface)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;

    TRACE("(this=%p)\n", This);

    res = DIJOY_StopEffect(This->joy->dwID, This->drvID);
    if (FAILED(res))
	goto done;

    res = DIJOY_UnloadEffect(This->joy->dwID, This->drvID);
    if (FAILED(res))
	goto done;

    This->loaded = 0;
    This->drvID = -1;
    res = DI_OK;

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

static HRESULT WINAPI 
EffectImpl_Download(LPDIRECTINPUTEFFECT iface)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;
    DIEFFECT eff;
    DWORD axes[32];
    int i, obj;

    TRACE("(this=%p)\n", This);

    eff = This->eff;
    eff.dwFlags = (eff.dwFlags & ~DIEFF_OBJECTOFFSETS) | DIEFF_OBJECTIDS;

    if ((This->eff.dwFlags & DIPH_BYOFFSET) && This->eff.dwTriggerButton != -1) {
	obj = find_object(This->joy, DIPH_BYOFFSET, This->eff.dwTriggerButton);
	if (obj < 0) {
	    res = DIERR_INVALIDPARAM;
	    goto done;
	}
	eff.dwTriggerButton = This->joy->drvdf->rgodf[obj].dwType;
    }

    if ((This->eff.dwFlags & DIPH_BYOFFSET) &&This->eff.rgdwAxes) {
	eff.rgdwAxes = axes;
	for (i = 0; i < This->eff.cAxes; i++) {
	    obj = find_object(This->joy, DIPH_BYOFFSET, This->eff.rgdwAxes[i]);
	    if (obj < 0) {
		res = DIERR_INVALIDPARAM;
		goto done;
	    }
	    eff.rgdwAxes[i] = This->joy->drvdf->rgodf[obj].dwType;
	}
    }

    res = DIJOY_LoadEffect(This->joy->dwID, &This->drvID, This->dwFlags, &eff, This->pdei);
    if (FAILED(res))
	goto done;

    This->loaded = 1;
    res = DI_OK;

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

static HRESULT WINAPI 
EffectImpl_Stop(LPDIRECTINPUTEFFECT iface)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;

    res = DIJOY_StopEffect(This->joy->dwID, This->drvID);
    if (FAILED(res))
	goto done;

    res = DI_OK;

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

static HRESULT WINAPI 
EffectImpl_Start(LPDIRECTINPUTEFFECT iface, 
		 DWORD dwIterations, DWORD dwFlags)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;
    int i;

    TRACE("(this=%p,%ld,%lx)\n", This, dwIterations, dwFlags);

    if (!This->loaded) {
	if (dwFlags & DIES_NODOWNLOAD) {
	    res = DIERR_NOTDOWNLOADED;
	    goto done;
	}

	res = EffectImpl_Download(iface);
	if (FAILED(res))
	    goto done;
    }

    if (dwFlags & DIES_SOLO) {
	for (i = 0; i < MAX_EFFECTS; i++)
	    if (i != This->dwID && This->joy->effects[i]) {
		res = EffectImpl_Stop((LPDIRECTINPUTEFFECT)This->joy->effects[i]);
		if (FAILED(res) && res != DIERR_NOTDOWNLOADED)
		    goto done;
	    }
    }

    res = DIJOY_StartEffect(This->joy->dwID, This->drvID, dwIterations);
    if (FAILED(res))
	goto done;

    res = DI_OK;

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

static HRESULT WINAPI 
EffectImpl_GetParameters(LPDIRECTINPUTEFFECT iface, 
			 LPDIEFFECT peff, DWORD dwFlags)
{
    ICOM_THIS(EffectImpl, iface);

    FIXME("(this=%p,%p,%lx)\n", This, peff, dwFlags);

    return DIERR_UNSUPPORTED;
}

static HRESULT WINAPI 
EffectImpl_SetParameters(LPDIRECTINPUTEFFECT iface, 
			 LPCDIEFFECT peff, DWORD dwFlags)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;

    TRACE("(this=%p,%p,%lx)\n", This, peff, dwFlags);

    res = DIERR_INVALIDPARAM;

    if (dwFlags & DIEP_DURATION)
	This->eff.dwDuration = peff->dwDuration;
    if (dwFlags & DIEP_SAMPLEPERIOD)
	This->eff.dwSamplePeriod = peff->dwSamplePeriod;
    if (dwFlags & DIEP_GAIN)
	This->eff.dwGain = peff->dwGain;
    if (dwFlags & DIEP_TRIGGERBUTTON)
	This->eff.dwTriggerButton = peff->dwTriggerButton;
    if (dwFlags & DIEP_TRIGGERREPEATINTERVAL)
	This->eff.dwTriggerRepeatInterval = peff->dwTriggerRepeatInterval;
    if (dwFlags & DIEP_AXES) {
	if (This->eff.rgdwAxes)
	    goto done;
	if (peff->cAxes > 32)
	    goto done;
	if (peff->cAxes) {
	    if (!peff->rgdwAxes)
		goto done;
	    if (This->eff.cAxes && This->eff.cAxes != peff->cAxes)
		goto done;
	    This->eff.cAxes = peff->cAxes;
	    This->eff.rgdwAxes = This->axes;
	    memcpy(This->eff.rgdwAxes, peff->rgdwAxes,  sizeof(DWORD)*peff->cAxes);
	} else
	    dwFlags &= ~DIEP_AXES;
    }
    if (dwFlags & DIEP_DIRECTION) {
	if (peff->cAxes > 32)
	    goto done;
	if (peff->cAxes) {
	    if (!peff->rglDirection)
		goto done;
	    if (This->eff.cAxes && This->eff.cAxes != peff->cAxes)
		goto done;
	    This->eff.dwFlags = peff->dwFlags & 0x00000070;
	    This->eff.cAxes = peff->cAxes;
	    This->eff.rglDirection = This->dirs;
	    memcpy(This->eff.rglDirection, peff->rglDirection, 
		   sizeof(LONG)*peff->cAxes);
	} else
	    dwFlags &= ~DIEP_DIRECTION;
    }
    if (dwFlags & DIEP_ENVELOPE) {
	if (peff->lpEnvelope) {
	    This->eff.lpEnvelope = &This->env;
	    *This->eff.lpEnvelope = *peff->lpEnvelope;
	} else
	    dwFlags &= ~DIEP_ENVELOPE;
    }
    if (dwFlags & DIEP_TYPESPECIFICPARAMS) {
	if (peff->lpvTypeSpecificParams) {
	    This->eff.lpvTypeSpecificParams = This->parms;
	    memcpy(This->eff.lpvTypeSpecificParams, peff->lpvTypeSpecificParams,
		   peff->cbTypeSpecificParams);
	} else
	    dwFlags &= ~DIEP_TYPESPECIFICPARAMS;
    }
    if (dwFlags & DIEP_STARTDELAY) {
	if (This->joy->dinput->ver >= 0x600)
	    This->eff.dwStartDelay = peff->dwStartDelay;
	else
	    dwFlags &= ~DIEP_STARTDELAY;
    }

    This->loaded = 0;
    This->dwFlags |= dwFlags;
    res = DI_OK;

    if (TRACE_ON(dinput))
	_dump_EffectImpl(This);

    if (dwFlags & DIEP_NODOWNLOAD)
	goto done;

    /* FIXME: ignore DIEP_NORESTART */

    res = EffectImpl_Download(iface);
    if (FAILED(res))
	goto done;

    if (!(dwFlags & DIEP_START))
	goto done;

    res = EffectImpl_Start(iface, 1, 0);
    if (FAILED(res))
	goto done;

    res = DI_OK;

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

static HRESULT WINAPI 
EffectImpl_GetEffectStatus(LPDIRECTINPUTEFFECT iface, 
			   LPDWORD pdwFlags)
{
    ICOM_THIS(EffectImpl, iface);
    HRESULT res;

    TRACE("(this=%p,%p)\n", This, pdwFlags);

    res = DIJOY_GetEffectStatus(This->joy->dwID, This->drvID, pdwFlags);

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

static HRESULT WINAPI 
EffectImpl_Escape(LPDIRECTINPUTEFFECT iface, 
		  LPDIEFFESCAPE pesc)
{
    ICOM_THIS(EffectImpl, iface);

    FIXME("(this=%p,%p)\n", This, pesc);

    return DI_OK;
}

static HRESULT WINAPI 
EffectImpl_QueryInterface(LPDIRECTINPUTEFFECT iface,
			  REFIID riid, LPVOID *ppobj)
{
    ICOM_THIS(EffectImpl, iface);

    TRACE("(this=%p,%s,%p)\n", This, debugstr_guid(riid), ppobj);

    return IDirectInputDevice2AImpl_QueryInterface((LPDIRECTINPUTDEVICE8A)iface, riid, ppobj);
}

static ULONG WINAPI 
EffectImpl_AddRef(LPDIRECTINPUTEFFECT iface)
{
    ICOM_THIS(EffectImpl, iface);

    TRACE("(this=%p,%ld)\n", This, This->ref);

    return ++This->ref;
}

static HRESULT WINAPI 
EffectImpl_Initialize(LPDIRECTINPUTEFFECT iface, 
		      HINSTANCE hinst, DWORD dwVersion, REFGUID rguid)
{
    ICOM_THIS(EffectImpl, iface);

    FIXME("(this=%p,%d,%ld,%s): stub!\n",
	  This, hinst, dwVersion, debugstr_guid(rguid));

    return DI_OK;
}

static ULONG WINAPI 
EffectImpl_Release(LPDIRECTINPUTEFFECT iface)
{
    ICOM_THIS(EffectImpl, iface);

    TRACE("(this=%p,%ld)\n", This, This->ref);

    if (--This->ref)
	return This->ref;

    EffectImpl_Unload(iface);

    This->joy->effects[This->dwID] = NULL;
    JoystickAImpl_Release((LPDIRECTINPUTDEVICE8A)This->joy);

    HeapFree(GetProcessHeap(),0,This);

    return 0;
}

static ICOM_VTABLE(IDirectInputEffect) Effectvt =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    EffectImpl_QueryInterface,
    EffectImpl_AddRef,
    EffectImpl_Release,
    EffectImpl_Initialize,
    EffectImpl_GetEffectGuid,
    EffectImpl_GetParameters,
    EffectImpl_SetParameters,
    EffectImpl_Start,
    EffectImpl_Stop,
    EffectImpl_GetEffectStatus,
    EffectImpl_Download,
    EffectImpl_Unload,
    EffectImpl_Escape
};

static HRESULT WINAPI 
JoystickAImpl_CreateEffect(LPDIRECTINPUTDEVICE8A iface, 
			   REFGUID rguid, LPCDIEFFECT peff, 
			   LPDIRECTINPUTEFFECT *ppdef, LPUNKNOWN pUnkOuter)
{
    ICOM_THIS(JoystickAImpl,iface);
    int i;
    DIEFFECTINFOA *pdei;
    EffectImpl *p = NULL;
    HRESULT res;

    TRACE("(this=%p,%s,%p,%p,%p): stub!\n",
	  This, debugstr_guid(rguid), peff, ppdef, pUnkOuter);

    /* We don't support aggregation yet */
    if (pUnkOuter) {
	res = DIERR_INVALIDPARAM;
	goto error;
    }

    /* See if the requested effect is supported */
    for (i = 0; i < This->drvNumEffs; i++)
	if (IsEqualGUID(&This->drvEffInfo[i].guid, rguid))
	    break;
    if (i >= This->drvNumEffs) {
	res = DIERR_INVALIDPARAM;
	goto error;
    }
    pdei = &This->drvEffInfo[i];

    if (DIEFT_GETTYPE(This->drvEffInfo[i].dwEffType) == DIEFT_CUSTOMFORCE) {
	FIXME("DIEFT_CUSTOMEFORCE not supported\n");
	res = DIERR_UNSUPPORTED;
	goto error;
    }

    /* See if there is romm for the new effect */
    for (i = 0; i < MAX_EFFECTS; i++)
	if (This->effects[i] == NULL)
	    break;
    if (i >= MAX_EFFECTS) {
	res = DIERR_DEVICEFULL;
	goto error;
    }

    /* Allocate memory for the new effect */
    p = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(EffectImpl));
    if (!p) {
	res = DIERR_DEVICEFULL;
	goto error;
    }

    /* Initialize the new effect core */
    *ppdef = (IDirectInputEffect *)p;
    p->lpVtbl = &Effectvt;
    p->ref = 1;
    p->joy = This;
    p->dwID = i;
    p->drvID = -1;
    p->pdei = pdei;
    p->loaded = 0;

    /* Initialize the new effect parameters */
    if (peff) {
	res = EffectImpl_SetParameters((LPDIRECTINPUTEFFECT)p, peff, 
				       DIEP_NODOWNLOAD | DIEP_ALLPARAMS);
	if (FAILED(res))
	    goto error;
    }

    /* Dump the new effect if not already done */
    if (!peff && TRACE_ON(dinput))
	_dump_EffectImpl(p);

    /* Success, save and return */
    IDirectInputDevice2AImpl_AddRef((LPDIRECTINPUTDEVICE8A)This);
    This->effects[i] = p;
    res = DI_OK;
    goto done;

 error:
    /* Failure, clean up and return */
    if (p)
	HeapFree(GetProcessHeap(), 0, p);

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

static HRESULT WINAPI 
JoystickAImpl_EnumEffects(LPDIRECTINPUTDEVICE8A iface, 
			  LPDIENUMEFFECTSCALLBACKA lpCallback, 
			  LPVOID lpvRef, DWORD dwEffType)
{
    ICOM_THIS(JoystickAImpl,iface);
    unsigned u;

    TRACE("(this=%p,%p,%p,%08lx)\n", This, lpCallback, lpvRef, dwEffType);

    for (u = 0; u < This->drvNumEffs; u++) {
	LPDIEFFECTINFOA pdei = &This->drvEffInfo[u];
	if ((dwEffType == DIEFT_ALL) || 
	    (DIEFT_GETTYPE(pdei->dwEffType) == dwEffType)) {
	    if (lpCallback(pdei, lpvRef) != DIENUM_CONTINUE)
		return DI_OK;
	}
    }

    return DI_OK;
}

static HRESULT WINAPI 
JoystickAImpl_GetEffectInfo(LPDIRECTINPUTDEVICE8A iface, 
			    LPDIEFFECTINFOA pdei, REFGUID rguid)
{
    ICOM_THIS(JoystickAImpl,iface);
    unsigned u;

    TRACE("(this=%p,%p,%s)\n", This, pdei, debugstr_guid(rguid));

    for (u = 0; u < This->drvNumEffs; u++) {
	LPDIEFFECTINFOA drvpdei = &This->drvEffInfo[u];
	if (IsEqualGUID(&drvpdei->guid, rguid)) {
	    int s = (pdei->dwSize < drvpdei->dwSize) ?
		pdei->dwSize : drvpdei->dwSize;
	    memcpy(pdei, drvpdei, s);
	    pdei->dwSize = s;
	    return DI_OK;
	}
    }

    /* FIXME: not listed as a valid result but I don't know what else
     * to return */
    return DIERR_OBJECTNOTFOUND;
}

static HRESULT WINAPI 
JoystickAImpl_EnumCreatedEffectObjects(LPDIRECTINPUTDEVICE8A iface,
				       LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback,
				       LPVOID pvRef, DWORD fl)
{
    ICOM_THIS(JoystickAImpl,iface);
    unsigned u;

    TRACE("(this=%p,%p,%p,%08lx)\n", This, lpCallback, pvRef, fl);

    if (fl != 0)
	return DIERR_INVALIDPARAM;

    for (u = 0; u < MAX_EFFECTS; u++) {
	if (This->effects[u] &&
	    lpCallback((LPDIRECTINPUTEFFECT)This->effects[u], pvRef) != DIENUM_CONTINUE)
	    return DI_OK;
    }

    return DI_OK;
}

