// InputBuffer.cpp : Implementation of cInputBuffer
#include "stdafx.h"
#include "Inject.h"
#include "InputBuffer.h"

#include "Manager.h"

/////////////////////////////////////////////////////////////////////////////
// cInputBuffer

struct cCharNames
{
   LPCTSTR szName;
   WORD m_nVKey;
};

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 } },
   *_end_charnames = _charnames + ( sizeof(_charnames ) / sizeof( cCharNames ) );

#define MASK_SHIFT   0x0100
#define MASK_CTRL    0x0200
#define MASK_ALT     0x0400

#define SHIFT_MASK   0x0700
#define VKEY_MASK    0x00FF

void pushVirtualKey( WORD vk, cInputVec &vec, bool bDown )
{
   INPUT i;
   ::memset( &i, 0, sizeof( INPUT ) );
   i.type = INPUT_KEYBOARD;
   i.ki.wVk = vk;
   i.ki.wScan = MapVirtualKey( vk, 0 );
   i.ki.dwFlags = ( bDown ) ? 0 : KEYEVENTF_KEYUP;

   vec.push_back( i );
}

void processKeyboardChars( BSTR strChars, cInputVec &vec )
{
   USES_CONVERSION;

   typedef std::stack< WORD > cMaskStack;
   cMaskStack mask;

   WORD wCurrentMask = 0;

   std::string strParse = OLE2T( strChars );

   for( std::string::iterator i = strParse.begin(); i != strParse.end(); )
   {
      switch( *i )
      {
      case _T( '+' ):
         _ASSERTE( ( i + 2 ) < strParse.end() );
         _ASSERTE( *( i + 1 ) == _T( '(' ) );
         _ASSERTE( !( wCurrentMask & MASK_SHIFT ) );

         wCurrentMask |= MASK_SHIFT;
         mask.push( MASK_SHIFT );

         // Send the keydown
         pushVirtualKey( VK_SHIFT, vec, true );
         
         i += 2;
         break;

      case _T( '^' ):
         _ASSERTE( ( i + 2 ) < strParse.end() );
         _ASSERTE( *( i + 1 ) == _T( '(' ) );
         _ASSERTE( !( wCurrentMask & MASK_CTRL ) );

         wCurrentMask |= MASK_CTRL;
         mask.push( MASK_CTRL );

         // Send the keydown
         pushVirtualKey( VK_CONTROL, vec, true );
         
         i += 2;
         break;

      case _T( '%' ):
         _ASSERTE( ( i + 2 ) < strParse.end() );
         _ASSERTE( *( i + 1 ) == _T( '(' ) );
         _ASSERTE( !( wCurrentMask & MASK_ALT ) );

         wCurrentMask |= MASK_ALT;
         mask.push( MASK_ALT );

         // Send the keydown
         pushVirtualKey( VK_MENU, vec, true );
         
         i += 2;
         break;

      case _T( ')' ):
         _ASSERTE( mask.size() > 0 );

         {
            DWORD wMask = mask.top();
            mask.pop();

            switch( wMask )
            {
            case MASK_SHIFT:
               pushVirtualKey( VK_SHIFT, vec, false );
               break;

            case MASK_CTRL:
               pushVirtualKey( VK_CONTROL, vec, false );
               break;

            case MASK_ALT:
               pushVirtualKey( VK_MENU, vec, false );
               break;

            default:
               // Bad value in the stack
               _ASSERTE( FALSE );
               break;
            }

            wCurrentMask &= ~wMask;
         }

         ++ i;
         break;

      case _T( '{' ):
         // This is a special key
         ++ i;
         {
            for( std::string::iterator j = i; *j != _T( '}' ); ++ j )
            {
               _ASSERTE( j != strParse.end() );
            }

            std::string strSpecial( i, j );
            for( cCharNames *i_name = _charnames; i_name != _end_charnames; ++ i_name )
            {
               if( strSpecial.compare( i_name->szName ) == 0 )
               {
                  // We found the match, push and release the vkey
                  pushVirtualKey( i_name->m_nVKey, vec, true );
                  pushVirtualKey( i_name->m_nVKey, vec, false );
                  break;
               }
            }

            // The special key was not found in the list
            _ASSERTE( i_name != _end_charnames );

            i = j + 1;
         }
         break;

      default:
         // Assume everything else is a text character but may require additional
         // shifting
         {
            WORD wVkScan = VkKeyScan( *i ),
               wAdditionalShift = ( wVkScan & SHIFT_MASK ) & ~wCurrentMask,
               wVKey = wVkScan & VKEY_MASK;

            if( wAdditionalShift & MASK_SHIFT )
               pushVirtualKey( VK_SHIFT, vec, true );
            if( wAdditionalShift & MASK_CTRL )
               pushVirtualKey( VK_CONTROL, vec, true );
            if( wAdditionalShift & MASK_ALT )
               pushVirtualKey( VK_MENU, vec, true );

            pushVirtualKey( wVKey, vec, true );
            pushVirtualKey( wVKey, vec, false );

            if( wAdditionalShift & MASK_ALT )
               pushVirtualKey( VK_MENU, vec, false );
            if( wAdditionalShift & MASK_CTRL )
               pushVirtualKey( VK_CONTROL, vec, false );
            if( wAdditionalShift & MASK_SHIFT )
               pushVirtualKey( VK_SHIFT, vec, false );
         }

         ++ i;
         break;
      }
   }

   // Make sure we released all shifts
   _ASSERTE( wCurrentMask == 0 );
}

void pushMouseEvent( DWORD dwFlags, cInputVec &vec )
{
   INPUT i;
   ::memset( &i, 0, sizeof( INPUT ) );
   i.type = INPUT_MOUSE;
   i.mi.dwFlags = dwFlags;

   vec.push_back( i );
}

void processMouseInput( eMouseInput eType, cInputVec &vec )
{
   DWORD dwError = ::GetLastError();

   switch( eType )
   {
   case eMouseLeftDoubleClick:
      pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec );
      pushMouseEvent( MOUSEEVENTF_LEFTUP, vec );
   case eMouseLeftClick:
      pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec );
      pushMouseEvent( MOUSEEVENTF_LEFTUP, vec );
      break;

   case eMouseRightDoubleClick:
      pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec );
      pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec );
   case eMouseRightClick:
      pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec );
      pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec );
      break;

   default:
      _ASSERTE( FALSE );
      break;
   }
}

HANDLE cInputBuffer::m_hThread = NULL;
HANDLE cInputBuffer::m_hInputWaiting = NULL;
HANDLE cInputBuffer::m_hTerm = NULL;

CRITICAL_SECTION cInputBuffer::m_csQueue;
cInputBuffer::cInputQueue cInputBuffer::m_queue;

DWORD WINAPI inputThreadProc( LPVOID pvParam );

void cInputBuffer::init()
{
   ::InitializeCriticalSection( &m_csQueue );
   m_hTerm = ::CreateEvent( NULL, TRUE, FALSE, NULL );
   m_hInputWaiting = ::CreateEvent( NULL, TRUE, FALSE, NULL );

   DWORD dwThreadID;
   m_hThread = ::CreateThread( NULL, 0, inputThreadProc, NULL, 0, &dwThreadID );
}

void cInputBuffer::term()
{
   // Clear out the queue
   ::EnterCriticalSection( &m_csQueue );
   while( m_queue.size() > 0 )
      m_queue.pop_front();
   ::SetEvent( m_hTerm );
   ::LeaveCriticalSection( &m_csQueue );

   ::WaitForSingleObject( m_hThread, INFINITE );

   ::CloseHandle( m_hThread );
   ::CloseHandle( m_hInputWaiting );
   ::CloseHandle( m_hTerm );
   ::DeleteCriticalSection( &m_csQueue );
}

void cInputBuffer::postBuffer()
{
   ::EnterCriticalSection( &m_csQueue );
   if( m_queue.empty() )
      ::SetEvent( m_hInputWaiting );

   // Marshal our interface into a drop in in the queue
   IStream *pStream;
   ::CoMarshalInterThreadInterfaceInStream( IID_IInputNotify, static_cast< IInputNotify * >( this ), &pStream );
   m_queue.push_back( cInputQueue::value_type( this, pStream ) );

   m_status = eInputWaiting;

   ::LeaveCriticalSection( &m_csQueue );
}

cInputVec *cInputBuffer::getLastInput()
{
   if( m_actions.size() == 0 || m_actions.back().first != eActionInput )
      // Add in a new buffer
      m_actions.push_back( cActionList::value_type( eActionInput, VSBridge::auto_ptr< cAction >( new cInput ) ) );

   // Return the last buffer
   return &( static_cast< cInput * >( m_actions.back().second.get() )->m_input );
}

DWORD WINAPI inputThreadProc( LPVOID pvParam )
{
   // This thread must join the mult-threaded apartment becasue it does
   // not create a message loop
   ::CoInitializeEx( NULL, COINIT_MULTITHREADED );

   BYTE prevKeyboard[ 256 ],
      blankKeyboard[ 256 ];

   ::memset( blankKeyboard, 0, sizeof( blankKeyboard ) );

   HANDLE hEvent[2] = { cInputBuffer::m_hInputWaiting, cInputBuffer::m_hTerm };

   while( ::WaitForMultipleObjects( 2, hEvent, FALSE, INFINITE ) == WAIT_OBJECT_0 )
   {
      // Extract the next item from the stream
      ::EnterCriticalSection( &cInputBuffer::m_csQueue );

      cInputBuffer *pBuffer = cInputBuffer::m_queue.front().first;
      CComPtr< IInputNotify > pNotify;
      ::CoGetInterfaceAndReleaseStream( cInputBuffer::m_queue.front().second, IID_IInputNotify, reinterpret_cast< void ** >( &pNotify ) );

      cInputBuffer::m_queue.pop_front();

      if( cInputBuffer::m_queue.empty() )
         ::ResetEvent( cInputBuffer::m_hInputWaiting );

      ::LeaveCriticalSection( &cInputBuffer::m_csQueue );

      // Save the keyboard state and take control of the input queue
      ::BlockInput( TRUE );
      ::GetKeyboardState( prevKeyboard );
      ::SetKeyboardState( blankKeyboard );

      // Looking good, process the messages
      pNotify->NotifyBegin();

      // The delay list contains the breaks in the input, so we use the delay list as a master
      // and use segments of the input queue
      for( cInputBuffer::cActionList::iterator i = pBuffer->m_actions.begin(); i != pBuffer->m_actions.end(); ++ i )
         i->second->execute( pNotify );

      // Restore keyboard state
      ::SetKeyboardState( prevKeyboard );
      pNotify->NotifyEnd();

      ::BlockInput( FALSE );
   }

   ::CoUninitialize();
   return 0;
}

// The action execution functions
void cInputBuffer::cInput::execute( IInputNotify * )
{
   UINT nSent = SendInput( m_input.size(), &(*(m_input.begin())), sizeof( INPUT ) );
   DWORD dwError = GetLastError();
   _ASSERTE( nSent == m_input.size() );
}

void cInputBuffer::cDelay::execute( IInputNotify *pNotify )
{
   if( m_bAllowInput )
   {
      BlockInput( FALSE );
      pNotify->NotifyPause();
   }

   ::Sleep( m_nMillis );

   if( m_bAllowInput )
   {
      BlockInput( TRUE );
      pNotify->NotifyBegin();
   }
}

void cInputBuffer::cMouseMove::execute( IInputNotify *pib )
{
   pib->SetMousePos( m_ptClient.x, m_ptClient.y );
}

STDMETHODIMP cInputBuffer::Clear()
{
   if( m_status != eInputIdle )
   {
      _ASSERTE( FALSE );
      return E_FAIL;
   }

   m_actions.clear();

	return S_OK;
}

STDMETHODIMP cInputBuffer::TypeText(BSTR strText)
{
   if( m_status != eInputIdle )
   {
      _ASSERTE( FALSE );
      return E_FAIL;
   }

   processKeyboardChars( strText, *getLastInput() );

	return S_OK;
}

STDMETHODIMP cInputBuffer::Delay(long nMilliseconds, VARIANT_BOOL bAllowInput )
{
   if( m_status != eInputIdle )
   {
      _ASSERTE( FALSE );
      return E_FAIL;
   }

   cDelay *pDelay = new cDelay;
   pDelay->m_bAllowInput = !!bAllowInput;
   pDelay->m_nMillis = nMilliseconds;

   m_actions.push_back( cActionList::value_type( eActionDelay, VSBridge::auto_ptr< cAction >( pDelay ) ) );

	return S_OK;
}

STDMETHODIMP cInputBuffer::MouseClick(long nX, long nY, eMouseInput eAction)
{
   if( m_status != eInputIdle )
   {
      _ASSERTE( FALSE );
      return E_FAIL;
   }

   // First insert a mouse movement
   cMouseMove *pMM = new cMouseMove;
   pMM->m_ptClient.x = nX;
   pMM->m_ptClient.y = nY;

   m_actions.push_back( cActionList::value_type( eActionMouseMove, VSBridge::auto_ptr< cAction >( pMM ) ) );
   processMouseInput( eAction, *getLastInput() );

	return S_OK;
}

STDMETHODIMP cInputBuffer::get_Status(eInputStatus *pVal)
{
   _ASSERTE( pVal != NULL );

   *pVal = m_status;

	return S_OK;
}

STDMETHODIMP cInputBuffer::Run()
{
   postBuffer();

	return S_OK;
}

STDMETHODIMP cInputBuffer::NotifyBegin()
{
   m_status = eInputRunning;

   Fire_Begin();

	return S_OK;
}

STDMETHODIMP cInputBuffer::NotifyEnd()
{
   m_status = eInputIdle;

   Fire_End();

	return S_OK;
}

STDMETHODIMP cInputBuffer::SetMousePos(long nX, long nY)
{
   // Convert to screen coords
   POINT pt = { nX, nY };
   ::ClientToScreen( cManager::_p->m_hMain, &pt );
   ::SetCursorPos( pt.x, pt.y );

   // Force the mouse message on the into the window proc
   // This updates AC to be the new message location
   cManager::_p->m_pfnOld( cManager::_p->m_hMain, WM_MOUSEMOVE, 0, MAKELONG( nX, nY ) );

	return S_OK;
}

STDMETHODIMP cInputBuffer::NotifyPause()
{
   Fire_Pause();

	return S_OK;
}
