// DecalManager.cpp : Implementation of cDecal
#include "stdafx.h"
#include "Decal.h"
#include "DecalManager.h"

#include "DecalEnum.h"
#include "PluginSite.h"

/////////////////////////////////////////////////////////////////////////////
// cDecal

HRESULT cDecal::convertToken( std::string &strToken, std::string &strOut )
{
   USES_CONVERSION;

   struct cPathRegistry
   {
      LPCTSTR szToken,
         szKey,
         szValue;
   };

   static cPathRegistry _paths[] = {
      { _T( "ac" ), _T( "SOFTWARE\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ), _T( "Portal Dat" ) },
      { _T( "decal" ), _T( "SOFTWARE\\Decal\\Agent" ), _T( "AgentPath" ) } },
      *_end_paths = _paths + sizeof( _paths ) / sizeof( cPathRegistry );

   // There are two types of tokens, ones that contain a colon (object relative)
   // and the other kind, which just gets looked up in the registry
   int nColon = strToken.find_first_of( _T( ':' ) );
   if( nColon == std::string::npos )
   {
      // Take the whole token string and look it up
      for( cPathRegistry *pPath = _paths; pPath != _end_paths; ++ pPath )
      {
		  if( strToken.compare( pPath->szToken ) != 0 )
            continue;

         // Found the path now look it up in the registry
         DWORD dwCount = MAX_PATH;
         TCHAR szBuffer[ MAX_PATH ];
         RegKey key;
         key.Open( HKEY_LOCAL_MACHINE, pPath->szKey );
		 key.QueryStringValue (pPath->szValue, szBuffer, &dwCount);

         strOut += T2A( szBuffer );
         return S_OK;
      }

      // Token not found
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   // Attempt to convert the CLSID
   LPOLESTR strCLSID = A2OLE( strToken.substr( nColon + 1 ).c_str() );
   HRESULT hRes;
   CLSID clsid;

   if( strCLSID[ 0 ] == OLESTR( '{' ) )
      hRes = ::CLSIDFromString( strCLSID, &clsid );
   else
      hRes = ::CLSIDFromProgID( strCLSID, &clsid );

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

   CComPtr< IDecalEnum > pEnum;
   if( get_Configuration( _bstr_t( strToken.substr( 0, nColon ).c_str() ), clsid, &pEnum ) != S_OK )
   {
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   CComBSTR strResourcePath;
   if( FAILED( pEnum->get_ResourcePath( &strResourcePath ) ) )
   {
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   strOut += OLE2A( strResourcePath );
   return S_OK;
}

STDMETHODIMP cDecal::InitGraphics( IUnknown *pDirectDraw, IUnknown *pD3DDevice )
{
   m_pD = static_cast< IDirectDraw4 * >( pDirectDraw );
   m_pD3D = static_cast< IDirect3DDevice3 * >( pD3DDevice );

   for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
   {
      if( !( i->m_dwCaps & eServiceRender ) )
         continue;

      CComPtr< IDecalRender > pRender;
      HRESULT hRes = i->m_p->QueryInterface( &pRender );
      _ASSERTE( SUCCEEDED( hRes ) );

      pRender->ChangeDirectX();
   }

   return S_OK;
}

STDMETHODIMP cDecal::get_DirectDraw(REFIID iid, VOID * * ppvItf)
{
   return m_pD->QueryInterface( iid, ppvItf );
}

STDMETHODIMP cDecal::get_D3DDevice(REFIID iid, VOID * * ppvItf)
{
   return m_pD3D->QueryInterface( iid, ppvItf );
}

STDMETHODIMP cDecal::get_HWND(LONG * pVal)
{
	if (pVal == NULL)
		return E_POINTER;

   *pVal = reinterpret_cast< long >( m_hWnd );
   return S_OK;
}

STDMETHODIMP cDecal::put_HWND( LONG newVal )
{
   HWND newWnd = reinterpret_cast< HWND >( newVal );

   if( newWnd != m_hWnd )
   {
      m_hWnd = newWnd;

      for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
      {
         if( !( i->m_dwCaps & eServiceRender ) )
            continue;

         CComPtr< IDecalRender > pRender;
         HRESULT hRes = i->m_p->QueryInterface( &pRender );
         _ASSERTE( SUCCEEDED( hRes ) );

         pRender->ChangeHWND();
      }
   }

   return S_OK;
}

STDMETHODIMP cDecal::get_Focus(VARIANT_BOOL * pVal)
{
	if (pVal == NULL)
		return E_POINTER;

   *pVal = m_bFocus;
   
	return S_OK;
}

STDMETHODIMP cDecal::put_Focus( VARIANT_BOOL newVal )
{
   m_bFocus = newVal;

   return S_OK;
}

STDMETHODIMP cDecal::get_ScreenSize(long *pWidth, long *pHeight)
{
	if (pWidth == NULL || pHeight == NULL)
		return E_POINTER;

   RECT rc;
   ::GetClientRect( m_hWnd, &rc );

   *pWidth = rc.right - rc.left;
   *pHeight = rc.bottom - rc.top;

	return S_OK;
}

STDMETHODIMP cDecal::MapPath(BSTR pPath, BSTR * pMapped)
{
   USES_CONVERSION;

	if (pMapped == NULL)
   {
      _ASSERT( FALSE );
		return E_POINTER;
   }

   std::string str( OLE2A( pPath ) ),
      strOut;

   // If the base implementation, iterate over the %% pairs and make substitutions
   // where appropriate
   int nIterate = 0;
   for( ;; )
   {
      int nStart = str.find_first_of( _T( '%' ), nIterate );

      if( nStart == std::string::npos )
      {
         strOut += str.substr( nIterate );
         break;
      }

      int nEnd = str.find_first_of( _T( '%' ), nStart + 1 );

      HRESULT hRes = convertToken( str.substr( nStart + 1, nEnd - ( nStart + 1  ) ), strOut );
      if( FAILED( hRes ) )
         return hRes;

      if( nEnd == std::string::npos )
      {
         // Unterminated '%'
         _ASSERT( FALSE );
         return E_INVALIDARG;
      }

      nIterate = nEnd + 1;
      if( nIterate == str.length() )
      {
         // The string ended in a token, break now
         break;
      }
   }

   // We're done - convert the string
   *pMapped = A2BSTR( strOut.c_str() );

	return S_OK;
}

STDMETHODIMP cDecal::StartPlugins()
{
   if( !m_bServicesStarted )
   {
      HRESULT hRes = StartServices();
      if( FAILED( hRes ) )
         return hRes;
   }

   if( m_bPluginsStarted )
      return S_FALSE;

   // Walk through the list of services and send the plugin start notification
   {
      for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
         i->m_p->BeforePlugins();
   }

   CComPtr< IDecalEnum > pEnumPlugins;
   get_Configuration( _bstr_t( "Plugins" ), GUID_NULL, &pEnumPlugins );

   while( pEnumPlugins->Next() == S_OK )
   {
      VARIANT_BOOL bEnabled;
      pEnumPlugins->get_Enabled( &bEnabled );

      if( !bEnabled )
         continue;

      cPlugin plugin;
      pEnumPlugins->get_ComClass( &plugin.m_clsid );

      // Attempt to make an instance of the plugin
      CComPtr< IPlugin2 > pPlugin;
      HRESULT hRes = pEnumPlugins->CreateInstance( __uuidof( IPlugin2 ), reinterpret_cast< void ** >( &pPlugin ) );
      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         continue;
      }

      CComObject< cPluginSite > *pSite;
      CComObject< cPluginSite >::CreateInstance( &pSite );

      CComPtr< IPluginSite2 > pSiteRef = pSite;

      pSite->m_pPlugin = pPlugin;
      pSite->m_pDecal = this;

      hRes = pSite->m_pPlugin->Initialize( pSiteRef );
      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         continue;
      }

      plugin.m_pSite = pSite;
      m_plugins.push_back( plugin );

      // NOTE: If the plugin has not stored the IPluginSite2 interface pointer, it
      // will immediately terminate
   }

   m_bPluginsStarted = true;

	return S_OK;
}

STDMETHODIMP cDecal::StopPlugins()
{
   if( !m_bPluginsStarted )
      return S_FALSE;

   IPluginSite2 **pSites = reinterpret_cast< IPluginSite2 ** > ( _alloca ( sizeof ( IPluginSite2 * ) * m_plugins.size () ) );

   {
      IPluginSite2 **i2 = pSites;
      for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i, ++ i2 )
         *i2 = i->m_pSite;
   }

   {
      IPluginSite2 **end_sites = pSites + m_plugins.size ();
      for ( IPluginSite2 **i = pSites; i != end_sites; ++ i )
         ( *i )->Unload ();
   }

   _ASSERTE ( m_plugins.empty () );
   m_bPluginsStarted = false;

   // Walk through the list of services and send the plugin start notification
   {
      for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
         i->m_p->AfterPlugins();
   }

	return S_OK;
}

STDMETHODIMP cDecal::get_Object(BSTR strPath, REFIID iid, LPVOID *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   USES_CONVERSION;

   std::string str = OLE2A( strPath );

   // OK, strPath is formated as such, {plugin|service}\{{clsid|progid}[\text]
   int nOffset = str.find_first_of( _T( '\\' ) );
   if( nOffset == std::string::npos )
   {
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   // Find the next backslash
   int nclsid = str.find_first_of( _T( '\\' ), nOffset + 1 );

   // Attempt to convert it to a CLSID
   std::string strCLSID = str.substr( nOffset + 1, nclsid - ( nOffset + 1 ) );
   CLSID clsid;
   HRESULT hRes;

   if( strCLSID[ 0 ] == _T( '{' ) )
      hRes = ::CLSIDFromString( A2OLE( strCLSID.c_str() ), &clsid );
   else
      hRes = ::CLSIDFromProgID( A2OLE( strCLSID.c_str() ), &clsid );

   if( FAILED( hRes ) )
   {
      // The substring could not be converted to a proper CLSID
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   std::string collection = str.substr( 0, nOffset );

   CComPtr< IUnknown > pObject;

   if( collection.compare( "plugins" ) == 0 )
   {
      for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i )
      {
         if( i->m_clsid == clsid ) 
         {
            pObject = i->m_pSite->m_pPlugin;
            break;
         }
      }
   }
   else if( collection.compare( "services" ) == 0 )
   {
      for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
      {
         if( i->m_clsid == clsid )
         {
            pObject = i->m_p;
            break;
         }
      }
   }
   else
   {
      // Invalid prefix
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   if( pObject == NULL )
   {
      // Could not find a match for that clsid
      _ASSERT( FALSE );
      return E_INVALIDARG;
   }

   while( nclsid != std::string::npos )
   {
      CComPtr< IDecalDirectory > pDir;
      HRESULT hRes = pObject->QueryInterface( &pDir );
      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         return E_INVALIDARG;
      }

      int nEndclsid = str.find_first_of( _T( '\\' ), nclsid + 1 );
      pObject.Release();
      hRes = pDir->Lookup( _bstr_t( str.substr( nclsid + 1, nEndclsid - ( nclsid + 1 ) ).c_str() ), &pObject );
      if( FAILED( hRes ) )
         return hRes;

      nclsid = nEndclsid;
   }

	return pObject->QueryInterface( iid, pVal );
}

struct cItfCaps
{
   const IID *iid;
   DWORD m_dwMask;
};

STDMETHODIMP cDecal::StartServices()
{
   static cItfCaps _service_caps[] = {
      { &IID_IDecalRender, eServiceRender } },
         *_end_service_caps = _service_caps + sizeof( _service_caps ) / sizeof( cItfCaps );

   if( m_bServicesStarted )
      // They've already been started
      return S_FALSE;

	m_pHooks.CoCreateInstance( _bstr_t( "Decal.ACHooks" ), NULL, CLSCTX_INPROC_SERVER );
	m_pHooks->SetDecal( reinterpret_cast< IUnknown * >( this ) );

   CComPtr< IDecalEnum > pEnumServices;
   get_Configuration( _bstr_t( "Services" ), GUID_NULL, &pEnumServices );

   while( pEnumServices->Next() == S_OK )
   {
      VARIANT_BOOL bEnabled;
      HRESULT hRes = pEnumServices->get_Enabled ( &bEnabled );
      _ASSERTE ( SUCCEEDED ( hRes ) );

      if ( !bEnabled )
         continue;

      cService service;
      pEnumServices->get_ComClass( &service.m_clsid );
      hRes = pEnumServices->CreateInstance( __uuidof( IDecalService ), reinterpret_cast< void ** >( &service.m_p ) );

      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         // Unlike plugins, all services must initialize correctly
         return hRes;
      }

      hRes = service.m_p->Initialize( this );

      if( FAILED( hRes ) )
      {
         _ASSERT( FALSE );
         // The service could not initialize for some reason
         return hRes;
      }

      service.m_dwCaps = 0;
      CComPtr< IUnknown > pUnkCaps;
      for( cItfCaps *i_caps = _service_caps; i_caps != _end_service_caps; ++ i_caps )
      {
         HRESULT hRes = service.m_p->QueryInterface( *i_caps->iid, reinterpret_cast< void ** >( &pUnkCaps ) );
         if( SUCCEEDED( hRes ) )
         {
            service.m_dwCaps |= i_caps->m_dwMask;
            pUnkCaps.Release();
         }
      }

      m_services.push_back( service );
   }

   m_bServicesStarted = true;

	return S_OK;
}

STDMETHODIMP cDecal::get_Configuration(BSTR strType, REFCLSID clsidAdvance, IDecalEnum **pVal)
{
	if (pVal == NULL)
   {
      _ASSERT( FALSE );
		return E_POINTER;
   }

   CComObject< cDecalEnum > *pEnum;
   HRESULT hRes = CComObject< cDecalEnum >::CreateInstance( &pEnum );

   if( FAILED( hRes ) )
      return hRes;

   CComPtr< IUnknown > pUnk = pEnum;

   if( !pEnum->Initialize( this, strType ) )
      return E_FAIL;

   if( clsidAdvance != GUID_NULL )
   {
      HRESULT hRes = pEnum->Advance( clsidAdvance );
      if ( FAILED ( hRes ) )
         return hRes;
   }
   else
   {
      HRESULT hRes = pEnum->Begin();
      if ( FAILED ( hRes ) )
         return hRes;
   }

	return pUnk->QueryInterface( pVal );
}

STDMETHODIMP cDecal::StopServices()
{
   if( m_bPluginsStarted )
      StopPlugins( );

   if( !m_bServicesStarted )
      return S_FALSE;

	try
	{
		while( !m_services.empty() )
		{
		  cServiceList::iterator i = ( m_services.end() - 1 );

		  i->m_p->Terminate();
		  
		  m_services.erase( i );
		}
	}

	catch( ... )
	{
	}

	if( m_pHooks.p )
		m_pHooks.Release( );

	return S_OK;
}

STDMETHODIMP cDecal::get_Plugin(REFCLSID clsid, REFIID iid, LPVOID *pVal)
{
   for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i )
   {
      if( i->m_clsid == clsid )
         return i->m_pSite->m_pPlugin->QueryInterface( iid, pVal );
   }

	return E_INVALIDARG;
}

STDMETHODIMP cDecal::get_Service(REFCLSID clsid, REFIID iid, LPVOID *pVal)
{
   for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
   {
      if( i->m_clsid == clsid )
         return i->m_p->QueryInterface( iid, pVal );
   }

	return E_INVALIDARG;
}

STDMETHODIMP cDecal::Render2D()
{
   for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
   {
      if( !( i->m_dwCaps & eServiceRender ) )
         continue;

      CComPtr< IDecalRender > pRender;
      HRESULT hRes = i->m_p->QueryInterface( &pRender );
      _ASSERTE( SUCCEEDED( hRes ) );

      pRender->Render2D();
   }

	return S_OK;
}

STDMETHODIMP cDecal::Render3D()
{
   for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
   {
      if( !( i->m_dwCaps & eServiceRender ) )
         continue;

      CComPtr< IDecalRender > pRender;
      HRESULT hRes = i->m_p->QueryInterface( &pRender );
      _ASSERTE( SUCCEEDED( hRes ) );

      pRender->Render3D();
   }

	return S_OK;
}

STDMETHODIMP cDecal::get_Hooks(IACHooks** pVal)
{
	if( m_pHooks == NULL )
		return E_FAIL;

	m_pHooks->QueryInterface( pVal );
	return S_OK;
}