// InputService.cpp : Implementation of cInputService

#include "stdafx.h"
#include "DecalInput.h"
#include "InputService.h"

#include "Timer.h"
#include "Hotkey.h"
#include "WinMsgHook.h"
#include "InputBuffer.h"

using cInputService::cCharNames;

static cCharNames _charnames[] = {
   { _T( "BACKSPACE" ), VK_BACK },
   { _T( "BS" ), VK_BACK },
   { _T( "BKSP" ), VK_BACK },
   { _T( "CAPSLOCK" ), VK_CAPITAL },
   { _T( "DELETE" ), VK_DELETE },
   { _T( "DEL" ), VK_DELETE },
   { _T( "DOWN" ), VK_DOWN },
   { _T( "END" ), VK_END },
   { _T( "ENTER" ), VK_RETURN },
   { _T( "ESC" ), VK_ESCAPE },
   { _T( "HELP" ), VK_HELP },
   { _T( "HOME" ), VK_HOME },
   { _T( "INS" ), VK_INSERT },
   { _T( "INSERT" ), VK_INSERT },
   { _T( "LEFT" ), VK_LEFT },
   { _T( "NUMLOCK" ), VK_NUMLOCK },
   { _T( "PGDN" ), VK_NEXT },
   { _T( "PGUP" ), VK_PRIOR },
   { _T( "PRTSC" ), VK_SNAPSHOT },
   { _T( "RIGHT" ), VK_RIGHT },
   { _T( "SCROLLLOCK" ), VK_SCROLL },
   { _T( "TAB" ), VK_TAB },
   { _T( "UP" ), VK_UP },
   { _T( "F1" ), VK_F1 },
   { _T( "F2" ), VK_F2 },
   { _T( "F3" ), VK_F3 },
   { _T( "F4" ), VK_F4 },
   { _T( "F5" ), VK_F5 },
   { _T( "F6" ), VK_F6 },
   { _T( "F7" ), VK_F7 },
   { _T( "F8" ), VK_F8 },
   { _T( "F9" ), VK_F9 },
   { _T( "F10" ), VK_F10 },
   { _T( "F11" ), VK_F11 },
   { _T( "F12" ), VK_F12 },
   { _T( "F13" ), VK_F13 },
   { _T( "F14" ), VK_F14 },
   { _T( "F15" ), VK_F15 },
   { _T( "F16" ), VK_F16 },
   { _T( "+" ), VK_ADD },
   { _T( "SHIFT" ), VK_SHIFT },
   { _T( "CTRL" ), VK_CONTROL },
   { _T( "ALT" ), VK_MENU },
   { _T( "LBUTTON" ), VK_LBUTTON },
   { _T( "RBUTTON" ), VK_RBUTTON } },
   *_end_charnames = _charnames + ( sizeof(_charnames ) / sizeof( cCharNames ) );

/////////////////////////////////////////////////////////////////////////////
// cInputService

LRESULT cInputService::wndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
{
   if( g_p == NULL || g_p->m_hWndHook != hWnd )
   {
      // We have no idea how to continue - this hook has been orphaned
      _ASSERT( FALSE );
      return 0;
   }

   if( g_p->m_msghooks.size() > 0 )
   {
      // Fill out the message
      g_p->m_pMessage->loadMessage( hWnd, nMsg, wParam, lParam );

      for( cWinMsgHookList::iterator i = g_p->m_msghooks.begin(); i != g_p->m_msghooks.end(); ++ i )
      {
         ( *i )->Fire_Message( *i, g_p->m_pMessage );
         if( g_p->m_pMessage->m_bEaten )
            // This message was eaten, yummy
            return 0;
      }
   }

   // NOTE: The hotkeys are not dispatched while an input buffer is running - tough luck
   if( nMsg == WM_KEYUP && g_p->m_pActive == NULL )
   {
      int nVK = static_cast< int >( wParam );

      // Check if there's a matching hotkey
      for( cHotkeyList::iterator i = g_p->m_hotkeys.begin(); i != g_p->m_hotkeys.end(); ++ i )
      {
         if( ( *i )->m_nVK == nVK )
         {
            // Found a hotkey - fire the event and eat it
            ( *i )->Fire_Hotkey( *i );
            break;
         }
      }
   }

   return g_p->m_pfnHook( hWnd, nMsg, wParam, lParam );
}

cInputService *cInputService::g_p = NULL;

HRESULT cInputService::onInitialize()
{
   // ::DebugBreak();

   USES_CONVERSION;

   if( g_p == NULL )
      g_p = this;

   // Create the message object
   CComObject< cWndMsg >::CreateInstance( &m_pMessage );
   m_pMessage->AddRef();

   // Load the list of action types from the configuration data
   {
      static _bstr_t _strPrefix( _T( "Prefix" ) );

      CComPtr< IDecalEnum > pActions;
      m_pDecal->get_Configuration( _bstr_t( _T( "InputActions" ) ), GUID_NULL, &pActions );
      while( pActions->Next() == S_OK )
      {
         VARIANT_BOOL bEnabled;
         HRESULT hRes = pActions->get_Enabled ( &bEnabled );
         _ASSERTE ( SUCCEEDED ( hRes ) );

         if ( !bEnabled )
            continue;

         cActionType at;
         pActions->get_ComClass( &at.m_clsid );

         CComVariant v;
         if( FAILED( pActions->get_Property( _strPrefix, &v ) ) )
            continue;

         ::_tcscpy( at.szAction, OLE2T( v.bstrVal ) );
         ::_tcslwr( at.szAction );
         m_actiontypes.push_back( at );
      }
   }

   // Get the OS version
   OSVERSIONINFO ovi;
   ovi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
   ::GetVersionEx( &ovi );

   if( ovi.dwPlatformId == VER_PLATFORM_WIN32_NT )
   {
      m_eWindows = eWindowsNT;
      return S_OK;
   }

   // First check if there's version info in the registry
   RegKey key;
   key.Open( HKEY_LOCAL_MACHINE, _T( "Software\\Decal" ) );
   DWORD dwRegMajor, dwRegMinor;
   if( key.QueryDWORDValue( _T( "ACMajorVersion" ), dwRegMajor ) != ERROR_SUCCESS ||
      key.QueryDWORDValue( _T( "ACMinorVersion" ), dwRegMinor ) != ERROR_SUCCESS )
   {
      m_eWindows = eWindows98NoMemLocs;
      return S_OK;
   }

   TCHAR szFilename[ MAX_PATH ];
   DWORD dwVerSize;

   ::GetModuleFileName( NULL, szFilename, MAX_PATH );
   if( ::GetFileVersionInfoSize( szFilename, &dwVerSize ) )
   {
      m_eWindows = eWindows98NoMemLocs;
      return S_OK;
   }

   BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );

   ::GetFileVersionInfo( szFilename, 0, dwVerSize, pbVersionInfo );

   VS_FIXEDFILEINFO *vffi;
   UINT nLength = sizeof( VS_FIXEDFILEINFO );
   if( ::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
   {
      if( dwRegMajor == vffi->dwFileVersionMS && dwRegMinor == vffi->dwFileVersionLS )
      {
         DWORD dwOffset1, dwOffset2;
         if( key.QueryDWORDValue( _T( "MouseOffset1" ), dwOffset1 ) == ERROR_SUCCESS &&
            key.QueryDWORDValue( _T( "MouseOffset2" ), dwOffset2 ) == ERROR_SUCCESS )
         {
            m_eWindows = eWindows98WithMemLocs;
            m_pnOffset1 = reinterpret_cast< long * >( dwOffset1 );
            m_pnOffset2 = reinterpret_cast< long * >( dwOffset2 );

            return S_OK;
         }
      }
   }

   m_eWindows = eWindows98NoMemLocs;

   return S_OK;
}

void cInputService::onTerminate()
{
   // Create the message object
   m_pMessage->Release();
   m_pMessage = NULL;

   if( g_p == this )
      g_p = NULL;
}

cCharNames *cInputService::begin_chars()
{
   return _charnames;
}

cCharNames *cInputService::end_chars()
{
   return _end_charnames;
}

cCharNames *cInputService::charFromVK( int nVK )
{
   for( cCharNames *i_chars = _charnames; i_chars != _end_charnames; ++ i_chars )
   {
      if( i_chars->m_nVKey == nVK )
         break;
   }

   return i_chars;
}

cCharNames *cInputService::charFromName( LPCTSTR szName, int nLength )
{
   for( cCharNames *i_chars = _charnames; i_chars != _end_charnames; ++ i_chars )
   {
      if( ::strlen( i_chars->szName ) == nLength && std::equal( szName, szName + nLength, i_chars->szName ) )
         break;
   }

   return i_chars;
}

STDMETHODIMP cInputService::BeforePlugins()
{
   // Hook the main plugin window
   long nWnd;
   m_pDecal->get_HWND( &nWnd );
   if( nWnd != 0 )
   {
      m_hWndHook = reinterpret_cast< HWND >( nWnd );
      m_pfnHook = reinterpret_cast< WNDPROC >( ::SetWindowLong( m_hWndHook, GWL_WNDPROC, reinterpret_cast< LONG >( wndProc ) ) );
   }

   // Load the keymap
   RegKey key;
   if(key.Open( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ), KEY_READ ) != ERROR_SUCCESS)
      return E_FAIL;

   TCHAR szFilename[ MAX_PATH ];
   TCHAR *pszInsert = szFilename;
   DWORD dwSize = MAX_PATH;
   key.QueryStringValue( _T("Path"), szFilename, &dwSize );
   pszInsert = &szFilename[dwSize-1];
   //pszInsert += dwSize;

   ::_tcscat( pszInsert, _T( "\\" ) );
   ++ pszInsert;

   dwSize = MAX_PATH - ( dwSize + 1 );
   key.QueryStringValue( _T( "CurrentInputMap" ), pszInsert, &dwSize );

   // Looking good, open up the file and load the keys
   std::ifstream is( szFilename );

   if( is.bad() )
      // Could not load the file
      return E_FAIL;

   int nCount = 0;
   is >> nCount;
   for( ; nCount > 0; -- nCount )
   {
      int nInputType, nVK, nChording, nCommandType, nAnalogType;
      TCHAR szCommandName[ 64 ];

      is >> nInputType >> nVK >> nChording >> szCommandName >> nCommandType >> nAnalogType;
      m_commands.insert( cCommandMap::value_type( szCommandName, static_cast< WORD >( nVK ) ) );
   }

   return S_OK;
}

STDMETHODIMP cInputService::AfterPlugins()
{
   m_commands.clear();

   if( m_hWndHook != NULL )
   {
      _ASSERTE( reinterpret_cast< LONG >( wndProc ) == ::GetWindowLong( m_hWndHook, GWL_WNDPROC ) );
      ::SetWindowLong( m_hWndHook, GWL_WNDPROC, reinterpret_cast< LONG >( m_pfnHook ) );
   }

   return S_OK;
}

STDMETHODIMP cInputService::get_Command(BSTR strCommandName, BSTR *pVal)
{
   USES_CONVERSION;

   // First try to find the command in the list
   _bstr_t strCommand( strCommandName );
   cCommandMap::iterator i = m_commands.find( strCommand );

   if( i == m_commands.end() )
   {
      *pVal = T2BSTR( _T( "" ) );
      return S_FALSE;
   }

   WORD nCommand = i->second;

   // Scan to see if this command has a special name
   cCharNames *i_chars = charFromVK( nCommand );

   if( i_chars != end_chars() )
   {
      TCHAR szFormat[ 12 ];
      ::_stprintf( szFormat, _T( "{%s}" ), i_chars->szName );

      *pVal = T2BSTR( szFormat );
      return S_OK;
   }

   // Ok, we'll assume the char is printable
   TCHAR szString[ 2 ] = { nCommand, _T( '\0' ) };
   *pVal = T2BSTR( szString );

	return S_OK;
}

STDMETHODIMP cInputService::Render2D()
{
   long nTime = static_cast< long >( ::timeGetTime() );

   cTimerList::iterator i = m_timers.begin();
   while( i != m_timers.end() )
   {
      cTimerList::iterator j = i++; // Make a copy of the iterator, increment it, then call the timeout.
      if( ( ( *j )->m_nStart + ( *j )->m_nInterval ) <= nTime )
      {
         ( *j )->m_nStart = nTime;
         ( *j )->Fire_Timeout( *j );
      }
   }

   // Check to see if there's a delay paused
   if( m_pActive != NULL && m_pActive->m_bWaiting && m_pActive->m_nUntilTime <= nTime )
      // We've passed the timeout, continue processing actions
      m_pActive->runActions();

   return S_OK;
}

STDMETHODIMP cInputService::ChangeHWND()
{
   return S_OK;
}

STDMETHODIMP cInputService::get_Decal(IDecal **pVal)
{
	return m_pDecal->QueryInterface( pVal );
}

STDMETHODIMP cInputService::get_KeyByName(BSTR strName, long *pVal)
{
   USES_CONVERSION;
   LPTSTR szName = OLE2T( strName );

   cCharNames *i_c = charFromName( szName, ::_tcslen( szName ) );
   if( i_c == end_chars() )
      return E_INVALIDARG;

   *pVal = i_c->m_nVKey;

	return S_OK;
}

STDMETHODIMP cInputService::get_CommandKey(BSTR strCommandName, long *pVal)
{
   _bstr_t strCommand( strCommandName );
   cCommandMap::iterator i = m_commands.find( strCommand );

   if( i == m_commands.end() )
      return E_FAIL;

   *pVal = i->second;
   return S_OK;
}
