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

#include "InputService.h"

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

void cInputBuffer::FinalRelease()
{
   if( cInputService::g_p != NULL && cInputService::g_p->m_pActive == this )
      Stop();
}

bool cInputBuffer::checkState()
{
   if( cInputService::g_p == NULL || cInputService::g_p->m_pActive == this )
   {
      // There must be an input service and it must be inactive
      _ASSERT( FALSE );
      return false;
   }

   return true;
}

HRESULT cInputBuffer::addItem( BSTR strInit, IInputAction **ppAction )
{
   USES_CONVERSION;
   LPTSTR szType = OLE2T( strInit );

   size_t iInitData = ::_tcscspn( szType, _T( ":" ) );

   if( iInitData == -1 )
      return E_INVALIDARG;

   szType[ iInitData ] = _T( '\0' );
   LPCTSTR szInitData = szType + iInitData + 1;
   ::_tcslwr( szType );

   for( cInputService::cActionTypeList::iterator i = cInputService::g_p->m_actiontypes.begin(); i != cInputService::g_p->m_actiontypes.end(); ++ i )
   {
      if( ::_tcscmp( szType, i->szAction ) == 0 )
         break;
   }

   if( i == cInputService::g_p->m_actiontypes.end() )
      return E_INVALIDARG;

   // Get the configuration object for this inputaction
   static _bstr_t _strActions( _T( "InputActions" ) );
   CComPtr< IDecalEnum > pInputEnum;

   if( cInputService::g_p->m_pDecal->get_Configuration( _strActions, i->m_clsid, &pInputEnum ) != S_OK )
      // What used to be there is gone
      return E_FAIL;

   HRESULT hRes = pInputEnum->CreateInstance( __uuidof( IInputAction ), reinterpret_cast< void ** >( ppAction ) );
   if( FAILED( hRes ) )
      // Could not create our object
      return hRes;

   return ( *ppAction )->Initialize( this, _bstr_t( szInitData ) );
}

void cInputBuffer::runActions()
{
   m_bWaiting = false;

   while( m_play != m_entries.end() )
   {
      HRESULT hRes;
      switch( m_play->m_eType )
      {
      case eExecute:
         hRes = m_play->m_pAction->Execute();
         break;
         
      case ePush:
         m_playstack.push( &*m_play );
         hRes = m_play->m_pAction->Push();
         break;

      case ePop:
         m_playstack.pop();
         hRes = m_play->m_pAction->Pop();
         break;

      default:
         hRes = E_FAIL;
      }

      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         Stop();
         return;
      }

      if( m_bWaiting || cInputService::g_p->m_pActive != this )
         // If we're now waiting, return from our actions and continue
         return;

      ++ m_play;
   }

   // We're done here
   Stop();
}

STDMETHODIMP cInputBuffer::get_Tag(VARIANT *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   return ::VariantCopy( pVal, &m_tag );
}

STDMETHODIMP cInputBuffer::put_Tag(VARIANT newVal)
{
   m_tag = newVal;

	return S_OK;
}

STDMETHODIMP cInputBuffer::Add(BSTR strCommand)
{
   if( !checkState() )
      return E_FAIL;

   cEntry e;
   HRESULT hRes = addItem( strCommand, &e.m_pAction );
   if( FAILED( hRes ) )
      return hRes;

   e.m_eType = eExecute;
   m_entries.push_back( e );

	return S_OK;
}

STDMETHODIMP cInputBuffer::Push(BSTR Command)
{
   if( !checkState() )
      return E_FAIL;

   cEntry e;
   HRESULT hRes = addItem( Command, &e.m_pAction );
   if( FAILED( hRes ) )
      return hRes;

   VARIANT_BOOL bStackable;
   e.m_pAction->get_Stackable( &bStackable );

   if( !bStackable )
      return E_INVALIDARG;

   e.m_eType = ePush;
   m_entries.push_back( e );
   m_insertstack.push( &m_entries.back() );

	return S_OK;
}

STDMETHODIMP cInputBuffer::Pop()
{
   if( !checkState() )
      return E_FAIL;

   if( m_insertstack.size() == 0 )
   {
      _ASSERT( FALSE );
      return E_FAIL;
   }

   cEntry e;
   e.m_eType = ePop;
   e.m_pAction = m_insertstack.top()->m_pAction;

   m_entries.push_back( e );
   m_insertstack.pop();

	return S_OK;
}

STDMETHODIMP cInputBuffer::get_CanRun(VARIANT_BOOL *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( cInputService::g_p == NULL )
   {
      // There must be an input service
      _ASSERT( FALSE );
      return E_FAIL;
   }

   *pVal = ( cInputService::g_p->m_pActive == NULL ) ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

STDMETHODIMP cInputBuffer::Run()
{
   if( cInputService::g_p == NULL || cInputService::g_p->m_pActive != NULL )
   {
      // There must be an input service and it must be inactive
      _ASSERT( FALSE );
      return E_FAIL;
   }

   // First check if the insert stack is incomplete
   while( m_insertstack.size() > 0 )
      Pop();

   // Reset the cursors
   while( m_playstack.size() > 0 )
      m_playstack.pop();

   m_play = m_entries.begin();
   cInputService::g_p->m_pActive = this;

   // Reset the actions
   for( cEntryList::iterator i = m_entries.begin(); i != m_entries.end(); ++ i )
      i->m_pAction->Reset();

   Fire_Begin( this );

   // Lock down the user input
   ::BlockInput( TRUE );
   runActions();

	return S_OK;
}

STDMETHODIMP cInputBuffer::Stop()
{
   if( cInputService::g_p == NULL )
   {
      // There must be an input service
      _ASSERT( FALSE );
      return E_FAIL;
   }

   if( cInputService::g_p->m_pActive == NULL )
      return S_FALSE;

   if( cInputService::g_p->m_pActive != this )
      return E_FAIL;

   // Stop immediately, flush all entries in the stack
   while( m_playstack.size() > 0 )
   {
      m_playstack.top()->m_pAction->Pop();
      m_playstack.pop();
   }

   cInputService::g_p->m_pActive = NULL;
   ::BlockInput( FALSE );

   Fire_End( this );

	return S_OK;
}

STDMETHODIMP cInputBuffer::Delay(long Time, VARIANT_BOOL Advance)
{
   if( Advance )
      ++ m_play;

   m_bWaiting = true;
   m_nUntilTime = ::timeGetTime() + Time;

	return S_OK;
}

STDMETHODIMP cInputBuffer::FireEvent(long nEventID, VARIANT vParam)
{
   Fire_Event( this, nEventID, vParam );
	
	return S_OK;
}

STDMETHODIMP cInputBuffer::MoveMouse(long X, long Y)
{
   if( cInputService::g_p == NULL )
   {
      // There must be an input service
      _ASSERT( FALSE );
      return E_FAIL;
   }

   // These are already traslated to screen coords
   cInputService *pis = cInputService::g_p;
   if( pis->m_eWindows == cInputService::eWindows98NoMemLocs )
      return E_FAIL;

   // Otherwise, get the window proc and send some info to move the mouse
   WNDPROC wndProc = reinterpret_cast< WNDPROC >( ::GetWindowLong( pis->m_hWndHook, GWL_WNDPROC ) );
   POINT ptScreen = { X, Y };
   ::ClientToScreen( pis->m_hWndHook, &ptScreen );

   ::SetCursorPos( ptScreen.x, ptScreen.y );
   POINTS pts = { X, Y };
   wndProc( pis->m_hWndHook, WM_MOUSEMOVE, 0, *reinterpret_cast< DWORD * >( &pts ) );

   // Special additional functionality for win98
   if( pis->m_eWindows == cInputService::eWindows98WithMemLocs )
   {
      pis->m_pnOffset1[ 0 ] = X;
      pis->m_pnOffset1[ 1 ] = Y;

      pis->m_pnOffset2[ 0 ] = X;
      pis->m_pnOffset2[ 1 ] = Y;
      pis->m_pnOffset2[ 2 ] = X;
      pis->m_pnOffset2[ 3 ] = Y;
      pis->m_pnOffset2[ 4 ] = X;
      pis->m_pnOffset2[ 5 ] = Y;

      // Delay a couple frames to be sure it's taken effect
      Delay( 100, VARIANT_TRUE );
   }

	return S_OK;
}

STDMETHODIMP cInputBuffer::get_Service(IInputService **pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( cInputService::g_p == NULL )
   {
      // There must be an input service
      _ASSERT( FALSE );
      return E_FAIL;
   }

   return static_cast< IInputService * >( cInputService::g_p )->QueryInterface( pVal );
}
