// DecalRes.h : Declaration of the cDecalRes

#ifndef __DECALRES_H_
#define __DECALRES_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// cDecalRes
class ATL_NO_VTABLE cDecalRes : 
	public CComObjectRootEx<CComMultiThreadModel>,
	public CComCoClass<cDecalRes, &CLSID_DecalRes>,
	public IDispatchImpl<IDecalRes, &IID_IDecalRes, &LIBID_Decal>,
   public IConnectionPoint,
   public IConnectionPointContainer,
   public IProvideClassInfo2
{
public:
	cDecalRes()
      : m_dwNextCookie( 1 )
	{
	}

   // This object iterates over our one connection
   // point.
   class cEnumRes
      : public CComObjectRootEx<CComMultiThreadModel>,
      public IEnumConnectionPoints
   {
   public:
      BEGIN_COM_MAP(cEnumRes)
         COM_INTERFACE_ENTRY(IEnumConnectionPoints)
      END_COM_MAP()

      bool m_bEnd;
      CComPtr< IConnectionPoint > m_pCP;

      void init( IConnectionPoint *pCP )
      {
         m_pCP = pCP;
         m_bEnd = false;
      }

   public:
      // IEnumConnectionPoints
      STDMETHOD(Next)( ULONG cConnections, IConnectionPoint **rgpcn, ULONG *pcFetched )
      {
         if( m_bEnd )
         {
            *pcFetched = 0;
            return S_FALSE;
         }

         if( cConnections == 0 )
         {
            *pcFetched = 0;
            return S_OK;
         }

         m_bEnd = true;
         *pcFetched = 1;
         return m_pCP->QueryInterface( rgpcn );
      }

      STDMETHOD(Skip)( ULONG cConnections )
      {
         if( cConnections == 0 )
            return S_OK;

         m_bEnd = true;
         return S_OK;
      }

      STDMETHOD(Reset)()
      {
         m_bEnd = false;
         return S_OK;
      }

      STDMETHOD(Clone)(IEnumConnectionPoints **ppNew )
      {
         CComObject< cEnumRes > *pEnum;
         CComObject< cEnumRes >::CreateInstance( &pEnum );

         pEnum->m_pCP = m_pCP;
         pEnum->m_bEnd = m_bEnd;

         return pEnum->QueryInterface( IID_IEnumConnectionPoints, reinterpret_cast< void ** >( ppNew ) );
      }
   };

   // This object implements the event object - Fire resolves
   // to dispatching an event to our parent. This object is added
   // into the namespace of the resource.
   class cResEvent
      : public CComObjectRootEx<CComMultiThreadModel>,
      public IDispatch
   {
   public:
   BEGIN_COM_MAP(cResEvent)
      COM_INTERFACE_ENTRY(IDispatch)
   END_COM_MAP()

      CComPtr< ITypeInfo > m_pItf;
      cDecalRes *m_pRes;
      DISPID m_dispid;

      HRESULT load( MSXML::IXMLDOMElementPtr &pElement )
      {
         // TODO: load the dispid and Fire paramters and create the temporary typeinfo
      }

   public:
      // IDispatch
      STDMETHOD(GetTypeInfoCount)( UINT *pctinfo )
      {
         if( pctinfo == NULL )
            return E_POINTER;

         *pctinfo = 1;
         return S_OK;
      }

      STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID, ITypeInfo **ppTI )
      {
         if( iTInfo != 0 )
            return E_INVALIDARG;

         if( ppTI == NULL )
            return E_POINTER;

         return m_pItf->QueryInterface( ppTI );
      }

      STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID, DISPID *rgDispID)
      {
         if( rgDispID == NULL || rgszNames == NULL )
            // By spec
            return E_POINTER;

         if( !::InlineIsEqualGUID( riid, IID_NULL ) )
            // By spec
            return DISP_E_UNKNOWNINTERFACE;

         static LPOLESTR _szFire = OLESTR( "Fire" );

         for( UINT i = 0; i < cNames; ++ i )
         {
            if( ::wcscmp( _szFire, rgszNames[ i ] ) == 0 )
               rgDispID[ i ] = 1;
            else
               rgDispID[ i ] = -1;
         }

         return S_OK;
      }

      STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID, WORD wFlags, DISPPARAMS *pDispParams,
         VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *pArgError )
      {
         // Do all the checks in here
         if( dispIdMember != 1 )
            return DISP_E_MEMBERNOTFOUND;

         if( !::InlineIsEqualGUID( riid, IID_NULL ) )
            return DISP_E_UNKNOWNINTERFACE;

         if( wFlags != DISPATCH_METHOD )
            return DISP_E_MEMBERNOTFOUND;

         return m_pRes->dispatchEvent( m_dispid, pDispParams, pVarResult, pExcepInfo, pArgError );
      }
   };

   // Generated TypeInfo
   CComPtr< ICreateTypeLib2 > m_pLib;
   CComPtr< ITypeInfo > m_pCoClass;
   CComPtr< ICreateTypeInfo > m_pSource;

   HRESULT GetSourceIID( IID *pIID )
   {
      if( !m_pSource.p )
         // Not yet initialized - bad
         return E_FAIL;

      CComPtr< ITypeInfo > pTI;
      HRESULT hRes = m_pSource->QueryInterface( &pTI );
      if( FAILED( hRes ) )
         return hRes;

      TYPEATTR *pTA;
      pTI->GetTypeAttr( &pTA );

      *pIID = pTA->guid;
      pTI->ReleaseTypeAttr( pTA );

      return S_OK;
   }

   // Connection point members
   typedef std::deque< std::pair< DWORD, CComPtr< IDispatch > > > cCPList;
   DWORD m_dwNextCookie;
   cCPList m_cp;

   HRESULT dispatchEvent( DISPID nID, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr )
   {
      // We simply pass the invoke on, the event object calling us
      // will correctly remap the ID.
      for( cCPList::iterator i = m_cp.begin(); i != m_cp.end(); ++ i )
         i->second->Invoke( nID, IID_NULL, 0, DISPATCH_METHOD, pDispParams, pVarResult, pExcepInfo, puArgErr );

      return S_OK;
   }

   // Series of methods to create the type library
   HRESULT getLibID( MSXML::IXMLDOMDocumentPtr &pdoc, GUID *pGUID );
   bool getTypelibFilename( MSXML::IXMLDOMDocumentPtr &pdoc, BSTR *pbstrFilename );
   HRESULT initTypelib( MSXML::IXMLDOMDocumentPtr &pdoc, BSTR strFilename );
   HRESULT scanTemplate( MSXML::IXMLDOMElementPtr &ptemp, ITypeInfo **ppCoClass, ITypeInfo **ppSource );

DECLARE_REGISTRY_RESOURCEID(IDR_DECALRES)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(cDecalRes)
	COM_INTERFACE_ENTRY(IDecalRes)
	COM_INTERFACE_ENTRY(IDispatch)
   COM_INTERFACE_ENTRY(IConnectionPoint)
   COM_INTERFACE_ENTRY(IConnectionPointContainer)
   COM_INTERFACE_ENTRY(IProvideClassInfo)
   COM_INTERFACE_ENTRY(IProvideClassInfo2)
END_COM_MAP()

// IDecalRes
public:
   // IConnectionPointContainer
   STDMETHOD(EnumConnectionPoints)( IEnumConnectionPoints **ppEnum )
   {
      CComObject< cEnumRes > *pEnum;
      CComObject< cEnumRes >::CreateInstance( &pEnum );

      pEnum->init( this );

      return pEnum->QueryInterface( IID_IEnumConnectionPoints, reinterpret_cast< void ** >( ppEnum ) );
   }

   STDMETHOD(FindConnectionPoint)( REFIID iid, IConnectionPoint **ppCP )
   {
      if( ppCP == NULL )
         return E_POINTER;

      IID iidSource;
      HRESULT hRes = GetSourceIID( &iidSource );
      if( FAILED( hRes ) )
         return hRes;

      if( ::InlineIsEqualGUID( iid, GUID_NULL ) ||
         ::InlineIsEqualGUID( iid, iidSource ) )
      {
         // Return the connection point
         return static_cast< IConnectionPoint * >( this )->QueryInterface( ppCP );
      }

      // Not found (duh, it's not the only connection)
      return CONNECT_E_NOCONNECTION;
   }

   // IConnectionPoint
   STDMETHOD(GetConnectionInterface)( IID *piid )
   {
      if( piid == NULL )
         return E_POINTER;
      return GetSourceIID( piid );
   }

   STDMETHOD(GetConnectionPointContainer)( IConnectionPointContainer **ppCPC )
   {
      return static_cast< IConnectionPoint * >( this )->QueryInterface( ppCPC );
   }

   STDMETHOD(Advise)(IUnknown *pUnk, DWORD *pdwCookie)
   {
      if( pdwCookie == NULL || pUnk == NULL )
         return E_POINTER;

      CComPtr< IDispatch > pDisp;
      HRESULT hRes = pUnk->QueryInterface( &pDisp );
      if( FAILED( hRes ) )
         return hRes;

      m_cp.push_back( cCPList::value_type( m_dwNextCookie, pDisp ) );
      *pdwCookie = ( m_dwNextCookie ++ );

      return S_OK;
   }

   STDMETHOD(Unadvise)( DWORD dwCookie )
   {
      // Find the matching cookie
      for( cCPList::iterator i = m_cp.begin(); i != m_cp.end(); ++ i )
      {
         if( i->first == dwCookie )
         {
            m_cp.erase( i );
            return S_OK;
         }
      }

      return CONNECT_E_NOCONNECTION;
   }

   STDMETHOD(EnumConnections)(IEnumConnections **)
   {
      // We don't support enum'ing connections
      return E_NOTIMPL;
   }

   // IProvideClassInfo
   STDMETHOD(GetClassInfo)(ITypeInfo **ppTI)
   {
      return m_pCoClass->QueryInterface( ppTI );
   }

   // IProvideClassInfo2
   STDMETHOD(GetGUID)(DWORD dwGuidKind, GUID *pGUID)
   {
      if( dwGuidKind != GUIDKIND_DEFAULT_SOURCE_DISP_IID )
         return E_INVALIDARG;

      if( pGUID == NULL )
         return E_POINTER;

      return GetSourceIID( pGUID );
   }
};

#endif //__DECALRES_H_
