// DecalEnum.cpp : Implementation of cDecalEnum
#include "stdafx.h"
#include "Decal.h"
#include "DecalEnum.h"

/////////////////////////////////////////////////////////////////////////////
// cDecalEnum

cDecalEnum::~cDecalEnum()
{
   // Write out the order if there were changes
   if ( !m_bOrderChanged )
      return;

	USES_CONVERSION;

   std::string strOrder;
   strOrder.reserve ( m_keys.size () );

   for ( cKeyList::iterator i = m_keys.begin (); i != m_keys.end(); ++ i )
      strOrder += i->m_letter;

   // Compile the new order from the 
   RegKey hkGroup;
   if( hkGroup.Create( HKEY_LOCAL_MACHINE, m_strGroupKey.c_str() ) != ERROR_SUCCESS ) 
   {
      _ASSERTE ( FALSE );
      return;
   }

   hkGroup.SetStringValue (_T("Order"), strOrder.c_str ());
}

bool cDecalEnum::Initialize( IDecal *pDecal, BSTR strKey )
{
   _ASSERTE( pDecal != NULL );

   USES_CONVERSION;
   m_strGroupKey = "SOFTWARE\\Decal\\";
   m_strGroupKey += OLE2A( strKey );
   m_strGroup = strKey;
   m_pDecal = pDecal;
   m_nIndex = -1;
   m_bOrderChanged = false;

   return true;
}

HRESULT cDecalEnum::Advance( REFCLSID clsid )
{
   // Assign a dummy letter
   cKeyEntry ke;
   ke.m_clsid = clsid;
   ke.m_letter = _T( 'A' );
   m_keys.reserve ( 1 );
   m_keys.push_back ( ke );

   return openKeyAt ( 0 );
}

HRESULT cDecalEnum::Begin()
{
   USES_CONVERSION;

   // Load and sort the list
   RegKey hkGroup;
   if( hkGroup.Create( HKEY_LOCAL_MACHINE, m_strGroupKey.c_str() ) != ERROR_SUCCESS ) 
      return Error ( IDE_GROUPKEYNOTOPEN );

   // Load the order string and prepare a new item string
   std::string strIdentifiers( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,./;'[]-=`<>?:{}|_+`!@#$%^&*()" );
   DWORD nMaxOrder = strIdentifiers.length ();
   LPTSTR szOrder = reinterpret_cast< LPTSTR > ( _alloca ( sizeof ( TCHAR ) * nMaxOrder ) );

   // Count the number of subkeys
   DWORD dwSubkeys;
   if ( ::RegQueryInfoKey ( hkGroup, NULL, NULL, NULL, &dwSubkeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL ) != ERROR_SUCCESS )
      return Error ( IDE_INVALIDSUBKEYS );
   m_keys.reserve ( dwSubkeys );

   LPTSTR szEndOrder;

   // Load an existing order
   if ( hkGroup.QueryStringValue (_T("Order"), szOrder, &nMaxOrder) == ERROR_SUCCESS )
   {
      szEndOrder = szOrder + ( nMaxOrder - 1 );

      // Sort and merge the differences
      std::string strOrder ( szOrder, szEndOrder );
      std::sort ( strOrder.begin (), strOrder.end () );
      std::string strMerged;
      strMerged.reserve ( strIdentifiers.length () - ( nMaxOrder - 1 ) );
      std::set_difference ( strIdentifiers.begin (), strIdentifiers.end (), strOrder.begin (), strOrder.end (), std::inserter ( strMerged, strMerged.end() ) );

      // Reset the identifiers to exclude the currently used ones
      strIdentifiers.swap ( strMerged );
   }
   else
   {
      szOrder[ 0 ] = _T( '\0' );
      szEndOrder = szOrder;
   }

   std::string::iterator iNextID = strIdentifiers.begin ();

   // Now we walk through the subkeys and insert in order
   TCHAR szCLSID[ 64 ];

   for ( int i = 0; ::RegEnumKey ( hkGroup, i, szCLSID, sizeof ( szCLSID ) ) == ERROR_SUCCESS; ++ i )
   {
      CLSID clsid;
      HRESULT hRes = ::CLSIDFromString ( T2OLE ( szCLSID ), &clsid );
      if ( FAILED ( hRes ) )
         // Skip non-CLSID keys
         continue;

      RegKey hkItem;
      if ( hkItem.Open ( hkGroup, szCLSID ) != ERROR_SUCCESS )
         continue;

      // Attempt to read the sequence indicator
      TCHAR szSequence[ 4 ];
      DWORD dwSequence = sizeof ( szSequence );
      cKeyEntry ke;
      ke.m_clsid = clsid;

      if ( hkItem.QueryStringValue ( _T( "Order" ), szSequence, &dwSequence ) == ERROR_SUCCESS )
      {
         _ASSERTE ( szSequence[ 1 ] == _T( '\0' ) );

         ke.m_letter = *szSequence;
         insertKey ( ke, szOrder, szEndOrder );
         continue;
      }

      // There's no sequence current, add on to the end
      _ASSERTE ( iNextID != strIdentifiers.end () );
      ke.m_letter = *( iNextID ++ );
      m_keys.push_back ( ke );
      TCHAR szVal[ 2 ] = { ke.m_letter, _T( '\0' ) };
      hkItem.SetStringValue ( _T( "Order" ), szVal );

      // The sequence is changed by adding items
      m_bOrderChanged = true;
   }

   return S_OK;
}

void cDecalEnum::insertKey ( cKeyEntry &ke, TCHAR *szOrder, TCHAR *szEndOrder )
{
   // Insert the item in the correct order into the list - first 
   // find the sequence number in the list
   for ( TCHAR *szOrderIndex = szOrder; szOrderIndex != szEndOrder; ++ szOrderIndex )
      if ( *szOrderIndex == ke.m_letter )
         break;

   if ( szOrderIndex == szEndOrder )
   {
      // This item is last, don't search for anything after
      m_keys.push_back ( ke );
      return;
   }

   // We have an insert, search for the member to insert before
   for ( szOrderIndex = szOrderIndex + 1; szOrderIndex != szEndOrder; ++ szOrderIndex )
   {
      // Search for a matchinf item in the array
      for ( cKeyList::iterator j = m_keys.begin(); j != m_keys.end(); ++ j )
      {
         if ( j->m_letter == *szOrderIndex )
         {
            m_keys.insert ( j, ke );
            return;
         }
      }
   }

   // The next entry most likely hasn't been loaded yet - just append
   m_keys.push_back ( ke );
}

HRESULT cDecalEnum::openKeyAt ( int nIndex )
{
   USES_CONVERSION;

   if ( m_key.m_hKey != NULL )
      m_key.Close ();

   LPOLESTR strClsid;
   ::StringFromCLSID( m_keys[ nIndex ].m_clsid, &strClsid );
   LPCTSTR szClsid = OLE2T( strClsid );
   ::CoTaskMemFree( strClsid );

   std::string strItemKey = m_strGroupKey + _T( "\\" );
   strItemKey += szClsid;
   if ( m_key.Open ( HKEY_LOCAL_MACHINE, strItemKey.c_str () ) != ERROR_SUCCESS )
      return Error ( IDE_CLASSKEYNOTFOUND );

   m_nIndex = nIndex;
   return S_OK;
}

STDMETHODIMP cDecalEnum::get_FriendlyName(BSTR *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   USES_CONVERSION;

   TCHAR szName[ MAX_PATH ];
   DWORD dwSize = MAX_PATH;
   if( m_key.QueryStringValue( NULL, szName, &dwSize ) != ERROR_SUCCESS )
      return E_FAIL;

   *pVal = T2BSTR( szName );

	return S_OK;
}

STDMETHODIMP cDecalEnum::get_ComClass(CLSID *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   USES_CONVERSION;

   *pVal = m_keys[ m_nIndex ].m_clsid;

   return S_OK;
}

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

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   DWORD dwEnabled;
   if( m_key.QueryDWORDValue( _T("Enabled"), dwEnabled ) != ERROR_SUCCESS )
      // Plugins are enabled when this value is ommitted
      dwEnabled = TRUE;

   *pVal = ( dwEnabled ) ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

STDMETHODIMP cDecalEnum::put_Enabled(VARIANT_BOOL newVal)
{
   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   if( m_key.SetDWORDValue( _T( "Enabled" ), ( ( newVal ) ? 1 : 0 ) ) != ERROR_SUCCESS )
      return E_FAIL;

	return S_OK;
}

STDMETHODIMP cDecalEnum::CreateInstance(REFIID iid, LPVOID *ppvItf)
{
   CLSID clsidSurrogate;
   if( FAILED( get_SurrogateClass( &clsidSurrogate ) ) )
   {
      _ASSERT( FALSE );
      return E_FAIL;
   }

   if( clsidSurrogate == GUID_NULL )
   {
      // Create the object without a surrogate
      CLSID clsidObject;
      HRESULT hRes = get_ComClass( &clsidObject );
      if( FAILED( hRes ) )
         return hRes;

      return ::CoCreateInstance( clsidObject, NULL, CLSCTX_INPROC_SERVER, iid, ppvItf );
   }

   // Create the surrogate object
   CComPtr< IDecalEnum > pEnum;
   static _bstr_t _strSurrogates ( _T( "Surrogates" ) );

   HRESULT hRes = m_pDecal->get_Configuration ( _strSurrogates, clsidSurrogate, &pEnum );
   if ( !SUCCEEDED ( hRes ) )
      return hRes;

   VARIANT_BOOL bEnabled;
   hRes = pEnum->get_Enabled ( &bEnabled );
   _ASSERTE ( SUCCEEDED ( hRes ) );

   if ( !bEnabled )
      return Error ( IDE_SURROGATEDISABLED );

   CComPtr< IDecalSurrogate > pSurrogate;
   hRes = pEnum->CreateInstance ( IID_IDecalSurrogate, reinterpret_cast< LPVOID * > ( &pSurrogate ) );

   if( FAILED( hRes ) )
      return hRes;

	return pSurrogate->CreateInstance( this, iid, ppvItf );
}

STDMETHODIMP cDecalEnum::Next()
{
   if ( m_nIndex == m_keys.size () - 1 )
   {
      // End of iteration
      if ( m_key.m_hKey != NULL )
         m_key.Close ();

      return S_FALSE;
   }

   return openKeyAt ( m_nIndex + 1 );
}

STDMETHODIMP cDecalEnum::get_SurrogateClass(CLSID *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   USES_CONVERSION;

   TCHAR szName[ MAX_PATH + 1 ];
   DWORD dwSize = MAX_PATH + 1;

   if( m_key.QueryStringValue( _T("Surrogate"), szName, &dwSize ) != ERROR_SUCCESS )
   {
      // If the surrogate is not found, return the NULL guid
      *pVal = GUID_NULL;
      return S_OK;
   }

   return ::CLSIDFromString( T2OLE( szName ), pVal );
}

STDMETHODIMP cDecalEnum::get_ResourcePath(BSTR *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   USES_CONVERSION;

   TCHAR szName[ MAX_PATH ];
   DWORD dwSize = MAX_PATH;
   if( m_key.QueryStringValue( NULL, szName, &dwSize ) != ERROR_SUCCESS )
      return E_FAIL;

   *pVal = T2BSTR( szName );

	return S_OK;
}

STDMETHODIMP cDecalEnum::get_Property(BSTR Name, VARIANT *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   USES_CONVERSION;

   LPCTSTR szValueName = OLE2T( Name );

   // Get some info about the property
   DWORD dwDataType;
   if( ::RegQueryValueEx( m_key.m_hKey, szValueName, NULL, &dwDataType, NULL, NULL ) != ERROR_SUCCESS )
      // We'll assume there's no value
      return E_INVALIDARG;

   switch( dwDataType )
   {
   case REG_DWORD:
      {
         DWORD dwValue;
         if( m_key.QueryDWORDValue( szValueName, dwValue ) != ERROR_SUCCESS )
            return E_FAIL;

         pVal->vt = VT_I4;
         pVal->lVal = dwValue;
         return S_OK;
      }
      break;

   case REG_EXPAND_SZ:
   case REG_SZ:
      {
         TCHAR szValue[ MAX_PATH ];
         DWORD dwSize = MAX_PATH;
         if( m_key.QueryStringValue( szValueName, szValue, &dwSize ) != ERROR_SUCCESS )
            return E_FAIL;

         pVal->vt = VT_BSTR;
         pVal->bstrVal = T2BSTR( szValue );
         return S_OK;
      }
      break;
   }

	return E_INVALIDARG;
}

STDMETHODIMP cDecalEnum::get_Group(BSTR *pVal)
{
   if( pVal == NULL )
   {
      _ASSERT( FALSE );
      return E_POINTER;
   }

   *pVal = OLE2BSTR( m_strGroup );

	return S_OK;
}

STDMETHODIMP cDecalEnum::Skip (REFCLSID clsid)
{
   // Search our loaded list for the clsid
   for ( cKeyList::iterator i = m_keys.begin (); i != m_keys.end(); ++ i )
   {
      if ( clsid == i->m_clsid )
         return openKeyAt ( i - m_keys.begin() );
   }

   return Error ( IDE_CLASSNOTFOUND );
}

STDMETHODIMP cDecalEnum::MoveBefore(REFCLSID clsidBefore)
{
   if( m_key.m_hKey == NULL )
   {
      _ASSERT( FALSE );
      return Error ( IDE_BADITERATOR );
   }

   // Find the entry we're inserting before
   for ( cKeyList::iterator i = m_keys.begin (); i != m_keys.end(); ++ i )
   {
      if ( i->m_clsid == clsidBefore )
         break;
   }

   if ( i == m_keys.end () )
      return Error ( IDE_CLASSNOTFOUND );

   int nFoundIndex = i - m_keys.begin ();
   if ( nFoundIndex == m_nIndex )
      // No op
      return S_FALSE;

   if ( nFoundIndex > m_nIndex )
      -- nFoundIndex;

   cKeyEntry eRemoved = m_keys[ m_nIndex ];
   m_keys.erase ( m_keys.begin () + m_nIndex );

   m_keys.insert ( m_keys.begin () + nFoundIndex, eRemoved );

   // Reset so we're still on the same item
   m_nIndex = nFoundIndex;
   m_bOrderChanged = true;

	return S_OK;
}
