// Prefilter.cpp : Implementation of cPrefilter
#include "stdafx.h"
#include "DecalFilters.h"
#include "Prefilter.h"

/////////////////////////////////////////////////////////////////////////////
// cPrefilter

class cFieldRule
: public cPrefilter::cFilterRule
{
public:
   _variant_t m_strField;
   bool m_bReset;

   static _bstr_t _strField,
      _strReset;

   cFieldRule()
      : m_bReset( false )
   {
   }

   bool loadField( MSXML::IXMLDOMElementPtr &pElement )
   {
      m_strField = pElement->getAttribute( _strField );

      _variant_t vReset = pElement->getAttribute( _strReset );
      if( vReset.vt != VT_NULL )
         m_bReset = vReset;

#ifdef _DEBUG
      if( m_bReset )
         _ASSERTE( m_strField.vt == VT_BSTR );
#endif

      return true;
   }

   void get( _variant_t &vValue, IMessageIterator *pMsg )
   {
      if( m_bReset )
         pMsg->Reset();

      if( m_strField.vt == VT_BSTR )
         pMsg->get_Next( m_strField, &vValue );
      else
         pMsg->get_Current( &vValue );
   }
};

class cEndRule
: public cPrefilter::cFilterRule
{
public:
   virtual bool test( IMessageIterator * )
   {
      return true;
   }
};

_bstr_t cFieldRule::_strField( _T( "field" ) );
_bstr_t cFieldRule::_strReset( _T( "reset" ) );

class cTestRule
: public cFieldRule
{
public:
   virtual bool test( IMessageIterator *pMsg )
   {
      _variant_t vValue;

      get( vValue, pMsg );

      return ( vValue.vt != VT_NULL );
   }
};

class cEqualsRule
: public cFieldRule
{
public:
   _variant_t m_vValue;

   virtual bool test( IMessageIterator *pMsg )
   {
      _variant_t vValue;

      get( vValue, pMsg );

      if( vValue.vt == VT_NULL || m_vValue.vt == VT_NULL )
         // Null values are never equal, use cTestRule
         return false;

      if( vValue.vt != m_vValue.vt )
      {
         // Attempt to convert them to the same type - this will typically happen
         // the first time the rule is invoked. This will speed up comparisons
         // in the future
         HRESULT hRes = ::VariantChangeType( &m_vValue, &m_vValue, 0, vValue.vt );
         if( FAILED( hRes ) )
         {
            _ASSERT( FALSE );

            // Could not convert the value - clear it so this rule becomes, disabled
            ::VariantClear( &m_vValue );
            m_vValue.vt = VT_NULL;

            return false;
         }
      }

      // Compare the values
      return( ::VarCmp( &vValue, &m_vValue, LOCALE_USER_DEFAULT, 0 ) == VARCMP_EQ );
   }
};

class cContainsRule
: public cFieldRule
{
public:
   _bstr_t m_strValue;

   virtual bool test( IMessageIterator *pMsg )
   {
      _variant_t vValue;

      get( vValue, pMsg );

      if( vValue.vt != VT_BSTR )
         // Null values are never equal, use cTestRule
         return false;

      // Use the std::algorithm search
      LPOLESTR szValueBegin = vValue.bstrVal,
         szValueEnd = vValue.bstrVal + ::SysStringLen( vValue.bstrVal ),
         szSearchBegin = m_strValue,
         szSearchEnd = szSearchBegin + m_strValue.length();

      LPOLESTR szFound = std::search( szValueBegin, szValueEnd, szSearchBegin, szSearchEnd );
      return( szFound != szValueEnd );
   }
};

class cMaskRule
: public cFieldRule
{
public:
   long m_nValue;

   virtual bool test( IMessageIterator *pMsg )
   {
      long nValue;

      // Custom get
      HRESULT hRes = pMsg->get_NextInt( m_strField.bstrVal, &nValue );
      if( FAILED( hRes ) )
         return false;

      return !!( nValue & m_nValue );
   }
};

bool cPrefilter::testRules( cFilterRule *pRule, IMessageIterator *pMsg )
{
   if( !pRule->test( pMsg ) )
      return false;

   // If there is a fire ID, fire it now
   if( pRule->m_nFire != cFilterRule::eFireChildren )
   {
      CComPtr< IMessage > pMsgObj;
      pMsg->get_Message( &pMsgObj );
      Fire_Event( pRule->m_nFire, pMsgObj );
      return true;
   }

   // If it has no children ... carry on
   if( pRule->m_childRules.empty() )
      return true;

   // Test our children
   for( cFilterRule::cFilterRuleList::iterator i = pRule->m_childRules.begin(); i != pRule->m_childRules.end(); ++ i )
   {
      if( testRules( i->get(), pMsg ) )
         return true;
   }

   return false;
}

cPrefilter::cFilterRule *cPrefilter::loadRule( MSXML::IXMLDOMElementPtr &pElement )
{
   USES_CONVERSION;

   // Load the current rule, first figure out the type
   static _bstr_t _strMessage( _T( "message" ) ),
      _strFire( _T( "fire" ) ),
      _strTest( _T( "test" ) ),
      _strEqual( _T( "equal" ) ),
      _strContain( _T( "contains" ) ),
      _strMask( _T( "mask" ) ),
      _strField( _T( "field" ) ),
      _strNext( _T( "next" ) ),
      _strValue( _T( "value" ) ),
      _strEnd( _T( "end" ) );

   try
   {
      _bstr_t strElement = pElement->tagName;

      VSBridge::auto_ptr< cFilterRule > pFilter;

      if( strElement == _strMessage ||
         strElement == _strFire )
      {
         pFilter = VSBridge::auto_ptr< cFilterRule >( new cFilterRule );
      }
      else if( strElement == _strTest )
      {
         cTestRule *pRule = new cTestRule;
         pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );

         if( !pRule->loadField( pElement ) )
            return NULL;
      }
      else if( strElement == _strEqual )
      {
         cEqualsRule *pRule = new cEqualsRule;
         pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );

         if( !pRule->loadField( pElement ) )
            return NULL;

         pRule->m_vValue = pElement->getAttribute( _strValue );
         if( pRule->m_vValue.vt == VT_NULL )
         {
            // Missing required field
            _ASSERT( FALSE );
            return false;
         }
      }
      else if( strElement == _strContain )
      {
         cContainsRule *pRule = new cContainsRule;
         pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );

         if( !pRule->loadField( pElement ) )
            return NULL;

         _variant_t vValue = pElement->getAttribute( _strValue );
         if( vValue.vt != VT_BSTR )
         {
            // Missing required field or incorrect type
            _ASSERT( FALSE );
            return false;
         }

         pRule->m_strValue = vValue;
      }
      else if( strElement == _strMask )
      {
         cMaskRule *pRule = new cMaskRule;
         pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );

         if( !pRule->loadField( pElement ) )
            return NULL;

         _variant_t vValue = pElement->getAttribute( _strValue );
         if( vValue.vt == VT_NULL )
         {
            // Missing required field
            _ASSERT( FALSE );
            return false;
         }

         // Attempt to convert the value from hex
         if( ::_stscanf( OLE2T( vValue.bstrVal ), _T( "%X" ), &pRule->m_nValue ) != 1 )
         {
            _ASSERT( FALSE );
            return NULL;
         }
      }
      else if( strElement == _strEnd )
         pFilter = VSBridge::auto_ptr< cFilterRule >( new cEndRule );
      else
         return NULL;

      _variant_t vFire = pElement->getAttribute( _strFire );

      // Convert the fire type, then carry on
      if( vFire.vt != VT_NULL )
         pFilter->m_nFire = vFire;
      else
      {
         // Select all children - all element children
         MSXML::IXMLDOMNodeListPtr pRules = pElement->selectNodes( _T( "*" ) );
         for( MSXML::IXMLDOMElementPtr pChildRule = pRules->nextNode(); pChildRule; pChildRule = pRules->nextNode() )
         {
            VSBridge::auto_ptr< cFilterRule > pChildFilter( loadRule( pChildRule ) );
            if( pChildFilter.get() == NULL )
               continue;

            // Load the children
            pFilter->m_childRules.push_back( pChildFilter );
         }
      }

      return pFilter.release();
   }
   catch( _com_error &e )
   {
      HRESULT hRes = e.Error();
      _ASSERTE( FALSE );
   }

   return NULL;
}

STDMETHODIMP cPrefilter::Initialize(INetService *pService)
{
   USES_CONVERSION;

   CComPtr< IDecal > pDecal;
   pService->get_Decal( &pDecal );

   CComBSTR strMappedFile;
   HRESULT hRes = pDecal->MapPath( m_strFile, &strMappedFile );

   if( FAILED( hRes ) )
      // Bad token in the filename
      return hRes;

   MSXML::IXMLDOMDocumentPtr pFilter;
   hRes = ::CoCreateInstance( __uuidof( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER,
      __uuidof( MSXML::IXMLDOMDocument ), reinterpret_cast< void ** >( &pFilter ) );

   if( FAILED( hRes ) )
      return hRes;

   bool bLoad = !!pFilter->load( strMappedFile.m_str );

   if( !bLoad )
   {
      // The document failed to load, get the error info for posterity
      MSXML::IXMLDOMParseErrorPtr pErr = pFilter->parseError;

      long nCode = pErr->errorCode;
      long nFilePos = pErr->filepos;
      long nLine = pErr->line;
      long nLinePos = pErr->linepos;
      _bstr_t strReason = pErr->reason;
      _bstr_t strText = pErr->srcText;

      TCHAR szError[ 1024 ];
      ::_stprintf( szError, _T( "0x%08X (%i, %i): %s" ),
         nCode, nLine, nLinePos, OLE2T( strReason ) );

      /*
      long nHWND;
      m_pSite->get_HWND( &nHWND );
      */

      ::MessageBox( NULL, szError, _T( "XML Parse Error" ), MB_ICONERROR | MB_OK );

      // Give the user a chance to break and look at this lovely info
      _ASSERTE( FALSE );

      return E_FAIL;
   }

   // Looking good - now we'll load el'schema
   MSXML::IXMLDOMNodeListPtr pMessages = pFilter->selectNodes( _T( "/filter/message" ) );
   for( MSXML::IXMLDOMElementPtr pMessage = pMessages->nextNode(); pMessage; pMessage = pMessages->nextNode() )
   {
      _variant_t vMessage = pMessage->getAttribute( _T( "type" ) ),
         vFire = pMessage->getAttribute( _T( "fire" ) );

      if( vMessage.vt != VT_BSTR )
      {
         _ASSERT( FALSE );
         continue;
      }

      // Attempt to convert the message type
      long nMessage;
      if( ::_stscanf( OLE2T( vMessage.bstrVal ), _T( "%X" ), &nMessage ) != 1 )
      {
         // Invalid message ID format
         _ASSERT( FALSE );
         continue;
      }

      VSBridge::auto_ptr<cFilterRule> pMessageRule( loadRule( pMessage ) );
      if( pMessageRule.get() == NULL )
         continue;

      m_messageRules.insert( cMessageRuleList::value_type( nMessage, pMessageRule ) );
   }

	return S_OK;
}

STDMETHODIMP cPrefilter::DispatchServer( IMessage2 *pMsg )
{
   long nID;
   pMsg->get_Type( &nID );

   // Look for a type
   cMessageRuleList::iterator i_msg = m_messageRules.find( nID );
   if( i_msg == m_messageRules.end() )
      return S_OK;

   CComPtr< IMessageIterator > pMembers;
   pMsg->get_Begin( &pMembers );
   testRules( i_msg->second.get(), pMembers );

   return S_OK;
}

STDMETHODIMP cPrefilter::CreateInstance( IDecalEnum *pInitData, REFIID iid, LPVOID *pObject )
{
   CComVariant vFile;
   HRESULT hRes = pInitData->get_Property( _bstr_t( "File" ), &vFile );

   if( FAILED( hRes ) )
      return hRes;

   if( vFile.vt != VT_BSTR )
   {
      // The surrogate must know the file
      _ASSERT( FALSE );
      return E_FAIL;
   }

   m_strFile = vFile.bstrVal;
   return static_cast< IDecalSurrogate * >( this )->QueryInterface( iid, pObject );
}
