//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef INITGUID
#define INITGUID
#endif

#include "platform/platform.h"
#include "platformWin32/winDInputDevice.h"

#include "math/mMath.h"
#include "console/console.h"
#include "core/strings/unicode.h"
#include "windowManager/platformWindowMgr.h"

// Static class data:
LPDIRECTINPUT8 DInputDevice::smDInputInterface;
U8    DInputDevice::smDeviceCount[ NUM_INPUT_DEVICE_TYPES ];
bool  DInputDevice::smInitialized = false;

#ifdef LOG_INPUT
const char* getKeyName( U16 key );
#endif

//------------------------------------------------------------------------------
DInputDevice::DInputDevice( const DIDEVICEINSTANCE* dii )
{
   mDeviceInstance   = *dii;
   mDevice           = NULL;
   mAcquired         = false;
   mNeedSync         = false;
   mObjInstance      = NULL;
   mObjFormat        = NULL;
   mObjInfo          = NULL;
   mObjBuffer1       = NULL;
   mObjBuffer2       = NULL;
   mPrevObjBuffer    = NULL;

   mForceFeedbackEffect    = NULL;
   mNumForceFeedbackAxes   = 0;
   mForceFeedbackAxes[0]   = 0;
   mForceFeedbackAxes[1]   = 0;

   const char* deviceTypeName = "unknown";
   U8 deviceType = UnknownDeviceType;

   switch ( GET_DIDEVICE_TYPE( mDeviceInstance.dwDevType ) )
   {
      // [rene, 12/09/2008] why do we turn a gamepad into a joystick here?
	  
      case DI8DEVTYPE_DRIVING:
      case DI8DEVTYPE_GAMEPAD:
      case DI8DEVTYPE_JOYSTICK:
         deviceTypeName    = "joystick";
         deviceType        = JoystickDeviceType;
         break;

      case DI8DEVTYPE_KEYBOARD:
         deviceTypeName    = "keyboard";
         deviceType        = KeyboardDeviceType;
         break;

      case DI8DEVTYPE_MOUSE:
         deviceTypeName    = "mouse";
         deviceType        = MouseDeviceType;
         break;
   }

   mDeviceType    = deviceType;
   mDeviceID      = smDeviceCount[ deviceType ] ++;

   dSprintf( mName, 29, "%s%d", deviceTypeName, mDeviceID );
}

//------------------------------------------------------------------------------
DInputDevice::~DInputDevice()
{
   destroy();
}

//------------------------------------------------------------------------------
void DInputDevice::init()
{
   // Reset all of the static variables:
   smDInputInterface    = NULL;
   dMemset( smDeviceCount, 0, sizeof( smDeviceCount ) );
}

//------------------------------------------------------------------------------
bool DInputDevice::create()
{
   HRESULT result;

   if ( smDInputInterface )
   {
      result = smDInputInterface->CreateDevice( mDeviceInstance.guidInstance, &mDevice, NULL );
      if ( result == DI_OK )
      {
		 mDeviceCaps.dwSize = sizeof( DIDEVCAPS );
         if ( FAILED( mDevice->GetCapabilities( &mDeviceCaps ) ) )
         {
            Con::errorf( "  Failed to get the capabilities of the %s input device.", mName );
#ifdef LOG_INPUT
            Input::log( "Failed to get the capabilities of &s!\n", mName );
#endif
            return false;
         }

#ifdef LOG_INPUT
         Input::log( "%s detected, created as %s (%s).\n", mDeviceInstance.tszProductName, mName, ( isPolled() ? "polled" : "asynchronous" ) );
#endif

         if ( enumerateObjects() )
         {
			   // Set the device's data format:
            DIDATAFORMAT dataFormat;
            dMemset( &dataFormat, 0, sizeof( DIDATAFORMAT ) );
            dataFormat.dwSize       = sizeof( DIDATAFORMAT );
            dataFormat.dwObjSize    = sizeof( DIOBJECTDATAFORMAT );
			   dataFormat.dwFlags      = ( mDeviceType == MouseDeviceType ) ? DIDF_RELAXIS : DIDF_ABSAXIS;
            dataFormat.dwDataSize   = mObjBufferSize;
            dataFormat.dwNumObjs    = mObjCount;
            dataFormat.rgodf        = mObjFormat;		
			
            result = mDevice->SetDataFormat( &dataFormat );
            if ( FAILED( result ) )
            {				
               Con::errorf( "  Failed to set the data format for the %s input device.", mName );
#ifdef LOG_INPUT
               Input::log( "Failed to set the data format for %s!\n", mName );
#endif
			   return false;
            }

            // Set up the data buffer for buffered input:
            DIPROPDWORD prop;
            prop.diph.dwSize        = sizeof( DIPROPDWORD );
            prop.diph.dwHeaderSize  = sizeof( DIPROPHEADER );
            prop.diph.dwObj         = 0;
            prop.diph.dwHow         = DIPH_DEVICE;
            if ( isPolled() )
               prop.dwData = mObjBufferSize;
            else
               prop.dwData = QUEUED_BUFFER_SIZE;

            result = mDevice->SetProperty( DIPROP_BUFFERSIZE, &prop.diph );
            if ( FAILED( result ) )
            {
               Con::errorf( "  Failed to set the buffer size property for the %s input device.", mName );
#ifdef LOG_INPUT
               Input::log( "Failed to set the buffer size property for %s!\n", mName );
#endif
               return false;
            }

            // If this device is a mouse, set it to relative axis mode:
            if ( mDeviceType == MouseDeviceType )
            {
               prop.diph.dwObj   = 0;
               prop.diph.dwHow   = DIPH_DEVICE;
               prop.dwData       = DIPROPAXISMODE_REL;

               result = mDevice->SetProperty( DIPROP_AXISMODE, &prop.diph );
               if ( FAILED( result ) )
               {
                  Con::errorf( "  Failed to set relative axis mode for the %s input device.", mName );
#ifdef LOG_INPUT
                  Input::log( "Failed to set relative axis mode for %s!\n", mName );
#endif
                  return false;
               }
            }
         }
      }
      else
      {
#ifdef LOG_INPUT
         switch ( result )
         {
            case DIERR_DEVICENOTREG:
               Input::log( "CreateDevice failed -- The device or device instance is not registered with DirectInput.\n" );
               break;

            case DIERR_INVALIDPARAM:
               Input::log( "CreateDevice failed -- Invalid parameter.\n" );
               break;

            case DIERR_NOINTERFACE:
               Input::log( "CreateDevice failed -- The specified interface is not supported by the object.\n" );
               break;

            case DIERR_OUTOFMEMORY:
               Input::log( "CreateDevice failed -- Out of memory.\n" );
               break;

            default:
               Input::log( "CreateDevice failed -- Unknown error.\n" );
               break;
         };
#endif // LOG_INPUT
         Con::printf( "  CreateDevice failed for the %s input device!", mName );
         return false;
      }
   }

   Con::printf( "   %s input device created.", mName );
   return true;
}

//------------------------------------------------------------------------------
void DInputDevice::destroy()
{
   if ( mDevice )
   {
      unacquire();

      // Tear down our forcefeedback.
      if (mForceFeedbackEffect)
      {
         mForceFeedbackEffect->Release();
         mForceFeedbackEffect = NULL;
         mNumForceFeedbackAxes = 0;
#ifdef LOG_INPUT
         Input::log("DInputDevice::destroy - releasing constant force feeback effect\n"); 
#endif
      }

      mDevice->Release();
      mDevice = NULL;

      delete [] mObjInstance;
      delete [] mObjFormat;
      delete [] mObjInfo;
      delete [] mObjBuffer1;
      delete [] mObjBuffer2;

      mObjInstance   = NULL;
      mObjFormat     = NULL;
      mObjInfo       = NULL;
      mObjBuffer1    = NULL;
      mObjBuffer2    = NULL;
      mPrevObjBuffer = NULL;
      mName[0]       = 0;
   }
}

//------------------------------------------------------------------------------
bool DInputDevice::acquire()
{
   if ( mDevice )
   {
      if ( mAcquired )
         return( true );

      bool result = false;
      // Set the cooperative level:
      // (do this here so that we have a valid app window)
      DWORD coopLevel = DISCL_BACKGROUND;
      if ( mDeviceType == JoystickDeviceType
// #ifndef DEBUG
//         || mDeviceType == MouseDeviceType
// #endif
         )
         // Exclusive access is required in order to perform force feedback
         coopLevel = DISCL_EXCLUSIVE | DISCL_FOREGROUND;
      else
         coopLevel |= DISCL_NONEXCLUSIVE;

      result = mDevice->SetCooperativeLevel( getWin32WindowHandle(), coopLevel );
      if ( FAILED( result ) )
      {
         Con::errorf( "Failed to set the cooperative level for the %s input device.", mName );
#ifdef LOG_INPUT
         Input::log( "Failed to set the cooperative level for %s!\n", mName );
#endif
         return false;
      }

      // Enumerate joystick axes to enable force feedback
      if ( NULL == mForceFeedbackEffect && JoystickDeviceType == mDeviceType)
      {
         // Since we will be playing force feedback effects, we should disable the auto-centering spring.
         DIPROPDWORD dipdw;
         dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
         dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
         dipdw.diph.dwObj        = 0;
         dipdw.diph.dwHow        = DIPH_DEVICE;
         dipdw.dwData            = FALSE;

         if( FAILED( result = mDevice->SetProperty( DIPROP_AUTOCENTER, &dipdw.diph ) ) )
            return false;
      }

      S32 code = mDevice->Acquire();
      result = SUCCEEDED( code );
      if ( result )
      {
         Con::printf( "%s input device acquired.", mName );
#ifdef LOG_INPUT
         Input::log( "%s acquired.\n", mName );
#endif
         mAcquired = true;

         // If we were previously playing a force feedback effect, before
         // losing acquisition, we do not automatically restart it. This is
         // where you could call mForceFeedbackEffect->Start( INFINITE, 0 ); 
         // if you want that behavior.

         // Update all of the key states:
         if ( !isPolled() )
            mNeedSync = true;
      }
      else
      {
         const char* reason;
         switch ( code )
         {
            case DIERR_INVALIDPARAM:      reason = "Invalid parameter"  ; break;
            case DIERR_NOTINITIALIZED:    reason = "Not initialized"; break;
            case DIERR_OTHERAPPHASPRIO:   reason = "Other app has priority"; break;
            case S_FALSE:                 reason = "Already acquired"; break;
            default:                      reason = "Unknown error"; break;
         }
         Con::warnf( "%s input device NOT acquired: %s", mName, reason );
#ifdef LOG_INPUT
         Input::log( "Failed to acquire %s: %s\n", mName, reason );
#endif
      }

      return( result );
   }

   return( false );
}

//------------------------------------------------------------------------------
bool DInputDevice::unacquire()
{
   if ( mDevice )
   {
      if ( !mAcquired )
         return( true );

      bool result = false;
      result = SUCCEEDED( mDevice->Unacquire() );
      if ( result )
      {
         Con::printf( "%s input device unacquired.", mName );
#ifdef LOG_INPUT
         Input::log( "%s unacquired.\n", mName );
#endif
         mAcquired = false;
      }
      else
      {
         Con::warnf( ConsoleLogEntry::General, "%s input device NOT unacquired.", mName );
#ifdef LOG_INPUT
         Input::log( "Failed to unacquire %s!\n", mName );
#endif
      }

      return( result );
   }

   return( false );
}

//------------------------------------------------------------------------------
BOOL CALLBACK DInputDevice::EnumObjectsProc( const DIDEVICEOBJECTINSTANCE* doi, LPVOID pvRef )
{
   // Don't enumerate unknown types:
   if ( doi->guidType == GUID_Unknown )
      return (DIENUM_CONTINUE);

   // Reduce a couple pointers:
   DInputDevice* diDevice = (DInputDevice*) pvRef;
   DIDEVICEOBJECTINSTANCE* objInstance = &diDevice->mObjInstance[diDevice->mObjEnumCount];
   DIOBJECTDATAFORMAT*     objFormat   = &diDevice->mObjFormat[diDevice->mObjEnumCount];

   // Fill in the object instance structure:
   *objInstance = *doi;

   // DWORD objects must be DWORD aligned:
   if ( !(objInstance->dwType & DIDFT_BUTTON ) )
      diDevice->mObjBufferOfs = ( diDevice->mObjBufferOfs + 3 ) & ~3;

   objInstance->dwOfs = diDevice->mObjBufferOfs;

   // Fill in the object data format structure:
   objFormat->pguid  = &objInstance->guidType;
   objFormat->dwType = objInstance->dwType;
   objFormat->dwFlags= 0;
   objFormat->dwOfs  = diDevice->mObjBufferOfs;

   // Advance the enumeration counters:
   if ( objFormat->dwType & DIDFT_BUTTON )
      diDevice->mObjBufferOfs += SIZEOF_BUTTON;
   else
      diDevice->mObjBufferOfs += SIZEOF_AXIS;
   diDevice->mObjEnumCount++;

   return (DIENUM_CONTINUE);
}

//------------------------------------------------------------------------------
bool DInputDevice::enumerateObjects()
{
   if ( !mDevice )
      return false;

   // Calculate the needed buffer sizes and allocate them:
   mObjCount = ( mDeviceCaps.dwAxes + mDeviceCaps.dwButtons + mDeviceCaps.dwPOVs );
   mObjBufferSize = mObjCount * sizeof( DWORD );

   mObjInstance   = new DIDEVICEOBJECTINSTANCE[mObjCount];
   mObjFormat     = new DIOBJECTDATAFORMAT[mObjCount];
   mObjInfo       = new ObjInfo[mObjCount];

   if ( isPolled() )
   {
      mObjBuffer1 = new U8[mObjBufferSize];
      dMemset( mObjBuffer1, 0, mObjBufferSize );
      mObjBuffer2 = new U8[mObjBufferSize];
      dMemset( mObjBuffer2, 0, mObjBufferSize );
   }
   mObjEnumCount = 0;
   mObjBufferOfs = 0;

   // We are about to enumerate, clear the axes we claim to know about
   mNumForceFeedbackAxes = 0;

   // Enumerate all of the 'objects' detected on the device:
   if ( FAILED( mDevice->EnumObjects( EnumObjectsProc, this, DIDFT_ALL ) ) )
      return false;

   // We only supports one or two axis joysticks
   if( mNumForceFeedbackAxes > 2 )
      mNumForceFeedbackAxes = 2;

   // if we enumerated fewer objects than are supposedly available, reset the
   // object count
   if (mObjEnumCount < mObjCount)
	   mObjCount = mObjEnumCount;

   mObjBufferSize = ( mObjBufferSize + 3 ) & ~3;   // Fill in the actual size to nearest DWORD

   U32 buttonCount   = 0;
   U32 povCount      = 0;
   U32 keyCount      = 0;
   U32 unknownCount  = 0;

   // Fill in the device object's info structure:
   for ( U32 i = 0; i < mObjCount; i++ )
   {
      if ( mObjInstance[i].guidType == GUID_Button )
      {
         mObjInfo[i].mType = SI_BUTTON;
         mObjInfo[i].mInst = (InputObjectInstances)(KEY_BUTTON0 + buttonCount++);
      }
      else if ( mObjInstance[i].guidType == GUID_POV )
      {
         // This is actually intentional - the POV handling code lower down
         // takes the instance number and converts everything to button events.
         mObjInfo[i].mType = SI_POV;
         mObjInfo[i].mInst = (InputObjectInstances)povCount++;
      }
      else if ( mObjInstance[i].guidType == GUID_XAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_XAXIS;

         if (mObjInstance[i].dwFFMaxForce > 0)
            mForceFeedbackAxes[mNumForceFeedbackAxes++] = mObjInstance[i].dwOfs;
      }
      else if ( mObjInstance[i].guidType == GUID_YAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_YAXIS;

         if (mObjInstance[i].dwFFMaxForce > 0)
            mForceFeedbackAxes[mNumForceFeedbackAxes++] = mObjInstance[i].dwOfs;
      }
      else if ( mObjInstance[i].guidType == GUID_ZAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_ZAXIS;
      }
      else if ( mObjInstance[i].guidType == GUID_RxAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_RXAXIS;
      }
      else if ( mObjInstance[i].guidType == GUID_RyAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_RYAXIS;
      }
      else if ( mObjInstance[i].guidType == GUID_RzAxis )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_RZAXIS;
      }
      else if ( mObjInstance[i].guidType == GUID_Slider )
      {
         mObjInfo[i].mType = SI_AXIS;
         mObjInfo[i].mInst = SI_SLIDER;
      }
      else if ( mObjInstance[i].guidType == GUID_Key )
      {
         mObjInfo[i].mType = SI_KEY;
         mObjInfo[i].mInst = DIK_to_Key( DIDFT_GETINSTANCE( mObjFormat[i].dwType ) );
         keyCount++;
      }
      else
      {
         mObjInfo[i].mType = SI_UNKNOWN;
         mObjInfo[i].mInst = (InputObjectInstances)unknownCount++;
      }

      // Set the device object's min and max values:
      if ( mObjInstance[i].guidType == GUID_Button
        || mObjInstance[i].guidType == GUID_Key
        || mObjInstance[i].guidType == GUID_POV )
      {
         mObjInfo[i].mMin = DIPROPRANGE_NOMIN;
         mObjInfo[i].mMax = DIPROPRANGE_NOMAX;
      }
      else
      {
         // This is an axis or a slider, so find out its range:
         DIPROPRANGE pr;
         pr.diph.dwSize       = sizeof( pr );
         pr.diph.dwHeaderSize = sizeof( pr.diph );
         pr.diph.dwHow        = DIPH_BYID;
         pr.diph.dwObj        = mObjFormat[i].dwType;

         if ( SUCCEEDED( mDevice->GetProperty( DIPROP_RANGE, &pr.diph ) ) )
         {
            mObjInfo[i].mMin = pr.lMin;
            mObjInfo[i].mMax = pr.lMax;
         }
         else
         {
            mObjInfo[i].mMin = DIPROPRANGE_NOMIN;
            mObjInfo[i].mMax = DIPROPRANGE_NOMAX;
         }
      }
   }

#ifdef LOG_INPUT
   Input::log( "  %d total objects detected.\n", mObjCount );
   if ( buttonCount )
      Input::log( "  %d buttons.\n", buttonCount );
   if ( povCount )
      Input::log( "  %d POVs.\n", povCount );

   if ( keyCount )
      Input::log( "  %d keys.\n", keyCount );
   if ( unknownCount )
      Input::log( "  %d unknown objects.\n", unknownCount );
   Input::log( "\n" );
#endif

   return true;
}

//------------------------------------------------------------------------------
const char* DInputDevice::getName()
{
#ifdef UNICODE
   static UTF8 buf[512];
   convertUTF16toUTF8(mDeviceInstance.tszInstanceName, buf);
   return (const char *)buf;
#else
   return mDeviceInstance.tszInstanceName;
#endif
}

//------------------------------------------------------------------------------
const char* DInputDevice::getProductName()
{
#ifdef UNICODE
   static UTF8 buf[512];
   convertUTF16toUTF8(mDeviceInstance.tszProductName, buf);
   return (const char *)buf;
#else
   return mDeviceInstance.tszProductName;
#endif
}

//------------------------------------------------------------------------------
bool DInputDevice::process()
{
   if ( mAcquired )
   {
      if ( isPolled() )
         return processImmediate();
      else
         return processAsync();
   }

   return false;
}

//------------------------------------------------------------------------------
bool DInputDevice::processAsync()
{
   DIDEVICEOBJECTDATA eventBuffer[QUEUED_BUFFER_SIZE];
   DWORD numEvents = QUEUED_BUFFER_SIZE;
   HRESULT result;

   if ( !mDevice )
      return false;

   do
   {
      result = mDevice->GetDeviceData( sizeof( DIDEVICEOBJECTDATA ), eventBuffer, &numEvents, 0 );
	
      if ( !SUCCEEDED( result ) )
      {
         switch ( result )
         {
            case DIERR_INPUTLOST:
               // Data stream was interrupted, so try to reacquire the device:
               mAcquired = false;
               acquire();
               break;

            case DIERR_INVALIDPARAM:
               Con::errorf( "DInputDevice::processAsync -- Invalid parameter passed to GetDeviceData of the %s input device!", mName );
#ifdef LOG_INPUT
               Input::log( "Invalid parameter passed to GetDeviceData for %s!\n", mName );
#endif
               break;

            case DIERR_NOTACQUIRED:
               // We can't get the device, so quit:
               mAcquired = false;
               // Don't error out - this is actually a natural occurrence...
               //Con::errorf( "DInputDevice::processAsync -- GetDeviceData called when %s input device is not acquired!", mName );
#ifdef LOG_INPUT
               Input::log( "GetDeviceData called when %s is not acquired!\n", mName );
#endif
               break;
         }

         return false;
      }

      // We have buffered input, so act on it:
      for ( DWORD i = 0; i < numEvents; i++ )
         buildEvent( findObjInstance( eventBuffer[i].dwOfs ), eventBuffer[i].dwData, eventBuffer[i].dwData );

      // Check for buffer overflow:
      if ( result == DI_BUFFEROVERFLOW )
	  {
         // This is a problem, but we can keep going...
         Con::errorf( "DInputDevice::processAsync -- %s input device's event buffer overflowed!", mName );
#ifdef LOG_INPUT
         Input::log( "%s event buffer overflowed!\n", mName );
#endif
         mNeedSync = true; // Let it know to resync next time through...
      }
   }
   while ( numEvents );

   return true;
}

//------------------------------------------------------------------------------
bool DInputDevice::processImmediate()
{
   if ( !mDevice )
      return false;

   mDevice->Poll();

   U8* buffer = ( mPrevObjBuffer == mObjBuffer1 ) ? mObjBuffer2 : mObjBuffer1;
   HRESULT result = mDevice->GetDeviceState( mObjBufferSize, buffer );
   if ( !SUCCEEDED( result ) )
   {
      switch ( result )
      {
         case DIERR_INPUTLOST:
            // Data stream was interrupted, so try to reacquire the device:
            mAcquired = false;
            acquire();
            break;

         case DIERR_INVALIDPARAM:
            Con::errorf( "DInputDevice::processPolled -- invalid parameter passed to GetDeviceState on %s input device!", mName );
#ifdef LOG_INPUT
            Input::log( "Invalid parameter passed to GetDeviceState on %s.\n", mName );
#endif
            break;

         case DIERR_NOTACQUIRED:
            Con::errorf( "DInputDevice::processPolled -- GetDeviceState called when %s input device is not acquired!", mName );
#ifdef LOG_INPUT
            Input::log( "GetDeviceState called when %s is not acquired!\n", mName );
#endif
            break;

         case E_PENDING:
            Con::warnf( "DInputDevice::processPolled -- Data not yet available for the %s input device!", mName );
#ifdef LOG_INPUT
            Input::log( "Data pending for %s.", mName );
#endif
            break;
      }

      return false;
   }

   // Loop through all of the objects and produce events where
   // the states have changed:

   // (oldData = 0 prevents a crashing bug in Torque. There is a case where 
   //  Torque accessed oldData without it ever being set.)
   S32 newData, oldData = 0;                          
   for ( DWORD i = 0; i < mObjCount; i++ )
   {
      if ( mObjFormat[i].dwType & DIDFT_BUTTON )
      {
         if ( mPrevObjBuffer )
         {
            newData = *( (U8*) ( buffer + mObjFormat[i].dwOfs ) );
            oldData = *( (U8*) ( mPrevObjBuffer + mObjFormat[i].dwOfs ) );
            if ( newData == oldData )
               continue;
         }
         else
            continue;
      }
      else if ( mObjFormat[i].dwType & DIDFT_POV )
      {
         if ( mPrevObjBuffer )
         {
            newData = *( (S32*) ( buffer + mObjFormat[i].dwOfs ) );
            oldData = *( (S32*) ( mPrevObjBuffer + mObjFormat[i].dwOfs ) );
            if ( LOWORD( newData ) == LOWORD( oldData ) )
               continue;
         }
         else
            continue;
      }
      else
      {
         // report normal axes every time through the loop:
         newData = *( (S32*) ( buffer + mObjFormat[i].dwOfs ) );
      }

      // Build an event:
      buildEvent( i, newData, oldData );
   }
   mPrevObjBuffer = buffer;

   return true;
}

//------------------------------------------------------------------------------
DWORD DInputDevice::findObjInstance( DWORD offset )
{
   DIDEVICEOBJECTINSTANCE *inst = mObjInstance;
   for ( U32 i = 0; i < mObjCount; i++, inst++ )
   {
      if ( inst->dwOfs == offset )
         return i;
   }

   AssertFatal( false, "DInputDevice::findObjInstance -- failed to locate object instance." );
   return 0;
}

//------------------------------------------------------------------------------
enum Win32POVDirBits
{
   POV_up      = 1 << 0,
   POV_right   = 1 << 1,
   POV_down    = 1 << 2,
   POV_left    = 1 << 3,
};

enum Win32POVDirsInQuadrant
{
   POVq_center    = 0,
   POVq_up        = POV_up,
   POVq_upright   = POV_up | POV_right,
   POVq_right     = POV_right,
   POVq_downright = POV_down | POV_right,
   POVq_down      = POV_down,
   POVq_downleft  = POV_down | POV_left,
   POVq_left      = POV_left,
   POVq_upleft    = POV_up | POV_left,
};

static const U32 Win32POVQuadrantMap[] = 
{
   POVq_up,    POVq_upright, 
   POVq_right, POVq_downright, 
   POVq_down,  POVq_downleft,
   POVq_left,  POVq_upleft
};

static U32 _Win32GetPOVDirs(U32 data)
{
   U32 quadrant = (data / 4500) % 8;
   U32 dirs = (data == 0xffff) ? POVq_center : Win32POVQuadrantMap[quadrant];
   return dirs;
}

#if defined(LOG_INPUT)
static void _Win32LogPOVInput(InputEventInfo &newEvent)
{

   U32 inst = 0xffff;
   const char* sstate = ( newEvent.action == SI_MAKE ) ? "pressed" : "released";
   const char* dir = "";
   switch( newEvent.objInst )
   {
   case SI_UPOV:
   case SI_UPOV2:
      dir = "Up"; inst = (newEvent.objInst == SI_UPOV) ? 1 : 2; break;
   case SI_RPOV:
   case SI_RPOV2:
      dir = "Right"; inst = (newEvent.objInst == SI_RPOV) ? 1 : 2; break;
   case SI_DPOV:
   case SI_DPOV2:
      dir = "Down"; inst = (newEvent.objInst == SI_DPOV) ? 1 : 2; break;
   case SI_LPOV:
   case SI_LPOV2:
      dir = "Left"; inst = (newEvent.objInst == SI_LPOV) ? 1 : 2; break;
   }
   Con::printf( "EVENT (DInput): %s POV %d %s.\n", dir, inst, sstate);
}
#else
#define _Win32LogPOVInput( a )
#endif

//------------------------------------------------------------------------------
bool DInputDevice::buildEvent( DWORD offset, S32 newData, S32 oldData )
{
   ObjInfo &objInfo = mObjInfo[offset];

   if ( objInfo.mType == SI_UNKNOWN )
      return false;

   InputEventInfo newEvent;
   newEvent.deviceType  = (InputDeviceTypes)mDeviceType;
   newEvent.deviceInst  = mDeviceID;
   newEvent.objType     = objInfo.mType;
   newEvent.objInst     = objInfo.mInst;
   newEvent.modifier    = (InputModifiers)0;

   switch ( newEvent.objType )
   {
      case SI_AXIS:
         newEvent.action = SI_MOVE;
         if ( newEvent.deviceType == MouseDeviceType )
         {
            newEvent.fValue = float( newData );

#ifdef LOG_INPUT
#ifdef LOG_MOUSEMOVE
            if ( newEvent.objInst == SI_XAXIS )
               Input::log( "EVENT (DInput): %s move (%.1f, 0.0).\n", mName, newEvent.fValue );
            else if ( newEvent.objInst == SI_YAXIS )
               Input::log( "EVENT (DInput): %s move (0.0, %.1f).\n", mName, newEvent.fValue );
            else
#endif
            if ( newEvent.objInst == SI_ZAXIS )
               Input::log( "EVENT (DInput): %s wheel move %.1f.\n", mName, newEvent.fValue );
#endif
         }
         else  // Joystick or other device:
         {
            // Scale to the range -1.0 to 1.0:
            if ( objInfo.mMin != DIPROPRANGE_NOMIN && objInfo.mMax != DIPROPRANGE_NOMAX )
            {
               F32 range = F32( objInfo.mMax - objInfo.mMin );
               newEvent.fValue = F32( ( 2 * newData ) - objInfo.mMax - objInfo.mMin ) / range;
            }
            else
               newEvent.fValue = (F32)newData;

#ifdef LOG_INPUT
            // Keep this commented unless you REALLY want these messages for something--
            // they come once per each iteration of the main game loop.
            //switch ( newEvent.objType )
            //{
               //case SI_XAXIS:
                  //if ( newEvent.fValue < -0.01f || newEvent.fValue > 0.01f )
                     //Input::log( "EVENT (DInput): %s X-axis move %.2f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_YAXIS:
                  //if ( newEvent.fValue < -0.01f || newEvent.fValue > 0.01f )
                     //Input::log( "EVENT (DInput): %s Y-axis move %.2f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_ZAXIS:
                  //Input::log( "EVENT (DInput): %s Z-axis move %.1f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_RXAXIS:
                  //Input::log( "EVENT (DInput): %s R-axis move %.1f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_RYAXIS:
                  //Input::log( "EVENT (DInput): %s U-axis move %.1f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_RZAXIS:
                  //Input::log( "EVENT (DInput): %s V-axis move %.1f.\n", mName, newEvent.fValue );
                  //break;
               //case SI_SLIDER:
                  //Input::log( "EVENT (DInput): %s slider move %.1f.\n", mName, newEvent.fValue );
                  //break;
            //};
#endif
         }

         newEvent.postToSignal(Input::smInputEvent);
         break;

      case SI_BUTTON:
         newEvent.action   = ( newData & 0x80 ) ? SI_MAKE : SI_BREAK;
         newEvent.fValue   = ( newEvent.action == SI_MAKE ) ? 1.0f : 0.0f;

#ifdef LOG_INPUT
         if ( newEvent.action == SI_MAKE )
            Input::log( "EVENT (DInput): %s button%d pressed.\n", mName, newEvent.objInst - KEY_BUTTON0 );
         else
            Input::log( "EVENT (DInput): %s button%d released.\n", mName, newEvent.objInst - KEY_BUTTON0 );
#endif

         newEvent.postToSignal(Input::smInputEvent);
         break;

      case SI_POV:
         // Handle artificial POV up/down/left/right buttons

         // If we're not a polling device, oldData and newData are the same, so "fake" transitions
         if(!isPolled()) {
            oldData = mPrevPOVPos;
            mPrevPOVPos = newData;
         }

         newData = LOWORD( newData );
         oldData = LOWORD( oldData );

         newData = _Win32GetPOVDirs(newData);
         oldData = _Win32GetPOVDirs(oldData);
         
         U32 setkeys = newData & (~oldData);
         U32 clearkeys = oldData & (~newData);
         U32 objInst = newEvent.objInst;

         if ( setkeys || clearkeys )
         {
            if ( clearkeys )
            {
               newEvent.action = SI_BREAK;
               newEvent.fValue = 0.0f;
               // post events for all buttons that need to be cleared.
               if( clearkeys & POV_up)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_UPOV : SI_UPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);

               }
               if( clearkeys & POV_right)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_RPOV : SI_RPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
               if( clearkeys & POV_down)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_DPOV : SI_DPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
               if( clearkeys & POV_left)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_LPOV : SI_LPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
            } // clear keys

            if ( setkeys )
            {
               newEvent.action = SI_MAKE;
               newEvent.fValue = 1.0f;
               // post events for all buttons that need to be set.
               if( setkeys & POV_up)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_UPOV : SI_UPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
               if( setkeys & POV_right)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_RPOV : SI_RPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
               if( setkeys & POV_down)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_DPOV : SI_DPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
               if( setkeys & POV_left)
               {
                  newEvent.objInst = ( objInst == 0 ) ? SI_LPOV : SI_LPOV2;
                  _Win32LogPOVInput(newEvent);
                  newEvent.postToSignal(Input::smInputEvent);
               }
            } // set keys
         }
         break;
   }

   return true;
}

void DInputDevice::rumble(F32 x, F32 y)
{
   LONG            rglDirection[2] = { 0, 0 };
   DICONSTANTFORCE cf              = { 0 };
   HRESULT         result;

   // Now set the new parameters and start the effect immediately.
   if (!mForceFeedbackEffect)
   {
#ifdef LOG_INPUT
      Input::log("DInputDevice::rumbleJoystick - creating constant force feeback effect\n"); 
#endif
      DIEFFECT eff;
      ZeroMemory( &eff, sizeof(eff) );
      eff.dwSize                  = sizeof(DIEFFECT);
      eff.dwFlags                 = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
      eff.dwDuration              = INFINITE;
      eff.dwSamplePeriod          = 0;
      eff.dwGain                  = DI_FFNOMINALMAX;
      eff.dwTriggerButton         = DIEB_NOTRIGGER;
      eff.dwTriggerRepeatInterval = 0;
      eff.cAxes                   = mNumForceFeedbackAxes;
      eff.rgdwAxes                = mForceFeedbackAxes;
      eff.rglDirection            = rglDirection;
      eff.lpEnvelope              = 0;
      eff.cbTypeSpecificParams    = sizeof(DICONSTANTFORCE);
      eff.lpvTypeSpecificParams   = &cf;
      eff.dwStartDelay            = 0;

      // Create the prepared effect
      if ( FAILED( result = mDevice->CreateEffect( GUID_ConstantForce, &eff, &mForceFeedbackEffect, NULL ) ) )
      {
#ifdef LOG_INPUT
         Input::log( "DInputDevice::rumbleJoystick - %s does not support force feedback.\n", mName );
#endif
	      Con::errorf( "DInputDevice::rumbleJoystick - %s does not support force feedback.\n", mName );
	      return;
      }
      else
      {
#ifdef LOG_INPUT
         Input::log( "DInputDevice::rumbleJoystick - %s supports force feedback.\n", mName );
#endif
	      Con::printf( "DInputDevice::rumbleJoystick - %s supports force feedback.\n", mName );
      }
   }

   // Clamp the input floats to [0 - 1]
   x = max(0, min(1, x));
   y = max(0, min(1, y));

   if ( 1 == mNumForceFeedbackAxes )
   {
      cf.lMagnitude = (DWORD)( x * DI_FFNOMINALMAX );
   }
   else
   {
      rglDirection[0] = (DWORD)( x * DI_FFNOMINALMAX );
      rglDirection[1] = (DWORD)( y * DI_FFNOMINALMAX );
      cf.lMagnitude = (DWORD)sqrt( (double)(x * x * DI_FFNOMINALMAX * DI_FFNOMINALMAX + y * y * DI_FFNOMINALMAX * DI_FFNOMINALMAX) );
   }

   DIEFFECT eff;
   ZeroMemory( &eff, sizeof(eff) );
   eff.dwSize                  = sizeof(DIEFFECT);
   eff.dwFlags                 = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
   eff.dwDuration              = INFINITE;
   eff.dwSamplePeriod          = 0;
   eff.dwGain                  = DI_FFNOMINALMAX;
   eff.dwTriggerButton         = DIEB_NOTRIGGER;
   eff.dwTriggerRepeatInterval = 0;
   eff.cAxes                   = mNumForceFeedbackAxes;
   eff.rglDirection            = rglDirection;
   eff.lpEnvelope              = 0;
   eff.cbTypeSpecificParams    = sizeof(DICONSTANTFORCE);
   eff.lpvTypeSpecificParams   = &cf;
   eff.dwStartDelay            = 0;

   if ( FAILED( result = mForceFeedbackEffect->SetParameters( &eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START ) ) )
   {
      const char* errorString = NULL;
      switch ( result )
      {
         case DIERR_INPUTLOST:
            errorString = "DIERR_INPUTLOST";
            break;

         case DIERR_INVALIDPARAM:
            errorString = "DIERR_INVALIDPARAM";
            break;

         case DIERR_NOTACQUIRED:
            errorString = "DIERR_NOTACQUIRED";
            break;

         default:
            errorString = "Unknown Error";
      }

#ifdef LOG_INPUT
      Input::log( "DInputDevice::rumbleJoystick - %s - Failed to start rumble effect\n", errorString );
#endif
      Con::errorf( "DInputDevice::rumbleJoystick - %s - Failed to start rumble effect\n", errorString );
   }
}

//------------------------------------------------------------------------------
//
// This function translates the DirectInput scan code to the associated
// internal key code (as defined in event.h).
//
//------------------------------------------------------------------------------
InputObjectInstances DIK_to_Key( U8 dikCode )
{
   switch ( dikCode )
   {
      case DIK_ESCAPE:        return KEY_ESCAPE;

      case DIK_1:             return KEY_1;
      case DIK_2:             return KEY_2;
      case DIK_3:             return KEY_3;
      case DIK_4:             return KEY_4;
      case DIK_5:             return KEY_5;
      case DIK_6:             return KEY_6;
      case DIK_7:             return KEY_7;
      case DIK_8:             return KEY_8;
      case DIK_9:             return KEY_9;
      case DIK_0:             return KEY_0;

      case DIK_MINUS:         return KEY_MINUS;
      case DIK_EQUALS:        return KEY_EQUALS;
      case DIK_BACK:          return KEY_BACKSPACE;
      case DIK_TAB:           return KEY_TAB;

      case DIK_Q:             return KEY_Q;
      case DIK_W:             return KEY_W;
      case DIK_E:             return KEY_E;
      case DIK_R:             return KEY_R;
      case DIK_T:             return KEY_T;
      case DIK_Y:             return KEY_Y;
      case DIK_U:             return KEY_U;
      case DIK_I:             return KEY_I;
      case DIK_O:             return KEY_O;
      case DIK_P:             return KEY_P;

      case DIK_LBRACKET:      return KEY_LBRACKET;
      case DIK_RBRACKET:      return KEY_RBRACKET;
      case DIK_RETURN:        return KEY_RETURN;
      case DIK_LCONTROL:      return KEY_LCONTROL;

      case DIK_A:             return KEY_A;
      case DIK_S:             return KEY_S;
      case DIK_D:             return KEY_D;
      case DIK_F:             return KEY_F;
      case DIK_G:             return KEY_G;
      case DIK_H:             return KEY_H;
      case DIK_J:             return KEY_J;
      case DIK_K:             return KEY_K;
      case DIK_L:             return KEY_L;

      case DIK_SEMICOLON:     return KEY_SEMICOLON;
      case DIK_APOSTROPHE:    return KEY_APOSTROPHE;
      case DIK_GRAVE:         return KEY_TILDE;
      case DIK_LSHIFT:        return KEY_LSHIFT;
      case DIK_BACKSLASH:     return KEY_BACKSLASH;

      case DIK_Z:             return KEY_Z;
      case DIK_X:             return KEY_X;
      case DIK_C:             return KEY_C;
      case DIK_V:             return KEY_V;
      case DIK_B:             return KEY_B;
      case DIK_N:             return KEY_N;
      case DIK_M:             return KEY_M;

      case DIK_COMMA:         return KEY_COMMA;
      case DIK_PERIOD:        return KEY_PERIOD;
      case DIK_SLASH:         return KEY_SLASH;
      case DIK_RSHIFT:        return KEY_RSHIFT;
      case DIK_MULTIPLY:      return KEY_MULTIPLY;
      case DIK_LALT:          return KEY_LALT;
      case DIK_SPACE:         return KEY_SPACE;
      case DIK_CAPSLOCK:      return KEY_CAPSLOCK;

      case DIK_F1:            return KEY_F1;
      case DIK_F2:            return KEY_F2;
      case DIK_F3:            return KEY_F3;
      case DIK_F4:            return KEY_F4;
      case DIK_F5:            return KEY_F5;
      case DIK_F6:            return KEY_F6;
      case DIK_F7:            return KEY_F7;
      case DIK_F8:            return KEY_F8;
      case DIK_F9:            return KEY_F9;
      case DIK_F10:           return KEY_F10;

      case DIK_NUMLOCK:       return KEY_NUMLOCK;
      case DIK_SCROLL:        return KEY_SCROLLLOCK;

      case DIK_NUMPAD7:       return KEY_NUMPAD7;
      case DIK_NUMPAD8:       return KEY_NUMPAD8;
      case DIK_NUMPAD9:       return KEY_NUMPAD9;
      case DIK_SUBTRACT:      return KEY_SUBTRACT;

      case DIK_NUMPAD4:       return KEY_NUMPAD4;
      case DIK_NUMPAD5:       return KEY_NUMPAD5;
      case DIK_NUMPAD6:       return KEY_NUMPAD6;
      case DIK_ADD:           return KEY_ADD;

      case DIK_NUMPAD1:       return KEY_NUMPAD1;
      case DIK_NUMPAD2:       return KEY_NUMPAD2;
      case DIK_NUMPAD3:       return KEY_NUMPAD3;
      case DIK_NUMPAD0:       return KEY_NUMPAD0;
      case DIK_DECIMAL:       return KEY_DECIMAL;

      case DIK_F11:           return KEY_F11;
      case DIK_F12:           return KEY_F12;
      case DIK_F13:           return KEY_F13;
      case DIK_F14:           return KEY_F14;
      case DIK_F15:           return KEY_F15;

      case DIK_KANA:          return KEY_NULL;
      case DIK_CONVERT:       return KEY_NULL;
      case DIK_NOCONVERT:     return KEY_NULL;
      case DIK_YEN:           return KEY_NULL;
      case DIK_NUMPADEQUALS:  return KEY_NULL;
      case DIK_CIRCUMFLEX:    return KEY_NULL;
      case DIK_AT:            return KEY_NULL;
      case DIK_COLON:         return KEY_NULL;
      case DIK_UNDERLINE:     return KEY_NULL;
      case DIK_KANJI:         return KEY_NULL;
      case DIK_STOP:          return KEY_NULL;
      case DIK_AX:            return KEY_NULL;
      case DIK_UNLABELED:     return KEY_NULL;

      case DIK_NUMPADENTER:   return KEY_NUMPADENTER;
      case DIK_RCONTROL:      return KEY_RCONTROL;
      case DIK_NUMPADCOMMA:   return KEY_SEPARATOR;
      case DIK_DIVIDE:        return KEY_DIVIDE;
      case DIK_SYSRQ:         return KEY_PRINT;
      case DIK_RALT:          return KEY_RALT;
      case DIK_PAUSE:         return KEY_PAUSE;

      case DIK_HOME:          return KEY_HOME;
      case DIK_UP:            return KEY_UP;
      case DIK_PGUP:          return KEY_PAGE_UP;
      case DIK_LEFT:          return KEY_LEFT;
      case DIK_RIGHT:         return KEY_RIGHT;
      case DIK_END:           return KEY_END;
      case DIK_DOWN:          return KEY_DOWN;
      case DIK_PGDN:          return KEY_PAGE_DOWN;
      case DIK_INSERT:        return KEY_INSERT;
      case DIK_DELETE:        return KEY_DELETE;

      case DIK_LWIN:          return KEY_WIN_LWINDOW;
      case DIK_RWIN:          return KEY_WIN_RWINDOW;
      case DIK_APPS:          return KEY_WIN_APPS;
      case DIK_OEM_102:       return KEY_OEM_102;
   }

   return KEY_NULL;
}

//------------------------------------------------------------------------------
//
// This function translates an internal key code to the associated
// DirectInput scan code
//
//------------------------------------------------------------------------------
U8 Key_to_DIK( U16 keyCode )
{
   switch ( keyCode )
   {
      case KEY_BACKSPACE:     return DIK_BACK;
      case KEY_TAB:           return DIK_TAB;
      case KEY_RETURN:        return DIK_RETURN;
      //KEY_CONTROL:
      //KEY_ALT:
      //KEY_SHIFT:
      case KEY_PAUSE:         return DIK_PAUSE;
      case KEY_CAPSLOCK:      return DIK_CAPSLOCK;
      case KEY_ESCAPE:        return DIK_ESCAPE;

      case KEY_SPACE:         return DIK_SPACE;
      case KEY_PAGE_DOWN:     return DIK_PGDN;
      case KEY_PAGE_UP:       return DIK_PGUP;
      case KEY_END:           return DIK_END;
      case KEY_HOME:          return DIK_HOME;
      case KEY_LEFT:          return DIK_LEFT;
      case KEY_UP:            return DIK_UP;
      case KEY_RIGHT:         return DIK_RIGHT;
      case KEY_DOWN:          return DIK_DOWN;
      case KEY_PRINT:         return DIK_SYSRQ;
      case KEY_INSERT:        return DIK_INSERT;
      case KEY_DELETE:        return DIK_DELETE;
      case KEY_HELP:          return 0;

      case KEY_0:             return DIK_0;
      case KEY_1:             return DIK_1;
      case KEY_2:             return DIK_2;
      case KEY_3:             return DIK_3;
      case KEY_4:             return DIK_4;
      case KEY_5:             return DIK_5;
      case KEY_6:             return DIK_6;
      case KEY_7:             return DIK_7;
      case KEY_8:             return DIK_8;
      case KEY_9:             return DIK_9;

      case KEY_A:             return DIK_A;
      case KEY_B:             return DIK_B;
      case KEY_C:             return DIK_C;
      case KEY_D:             return DIK_D;
      case KEY_E:             return DIK_E;
      case KEY_F:             return DIK_F;
      case KEY_G:             return DIK_G;
      case KEY_H:             return DIK_H;
      case KEY_I:             return DIK_I;
      case KEY_J:             return DIK_J;
      case KEY_K:             return DIK_K;
      case KEY_L:             return DIK_L;
      case KEY_M:             return DIK_M;
      case KEY_N:             return DIK_N;
      case KEY_O:             return DIK_O;
      case KEY_P:             return DIK_P;
      case KEY_Q:             return DIK_Q;
      case KEY_R:             return DIK_R;
      case KEY_S:             return DIK_S;
      case KEY_T:             return DIK_T;
      case KEY_U:             return DIK_U;
      case KEY_V:             return DIK_V;
      case KEY_W:             return DIK_W;
      case KEY_X:             return DIK_X;
      case KEY_Y:             return DIK_Y;
      case KEY_Z:             return DIK_Z;

      case KEY_TILDE:         return DIK_GRAVE;
      case KEY_MINUS:         return DIK_MINUS;
      case KEY_EQUALS:        return DIK_EQUALS;
      case KEY_LBRACKET:      return DIK_LBRACKET;
      case KEY_RBRACKET:      return DIK_RBRACKET;
      case KEY_BACKSLASH:     return DIK_BACKSLASH;
      case KEY_SEMICOLON:     return DIK_SEMICOLON;
      case KEY_APOSTROPHE:    return DIK_APOSTROPHE;
      case KEY_COMMA:         return DIK_COMMA;
      case KEY_PERIOD:        return DIK_PERIOD;
      case KEY_SLASH:         return DIK_SLASH;

      case KEY_NUMPAD0:       return DIK_NUMPAD0;
      case KEY_NUMPAD1:       return DIK_NUMPAD1;
      case KEY_NUMPAD2:       return DIK_NUMPAD2;
      case KEY_NUMPAD3:       return DIK_NUMPAD3;
      case KEY_NUMPAD4:       return DIK_NUMPAD4;
      case KEY_NUMPAD5:       return DIK_NUMPAD5;
      case KEY_NUMPAD6:       return DIK_NUMPAD6;
      case KEY_NUMPAD7:       return DIK_NUMPAD7;
      case KEY_NUMPAD8:       return DIK_NUMPAD8;
      case KEY_NUMPAD9:       return DIK_NUMPAD9;
      case KEY_MULTIPLY:      return DIK_MULTIPLY;
      case KEY_ADD:           return DIK_ADD;
      case KEY_SEPARATOR:     return DIK_NUMPADCOMMA;
      case KEY_SUBTRACT:      return DIK_SUBTRACT;
      case KEY_DECIMAL:       return DIK_DECIMAL;
      case KEY_DIVIDE:        return DIK_DIVIDE;
      case KEY_NUMPADENTER:   return DIK_NUMPADENTER;

      case KEY_F1:            return DIK_F1;
      case KEY_F2:            return DIK_F2;
      case KEY_F3:            return DIK_F3;
      case KEY_F4:            return DIK_F4;
      case KEY_F5:            return DIK_F5;
      case KEY_F6:            return DIK_F6;
      case KEY_F7:            return DIK_F7;
      case KEY_F8:            return DIK_F8;
      case KEY_F9:            return DIK_F9;
      case KEY_F10:           return DIK_F10;
      case KEY_F11:           return DIK_F11;
      case KEY_F12:           return DIK_F12;
      case KEY_F13:           return DIK_F13;
      case KEY_F14:           return DIK_F14;
      case KEY_F15:           return DIK_F15;
      case KEY_F16:
      case KEY_F17:
      case KEY_F18:
      case KEY_F19:
      case KEY_F20:
      case KEY_F21:
      case KEY_F22:
      case KEY_F23:
      case KEY_F24:           return 0;

      case KEY_NUMLOCK:       return DIK_NUMLOCK;
      case KEY_SCROLLLOCK:    return DIK_SCROLL;
      case KEY_LCONTROL:      return DIK_LCONTROL;
      case KEY_RCONTROL:      return DIK_RCONTROL;
      case KEY_LALT:          return DIK_LALT;
      case KEY_RALT:          return DIK_RALT;
      case KEY_LSHIFT:        return DIK_LSHIFT;
      case KEY_RSHIFT:        return DIK_RSHIFT;

      case KEY_WIN_LWINDOW:   return DIK_LWIN;
      case KEY_WIN_RWINDOW:   return DIK_RWIN;
      case KEY_WIN_APPS:      return DIK_APPS;
      case KEY_OEM_102:       return DIK_OEM_102;

   };

   return 0;
}

#ifdef LOG_INPUT
//------------------------------------------------------------------------------
const char* getKeyName( U16 key )
{
   switch ( key )
   {
      case KEY_BACKSPACE:     return "Backspace";
      case KEY_TAB:           return "Tab";
      case KEY_RETURN:        return "Return";
      case KEY_PAUSE:         return "Pause";
      case KEY_CAPSLOCK:      return "CapsLock";
      case KEY_ESCAPE:        return "Esc";

      case KEY_SPACE:         return "SpaceBar";
      case KEY_PAGE_DOWN:     return "PageDown";
      case KEY_PAGE_UP:       return "PageUp";
      case KEY_END:           return "End";
      case KEY_HOME:          return "Home";
      case KEY_LEFT:          return "Left";
      case KEY_UP:            return "Up";
      case KEY_RIGHT:         return "Right";
      case KEY_DOWN:          return "Down";
      case KEY_PRINT:         return "PrintScreen";
      case KEY_INSERT:        return "Insert";
      case KEY_DELETE:        return "Delete";
      case KEY_HELP:          return "Help";

      case KEY_NUMPAD0:       return "Numpad 0";
      case KEY_NUMPAD1:       return "Numpad 1";
      case KEY_NUMPAD2:       return "Numpad 2";
      case KEY_NUMPAD3:       return "Numpad 3";
      case KEY_NUMPAD4:       return "Numpad 4";
      case KEY_NUMPAD5:       return "Numpad 5";
      case KEY_NUMPAD6:       return "Numpad 6";
      case KEY_NUMPAD7:       return "Numpad 7";
      case KEY_NUMPAD8:       return "Numpad 8";
      case KEY_NUMPAD9:       return "Numpad 9";
      case KEY_MULTIPLY:      return "Multiply";
      case KEY_ADD:           return "Add";
      case KEY_SEPARATOR:     return "Separator";
      case KEY_SUBTRACT:      return "Subtract";
      case KEY_DECIMAL:       return "Decimal";
      case KEY_DIVIDE:        return "Divide";
      case KEY_NUMPADENTER:   return "Numpad Enter";

      case KEY_F1:            return "F1";
      case KEY_F2:            return "F2";
      case KEY_F3:            return "F3";
      case KEY_F4:            return "F4";
      case KEY_F5:            return "F5";
      case KEY_F6:            return "F6";
      case KEY_F7:            return "F7";
      case KEY_F8:            return "F8";
      case KEY_F9:            return "F9";
      case KEY_F10:           return "F10";
      case KEY_F11:           return "F11";
      case KEY_F12:           return "F12";
      case KEY_F13:           return "F13";
      case KEY_F14:           return "F14";
      case KEY_F15:           return "F15";
      case KEY_F16:           return "F16";
      case KEY_F17:           return "F17";
      case KEY_F18:           return "F18";
      case KEY_F19:           return "F19";
      case KEY_F20:           return "F20";
      case KEY_F21:           return "F21";
      case KEY_F22:           return "F22";
      case KEY_F23:           return "F23";
      case KEY_F24:           return "F24";

      case KEY_NUMLOCK:       return "NumLock";
      case KEY_SCROLLLOCK:    return "ScrollLock";
      case KEY_LCONTROL:      return "LCtrl";
      case KEY_RCONTROL:      return "RCtrl";
      case KEY_LALT:          return "LAlt";
      case KEY_RALT:          return "RAlt";
      case KEY_LSHIFT:        return "LShift";
      case KEY_RSHIFT:        return "RShift";

      case KEY_WIN_LWINDOW:   return "LWin";
      case KEY_WIN_RWINDOW:   return "RWin";
      case KEY_WIN_APPS:      return "Apps";
   }

   static char returnString[5];
   dSprintf( returnString, sizeof( returnString ), "%c", Input::getAscii( key, STATE_UPPER ) );
   return returnString;
}
#endif // LOG_INPUT

//------------------------------------------------------------------------------
const char* DInputDevice::getJoystickAxesString()
{
   if ( mDeviceType != JoystickDeviceType )
      return( "" );

   U32 axisCount = mDeviceCaps.dwAxes;
   char buf[64];
   dSprintf( buf, sizeof( buf ), "%d", axisCount );
   for ( U32 i = 0; i < mObjCount; i++ )
   {
      switch ( mObjInfo[i].mInst )
      {
         case SI_XAXIS:
            dStrcat( buf, "\tX", 64 );
            break;
         case SI_YAXIS:
            dStrcat( buf, "\tY", 64 );
            break;
         case SI_ZAXIS:
            dStrcat( buf, "\tZ", 64 );
            break;
         case SI_RXAXIS:
            dStrcat( buf, "\tR", 64 );
            break;
         case SI_RYAXIS:
            dStrcat( buf, "\tU", 64 );
            break;
         case SI_RZAXIS:
            dStrcat( buf, "\tV", 64 );
            break;
         case SI_SLIDER:
            dStrcat( buf, "\tS", 64 );
            break;
      }
   }

   dsize_t returnLen = dStrlen(buf) + 1;
   char* returnString = Con::getReturnBuffer(returnLen);
   dStrcpy( returnString, buf, returnLen );
   return( returnString );
}

//------------------------------------------------------------------------------
bool DInputDevice::joystickDetected()
{
   return( smDeviceCount[ JoystickDeviceType ] > 0 );
}



