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

#include "config.h"
#include <string.h>
#ifdef HAVE_SYS_ERRNO_H
# include <sys/errno.h>
#endif

#include "winbase.h"
#include "winuser.h"
#include "winerror.h"
#include "winnls.h"
#include "dinput.h"

#include "dinput_private.h"
#include "device_private.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(dinput);

#define WINE_INTERNAL_NUM_KEYS  256

/* ------------------------------------ */
/* Wine keyboard internal data format   */
/* ------------------------------------ */

typedef BYTE Wine_InternalKeyboardData;

static DIOBJECTDATAFORMAT Wine_InternalKeyboardObjectFormat[WINE_INTERNAL_NUM_KEYS]; /* initialized in alloc_device */

static DIDATAFORMAT Wine_InternalKeyboardFormat = {
  sizeof(DIDATAFORMAT), /* dwSize - unused */
  sizeof(DIOBJECTDATAFORMAT), /* dwObjsize - unused */
  0, /* dwFlags - unused */
  WINE_INTERNAL_NUM_KEYS*sizeof(Wine_InternalKeyboardData),
  WINE_INTERNAL_NUM_KEYS, /* dwNumObjs */
  Wine_InternalKeyboardObjectFormat
};


static ICOM_VTABLE(IDirectInputDevice8A) SysKeyboardAvt;

typedef struct SysKeyboardAImpl SysKeyboardAImpl;
struct SysKeyboardAImpl
{
        LPVOID                          lpVtbl;
        DWORD                           ref;
        GUID                            guid;

	IDirectInputImpl *dinput;

        /* SysKeyboardAImpl */
	HANDLE	                        hEvent;
	int                             acquired;
        int                             buffersize;  /* set in 'SetProperty'         */
        LPDIDEVICEOBJECTDATA            buffer;      /* buffer for 'GetDeviceData'.
                                                        Alloc at 'Acquire', Free at
                                                        'Unacquire'                  */
        int                             count;       /* number of objects in use in
                                                        'buffer'                     */
        int                             start;       /* 'buffer' rotates. This is the
                                                        first in use (if count > 0)  */
        BOOL                            overflow;    /* return DI_BUFFEROVERFLOW in
                                                        'GetDeviceData'              */
	/* The current data format and the conversion between internal
	 * and external data formats */
	LPDIDATAFORMAT                  df;
	DataFormat *                    wine_df;
	int                             offset_array[WINE_INTERNAL_NUM_KEYS];
	UINT_PTR                        appdata_array[WINE_INTERNAL_NUM_KEYS];
	
        CRITICAL_SECTION                crit;
};

SysKeyboardAImpl *current; /* Today's acquired device
FIXME: currently this can be only one.
Maybe this should be a linked list or st.
I don't know what the rules are for multiple acquired keyboards,
but 'DI_LOSTFOCUS' and 'DI_UNACQUIRED' exist for a reason.
*/

static BYTE DInputKeyState[WINE_INTERNAL_NUM_KEYS]; /* array for 'GetDeviceState' */
static void set_name(char *dest, BYTE dik_code);

static CRITICAL_SECTION keyboard_crit;
static DWORD keyboard_users;
static HHOOK keyboard_hook;

LRESULT CALLBACK KeyboardCallback( int code, WPARAM wparam, LPARAM lparam )
{
  TRACE("(%d,%d,%ld)\n", code, wparam, lparam);

  if (code == HC_ACTION)
    {
      BYTE dik_code;
      BOOL down;
      DWORD timestamp;
      BYTE new_state;

      {
        KBDLLHOOKSTRUCT *hook = (KBDLLHOOKSTRUCT *)lparam;
        dik_code = hook->scanCode;
        if (hook->flags & LLKHF_EXTENDED) dik_code |= 0x80;
        down = !(hook->flags & LLKHF_UP);
        timestamp = hook->time;
      }

      new_state = (down ? 0x80 : 0x00);
      /* return early if this is a repeated event, 
       * since dinput does support repeating key events */
      if( DInputKeyState[dik_code] == new_state ) {
	return CallNextHookEx(keyboard_hook, code, wparam, lparam);
      }

      DInputKeyState[dik_code] = new_state;

      if (current != NULL) {
	
	  EnterCriticalSection(&(current->crit));

	  if (current->hEvent)
            SetEvent(current->hEvent);

          if (current->buffer != NULL) {
	    int n;
	    int offset = current->offset_array[dik_code];
	    UINT_PTR appdata = current->appdata_array[dik_code];
	    if( offset == -1 ) {
	      // application doesn't seem to care about this key 
	      current->dinput->evsequence++;
	      TRACE("Ignoring event : %d - %d - %ld - %ld\n", dik_code, (down ? 0x80 : 0), timestamp, 
		    current->dinput->evsequence);
	    } else {

	      n = (current->start + current->count) % current->buffersize;

	      current->buffer[n].dwOfs = offset; 
	      current->buffer[n].dwData = down ? 0x80 : 0;
	      current->buffer[n].dwTimeStamp = timestamp;
	      current->buffer[n].dwSequence = current->dinput->evsequence++;
	      current->buffer[n].uAppData = appdata;

	      TRACE("Adding event at offset %d : %ld(%d) - %ld - %ld - %ld\n", n,
		    current->buffer[n].dwOfs, dik_code, current->buffer[n].dwData, current->buffer[n].dwTimeStamp, 
		    current->buffer[n].dwSequence);

	      if (current->count == current->buffersize) {
		  current->start++;
		  current->overflow = TRUE;
	      } else {
		current->count++;
	      }
	    }
	  }

	  LeaveCriticalSection(&(current->crit));
      }
    }

  return CallNextHookEx(keyboard_hook, code, wparam, lparam);
}

static GUID DInput_Wine_Keyboard_GUID = { /* 0ab8648a-7735-11d2-8c73-71df54a96441 */
  0x0ab8648a,
  0x7735,
  0x11d2,
  {0x8c, 0x73, 0x71, 0xdf, 0x54, 0xa9, 0x64, 0x41}
};

static int keyboarddev_enum_device(IDirectInputImpl *dinput, DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEA lpddi, DWORD dwID)
{
  if ((dwDevType != 0) && (dwDevType != DIDEVTYPE_KEYBOARD) &&
      (dwDevType != DI8DEVTYPE_KEYBOARD))
    return 0;

  /* We only support 1 keyboard device. */
  if (dwID > 0)
    return 0;

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

  lpddi->guidInstance = GUID_SysKeyboard;/* DInput's GUID */
  lpddi->guidProduct = DInput_Wine_Keyboard_GUID; /* Vendor's GUID */
  if (HIBYTE(dinput->ver) >= 8)
    lpddi->dwDevType = DI8DEVTYPE_KEYBOARD | (DI8DEVTYPEKEYBOARD_UNKNOWN << 8);
  else
    lpddi->dwDevType = DIDEVTYPE_KEYBOARD | (DIDEVTYPEKEYBOARD_UNKNOWN << 8);
  strcpy(lpddi->tszInstanceName, "Keyboard");
  strcpy(lpddi->tszProductName, "Wine Keyboard");

  return 1;
}

static SysKeyboardAImpl *alloc_device(REFGUID rguid, LPVOID kvt, IDirectInputImpl *dinput)
{
    int offset_array[WINE_INTERNAL_NUM_KEYS];
    int i;
    SysKeyboardAImpl* newDevice;

    /* initialize Wine_InternalKeyboardObjectFormat */
    for( i=0; i < WINE_INTERNAL_NUM_KEYS; i++ ) {
      Wine_InternalKeyboardObjectFormat[i].pguid = &GUID_Key;
      Wine_InternalKeyboardObjectFormat[i].dwOfs = offset_array[i] = i;
      Wine_InternalKeyboardObjectFormat[i].dwType = DIDFT_MAKEINSTANCE(i) | DIDFT_BUTTON;
      Wine_InternalKeyboardObjectFormat[i].dwFlags = 0;
    }

    newDevice = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(SysKeyboardAImpl));
    newDevice->lpVtbl = kvt;
    newDevice->ref = 1;
    InitializeCriticalSection(&(newDevice->crit));
    memcpy(&(newDevice->guid),rguid,sizeof(*rguid));

    /* setup internal data format */
    newDevice->df = &Wine_InternalKeyboardFormat;
    memcpy(newDevice->offset_array, offset_array, WINE_INTERNAL_NUM_KEYS*sizeof(int));
    newDevice->wine_df = (DataFormat *) HeapAlloc(GetProcessHeap(), 0, sizeof(DataFormat));
    newDevice->wine_df->size = 0;
    newDevice->wine_df->internal_format_size = Wine_InternalKeyboardFormat.dwSize;
    newDevice->wine_df->dt = NULL;
    newDevice->dinput = dinput;

    EnterCriticalSection(&keyboard_crit);
    if (!keyboard_users++)
	keyboard_hook = SetWindowsHookExW( WH_KEYBOARD_LL, KeyboardCallback, 0, 0 );
    LeaveCriticalSection(&keyboard_crit);

    /* read current keyboard state */
    memset(&DInputKeyState, 0, WINE_INTERNAL_NUM_KEYS);
    for( i=0; i < WINE_INTERNAL_NUM_KEYS; i++ ) {
      WORD vkey = MapVirtualKeyA( i, 3 );
      if( vkey && (GetAsyncKeyState( vkey ) & 0x8000 )) {
	DInputKeyState[i] = 0x80;
      }
    }

    return newDevice;
}


static HRESULT keyboarddev_create_device(IDirectInputImpl *dinput, REFGUID rguid, REFIID riid, LPDIRECTINPUTDEVICEA* pdev)
{
  if ((IsEqualGUID(&GUID_SysKeyboard,rguid)) ||          /* Generic Keyboard */
      (IsEqualGUID(&DInput_Wine_Keyboard_GUID,rguid))) { /* Wine Keyboard */
    if ((riid == NULL) ||
	IsEqualGUID(&IID_IDirectInputDeviceA,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice2A,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice7A,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice8A,riid)) {
      *pdev=(IDirectInputDeviceA*) alloc_device(rguid, &SysKeyboardAvt, dinput);

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

  return DIERR_DEVICENOTREG;
}

static dinput_device keyboarddev = {
  100,
  keyboarddev_enum_device,
  keyboarddev_create_device
};

void init_keyboard(void)
{
  InitializeCriticalSection( &keyboard_crit );
  CRITICAL_SECTION_NAME( &keyboard_crit, "dinput_keyboard" );
  
  dinput_register_device(&keyboarddev);
}

void deinit_keyboard(void)
{
  DeleteCriticalSection( &keyboard_crit );
}



static ULONG WINAPI SysKeyboardAImpl_Release(LPDIRECTINPUTDEVICE8A iface)
{
	ICOM_THIS(SysKeyboardAImpl,iface);

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

	EnterCriticalSection(&keyboard_crit);
	if (!--keyboard_users) {
	    UnhookWindowsHookEx( keyboard_hook );
	    keyboard_hook = 0;
	}
	LeaveCriticalSection(&keyboard_crit);

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

	/* Free the DataFormat */
	if (This->df != &(Wine_InternalKeyboardFormat)) {
	  HeapFree(GetProcessHeap(), 0, This->df->rgodf);
	  HeapFree(GetProcessHeap(), 0, This->df);
	}
	if (This->wine_df) {
	  if (This->wine_df->dt) {
	    HeapFree(GetProcessHeap(), 0, This->wine_df->dt);
	  }
	  HeapFree(GetProcessHeap(), 0, This->wine_df);
	}

	DeleteCriticalSection(&(This->crit));

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

static HRESULT WINAPI SysKeyboardAImpl_SetProperty(
	LPDIRECTINPUTDEVICE8A iface,REFGUID rguid,LPCDIPROPHEADER ph
)
{
	ICOM_THIS(SysKeyboardAImpl,iface);

	TRACE("(this=%p,%s,%p)\n",This,debugstr_guid(rguid),ph);
	TRACE("(size=%ld,headersize=%ld,obj=%ld,how=%ld\n",
	    ph->dwSize,ph->dwHeaderSize,ph->dwObj,ph->dwHow);
	if (!HIWORD(rguid)) {
		switch ((DWORD)rguid) {
		case (DWORD) DIPROP_BUFFERSIZE: {
			LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;

			TRACE("(buffersize=%ld)\n",pd->dwData);

                        if (This->acquired)
                           return DIERR_INVALIDPARAM;

                        This->buffersize = pd->dwData;

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

static HRESULT WINAPI SysKeyboardAImpl_GetProperty(LPDIRECTINPUTDEVICE8A iface,
						   REFGUID rguid,
						   LPDIPROPHEADER pdiph)
{
  ICOM_THIS(SysKeyboardAImpl,iface);

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

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

  if (!HIWORD(rguid)) {
    switch((DWORD)rguid) {
      case (DWORD)DIPROP_BUFFERSIZE:
	{
	  LPDIPROPDWORD pd = (LPDIPROPDWORD)pdiph;
	  TRACE(" return buffersize = %d\n", This->buffersize);
	  pd->dwData = This->buffersize;
	  break;
	}
      case (DWORD)DIPROP_KEYNAME:
	{
	  LPDIPROPSTRING pps = (LPDIPROPSTRING)pdiph;
	  int i;
	  char name[MAX_PATH] = "";
	  
	  switch(pps->diph.dwHow) {
	    case DIPH_BYOFFSET:
	      /* loop until we find the offset the app is looking for, then fill in the name */
	      for( i=0; i<WINE_INTERNAL_NUM_KEYS; i++ ) {
		if( Wine_InternalKeyboardObjectFormat[i].dwOfs == pps->diph.dwObj ) {
		  set_name(name, i);
		  TRACE("matched KEYNAME offset %ld with key %s\n", pps->diph.dwObj, name);
		  break;
		}
	      }
	      break;
	    case DIPH_DEVICE:
	    case DIPH_BYID:
	    default:
	      FIXME("DIPROP_KEYNAME dwHow=%ld not implemented yet!\n", pps->diph.dwHow);
	      break;
	  }
	  MultiByteToWideChar(CP_ACP, 0, name, -1, pps->wsz, MAX_PATH);
	  break;
	}
      default:
	FIXME("Unknown type %ld (%s)\n", (DWORD)rguid,debugstr_guid(rguid));
	break;
    }
  }
	  	
  return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_GetDeviceState(
	LPDIRECTINPUTDEVICE8A iface,DWORD len,LPVOID ptr
)
{
    ICOM_THIS(SysKeyboardAImpl,iface);

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

    /* Note: device does not need to be acquired */
    if( len < This->df->dwDataSize ) {
      ERR("buffer too small: len=%ld,required=%ld\n", len, This->df->dwDataSize);
      return DIERR_INVALIDPARAM;
    }

    MsgWaitForMultipleObjectsEx(0, NULL, 0, 0, 0);

    /* zero out the memory they pass in */
    memset(ptr, 0, len);
    
    EnterCriticalSection(&(This->crit));

    /* copy the current keyboard state */
    fill_DataFormat(ptr, DInputKeyState, This->wine_df);

    LeaveCriticalSection(&(This->crit));

    return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_GetDeviceData(
	LPDIRECTINPUTDEVICE8A iface,DWORD dodsize,LPDIDEVICEOBJECTDATA dod,
	LPDWORD entries,DWORD flags
)
{
	ICOM_THIS(SysKeyboardAImpl,iface);
	int ret = DI_OK, i = 0;

        TRACE("(this=%p,%ld,%p,%p(%ld)),0x%08lx)\n",
              This,dodsize,dod,entries,entries?*entries:0,flags);

	if (This->acquired == 0)
	  return DIERR_NOTACQUIRED;

        if (This->buffer == NULL)
          return DIERR_NOTBUFFERED;

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

        MsgWaitForMultipleObjectsEx(0, NULL, 0, 0, 0);

        EnterCriticalSection(&(This->crit));

        /* Copy item at a time for the case dodsize > sizeof(buffer[n]) */
        while ((i < *entries || *entries == INFINITE) && i < This->count)
          {
            if (dod != NULL)
              {
                int n = (This->start + i) % This->buffersize;
                LPDIDEVICEOBJECTDATA pd
                   = (LPDIDEVICEOBJECTDATA)((BYTE *)dod + dodsize * i);
                pd->dwOfs       = This->buffer[n].dwOfs;
                pd->dwData      = This->buffer[n].dwData;
                pd->dwTimeStamp = This->buffer[n].dwTimeStamp;
                pd->dwSequence  = This->buffer[n].dwSequence;
                if (dodsize >= sizeof(DIDEVICEOBJECTDATA))
                  pd->uAppData = This->buffer[n].uAppData;
              }
            i++;
          }

        *entries = i;

        if (This->overflow)
          ret = DI_BUFFEROVERFLOW;

        if (!(flags & DIGDD_PEEK))
          {
            /* Empty buffer */
            This->count -= i;
            This->start = (This->start + i) % This->buffersize;
            This->overflow = FALSE;
          }

        LeaveCriticalSection(&(This->crit));

	TRACE("Returning %ld events queued\n", *entries);

        return ret;
}

HRESULT WINAPI SysKeyboardAImpl_SetDataFormat(
	LPDIRECTINPUTDEVICE8A iface,LPCDIDATAFORMAT df
) {
  ICOM_THIS(SysKeyboardAImpl,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("\tdf.rgodf[%d].guid %s\n",i,debugstr_guid(df->rgodf[i].pguid));
    TRACE("\tdf.rgodf[%d].dwOfs %ld\n",i,df->rgodf[i].dwOfs);
    TRACE("\tdwType 0x%02x,dwInstance %d\n",DIDFT_GETTYPE(df->rgodf[i].dwType),DIDFT_GETINSTANCE(df->rgodf[i].dwType));
    TRACE("\tdf.rgodf[%d].dwFlags 0x%08lx\n",i,df->rgodf[i].dwFlags);
  }

  /* Store the new data format */
  if (This->df != &(Wine_InternalKeyboardFormat)) {
    HeapFree(GetProcessHeap(), 0, This->df->rgodf);
    HeapFree(GetProcessHeap(), 0, This->df);
  }
  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 */
  if (This->wine_df) {
    if (This->wine_df->dt) {
      HeapFree(GetProcessHeap(), 0, This->wine_df->dt);
    }
    HeapFree(GetProcessHeap(), 0, This->wine_df);
  }
  This->wine_df = create_DataFormat(&(Wine_InternalKeyboardFormat), df, This->offset_array, This->dinput->ver);

  return 0;
}

static void set_name(char *dest, BYTE dik_code) {
  /* the high bit 0x80 of the DIK code needs to be mapped to bit 24 (extended keys)
   * the rest of the bits get mapped to bits 16-23 */
  DWORD scancode = (dik_code & ~0x80) << 16;
  if( dik_code & 0x80 ) scancode |= (1<<24);
  GetKeyNameTextA(scancode, dest, MAX_PATH-1);
}

static HRESULT WINAPI SysKeyboardAImpl_EnumObjects(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIENUMDEVICEOBJECTSCALLBACKA lpCallback,
	LPVOID lpvRef,
	DWORD dwFlags)
{
    ICOM_THIS(SysKeyboardAImpl,iface);
    DIDEVICEOBJECTINSTANCEA ddoi;
    int i;

    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 */
    memset(&ddoi, 0, sizeof(ddoi));
    ddoi.dwSize = FIELD_OFFSET(DIDEVICEOBJECTINSTANCEA, dwFFMaxForce);

    for (i = 0; i < WINE_INTERNAL_NUM_KEYS; i++) {
	ddoi.guidType = *Wine_InternalKeyboardObjectFormat[i].pguid;
	ddoi.dwOfs    =  Wine_InternalKeyboardObjectFormat[i].dwOfs;
	ddoi.dwType   =  Wine_InternalKeyboardObjectFormat[i].dwType;
	ddoi.dwFlags  =  Wine_InternalKeyboardObjectFormat[i].dwFlags;
	set_name(ddoi.tszName, i);
	_dump_OBJECTINSTANCEA(&ddoi);
	if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
    }

    return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_Unacquire(LPDIRECTINPUTDEVICE8A iface);

static HRESULT WINAPI SysKeyboardAImpl_Acquire(LPDIRECTINPUTDEVICE8A iface)
{
	ICOM_THIS(SysKeyboardAImpl,iface);

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

	if (This->lpVtbl != &SysKeyboardAvt) {
	  /* City of Heroes uses the wrong vtbl to acquire for some reason.
	   * Just dispatch to the right vtbl here, then. */
	  return IDirectInputDevice8_Acquire(iface);
	}

        if (This->acquired)
          return S_FALSE;

        This->acquired = 1;

        if (current != NULL)
          {
            FIXME("Not more than one keyboard can be acquired at the same time.\n");
            SysKeyboardAImpl_Unacquire(iface);
          }

        current = This;

        if (This->buffersize > 0)
          {
            This->buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                     This->buffersize * sizeof(*(This->buffer)));
            This->start = 0;
            This->count = 0;
            This->overflow = FALSE;
          }
        else
          This->buffer = NULL;

	return DI_OK;
}

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

        if (This->acquired == 0)
          return DI_NOEFFECT;

        if (current == This)
          current = NULL;
        else
          ERR("this != current\n");

        This->acquired = 0;

        if (This->buffersize >= 0)
          {
            HeapFree(GetProcessHeap(), 0, This->buffer);
            This->buffer = NULL;
          }

	return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_SetEventNotification(LPDIRECTINPUTDEVICE8A iface,
							    HANDLE hnd) {
  ICOM_THIS(SysKeyboardAImpl,iface);

  TRACE("(this=%p,0x%08lx)\n",This,(DWORD)hnd);

  This->hEvent = hnd;
  return DI_OK;
}

/******************************************************************************
  *     GetCapabilities : get the device capablitites
  */
static HRESULT WINAPI SysKeyboardAImpl_GetCapabilities(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVCAPS lpDIDevCaps)
{
  ICOM_THIS(SysKeyboardAImpl,iface);

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

  if (lpDIDevCaps->dwSize == sizeof(DIDEVCAPS)) {
    lpDIDevCaps->dwFlags = DIDC_ATTACHED;
    lpDIDevCaps->dwDevType = DIDEVTYPE_KEYBOARD;
    lpDIDevCaps->dwAxes = 0;
    lpDIDevCaps->dwButtons = WINE_INTERNAL_NUM_KEYS;
    lpDIDevCaps->dwPOVs = 0;
    lpDIDevCaps->dwFFSamplePeriod = 0;
    lpDIDevCaps->dwFFMinTimeResolution = 0;
    lpDIDevCaps->dwFirmwareRevision = 100;
    lpDIDevCaps->dwHardwareRevision = 100;
    lpDIDevCaps->dwFFDriverVersion = 0;
  } else {
    /* DirectX 3.0 */
    FIXME("DirectX 3.0 not supported....\n");
  }

  return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_GetObjectInfo(LPDIRECTINPUTDEVICE8A iface,
                                                     LPDIDEVICEOBJECTINSTANCEA pdidoi,
						     DWORD dwObj,
						     DWORD dwHow)
{
  ICOM_THIS(SysKeyboardAImpl,iface);
  int instance;

  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:
      instance = DIDFT_GETINSTANCE(dwObj);
      pdidoi->guidType = *Wine_InternalKeyboardObjectFormat[instance].pguid;
      pdidoi->dwOfs    =  Wine_InternalKeyboardObjectFormat[instance].dwOfs;
      pdidoi->dwType   =  Wine_InternalKeyboardObjectFormat[instance].dwType;
      pdidoi->dwFlags  =  Wine_InternalKeyboardObjectFormat[instance].dwFlags;
      set_name(pdidoi->tszName, instance);
      break;
    case DIPH_BYOFFSET:
      {
	int i;
	BOOL found = FALSE;
	LPDIDATAFORMAT df = This->df;
	/* find the offset the app is looking for, then fill in the data */
	for( i=0; i < df->dwNumObjs; i++ ) {
	  if( df->rgodf[i].dwOfs == dwObj ) {
	    pdidoi->guidType = *df->rgodf[i].pguid;
	    pdidoi->dwOfs    = df->rgodf[i].dwOfs;
	    pdidoi->dwType   = df->rgodf[i].dwType;
	    pdidoi->dwFlags  = df->rgodf[i].dwFlags;
	    set_name(pdidoi->tszName, DIDFT_GETINSTANCE(pdidoi->dwType));
	    found = TRUE;
	    break;
	  }
	}
	if( !found ) return DIERR_OBJECTNOTFOUND;
	break;
      }
    case DIPH_DEVICE:
    default:
      FIXME("dwHow = 0x%08lx - unsupported!\n", dwHow);
  }
  
  if (TRACE_ON(dinput))
    _dump_OBJECTINSTANCEA(pdidoi);

  return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_GetDeviceInfo(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVICEINSTANCEA lpddi)
{
	ICOM_THIS(SysKeyboardAImpl,iface);

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

	lpddi->guidInstance = GUID_SysKeyboard;/* DInput's GUID */
	lpddi->guidProduct = DInput_Wine_Keyboard_GUID; /* Vendor's GUID */
	if (HIBYTE(This->dinput->ver) >= 8)
	  lpddi->dwDevType = DI8DEVTYPE_KEYBOARD | (DI8DEVTYPEKEYBOARD_UNKNOWN << 8);
	else
	  lpddi->dwDevType = DIDEVTYPE_KEYBOARD | (DIDEVTYPEKEYBOARD_UNKNOWN << 8);
	strcpy(lpddi->tszInstanceName, "Keyboard");
	strcpy(lpddi->tszProductName, "Wine Keyboard");

	return DI_OK;
}

static HRESULT WINAPI SysKeyboardAImpl_BuildActionMap(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIACTIONFORMATA lpdiaf,
	LPCSTR lpszUserName,
	DWORD dwFlags)
{
	LPDIACTIONA act;
	HRESULT hr = DI_NOEFFECT;
	DWORD c;

	TRACE("(this=%p,%p,%s,0x%lx)\n",
	      iface, lpdiaf, debugstr_a(lpszUserName), dwFlags);
	act = lpdiaf->rgoAction;
	for (c=0; c<lpdiaf->dwNumActions; c++,act++) {
	  if ((act->dwSemantic & 0xffffff00) == 0x81000400) {
	    act->dwObjID = act->dwSemantic & 0xff;
	    act->dwHow = DIAH_DEFAULT;
	    hr = DI_OK;
	  } else {
	    act->dwObjID = 0;
	    act->dwHow = DIAH_UNMAPPED;
	  }
	}

	return hr;
}

static HRESULT WINAPI SysKeyboardAImpl_SetActionMap(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIACTIONFORMATA lpdiaf,
	LPCSTR lpszUserName,
	DWORD dwFlags)
{
	ICOM_THIS(SysKeyboardAImpl,iface);
	LPDIACTIONA act;
	HRESULT hr = DI_NOEFFECT;
	DWORD c;
	int i;

	TRACE("(this=%p,%p,%s,0x%lx)\n",
	      iface, lpdiaf, debugstr_a(lpszUserName), dwFlags);

	if (This->acquired)
	  return DIERR_INVALIDPARAM;

	/* install default data format */
	if (This->df != &(Wine_InternalKeyboardFormat)) {
	  HeapFree(GetProcessHeap(), 0, This->df->rgodf);
	  HeapFree(GetProcessHeap(), 0, This->df);
	}
	This->df = &(Wine_InternalKeyboardFormat);
	if (This->wine_df) {
	  if (This->wine_df->dt) {
	    HeapFree(GetProcessHeap(), 0, This->wine_df->dt);
	  }
	  HeapFree(GetProcessHeap(), 0, This->wine_df);
	}
	This->wine_df = (DataFormat *) HeapAlloc(GetProcessHeap(), 0, sizeof(DataFormat));
	This->wine_df->size = 0;
	This->wine_df->internal_format_size = Wine_InternalKeyboardFormat.dwSize;
	This->wine_df->dt = NULL;

	/* clear all data format offsets */
	for( i=0; i < WINE_INTERNAL_NUM_KEYS; i++ ) {
	  This->offset_array[i] = -1;
	}

	/* install new action map */
	act = lpdiaf->rgoAction;
	for (c=0; c<lpdiaf->dwNumActions; c++,act++) {
	  if (act->dwHow == DIAH_UNMAPPED ||
	      (act->dwHow & DIAH_ERROR)) continue;
	  This->offset_array[act->dwObjID] = Wine_InternalKeyboardObjectFormat[act->dwObjID].dwOfs;
	  This->appdata_array[act->dwObjID] = act->uAppData;
	}

	/* set new buffer size */
	This->buffersize = lpdiaf->dwBufferSize;

	return hr;
}

static ICOM_VTABLE(IDirectInputDevice8A) SysKeyboardAvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectInputDevice2AImpl_QueryInterface,
	IDirectInputDevice2AImpl_AddRef,
	SysKeyboardAImpl_Release,
	SysKeyboardAImpl_GetCapabilities,
	SysKeyboardAImpl_EnumObjects,
	SysKeyboardAImpl_GetProperty,
	SysKeyboardAImpl_SetProperty,
	SysKeyboardAImpl_Acquire,
	SysKeyboardAImpl_Unacquire,
	SysKeyboardAImpl_GetDeviceState,
	SysKeyboardAImpl_GetDeviceData,
	SysKeyboardAImpl_SetDataFormat,
	SysKeyboardAImpl_SetEventNotification,
	IDirectInputDevice2AImpl_SetCooperativeLevel,
	SysKeyboardAImpl_GetObjectInfo, 
	SysKeyboardAImpl_GetDeviceInfo,
	IDirectInputDevice2AImpl_RunControlPanel,
	IDirectInputDevice2AImpl_Initialize,
	IDirectInputDevice2AImpl_CreateEffect,
	IDirectInputDevice2AImpl_EnumEffects,
	IDirectInputDevice2AImpl_GetEffectInfo,
	IDirectInputDevice2AImpl_GetForceFeedbackState,
	IDirectInputDevice2AImpl_SendForceFeedbackCommand,
	IDirectInputDevice2AImpl_EnumCreatedEffectObjects,
	IDirectInputDevice2AImpl_Escape,
	IDirectInputDevice2AImpl_Poll,
	IDirectInputDevice2AImpl_SendDeviceData,
	IDirectInputDevice7AImpl_EnumEffectsInFile,
	IDirectInputDevice7AImpl_WriteEffectToFile,
	SysKeyboardAImpl_BuildActionMap,
	SysKeyboardAImpl_SetActionMap,
	IDirectInputDevice8AImpl_GetImageInfo
};
