/*++

Copyright (C) 1996-2001 Microsoft Corporation

Module Name:

Abstract:

History:

--*/


// WMIObjCooker.cpp

#include "precomp.h"
#include "WMIObjCooker.h"
#include "RawCooker.h"
#include <comdef.h>

///////////////////////////////////////////////////////////////////////////////
//
//	Helper Functions
//	================
//
///////////////////////////////////////////////////////////////////////////////

WMISTATUS GetPropValue( CProperty* pProp, IWbemObjectAccess* pInstance, __int64* pnResult )
{
	WMISTATUS dwStatus = WBEM_NO_ERROR;

	unsigned __int64 nResult = 0;
	DWORD dwRes = 0;

	switch( pProp->GetType() )
	{
	case CIM_UINT32:
		{
			dwStatus = pInstance->ReadDWORD( pProp->GetHandle(), &dwRes );
			if (pnResult) {
			    *pnResult = dwRes;
			}			
		}break;
	case CIM_UINT64:
		{
			dwStatus = pInstance->ReadQWORD( pProp->GetHandle(), &nResult );
			if (pnResult) {
			    *pnResult = nResult;
			}
		}break;
	default:
	    dwStatus = WBEM_E_TYPE_MISMATCH;
	}

	return dwStatus;
}

//////////////////////////////////////////////////////////////
//
//	CWMISimpleObjectCooker
//
//////////////////////////////////////////////////////////////

CWMISimpleObjectCooker::CWMISimpleObjectCooker( WCHAR* wszCookingClassName, 
                                                IWbemObjectAccess* pCookingClass, 
                                                IWbemObjectAccess* pRawClass, 
                                                IWbemServices * pNamespace ) : 
  m_lRef( 1 ),
  m_pCookingClass( NULL ),
  m_wszClassName(NULL),
  m_pNamespace(NULL),
  m_dwPropertyCacheSize( 16 ),
  m_dwNumProperties( 0 ),
  m_NumInst(0),
  m_InitHR(WBEM_E_INITIALIZATION_FAILURE)  
{
#ifdef _VERBOSE
    {
        char pBuff[128];
        wsprintfA(pBuff,"Cooker %p\n",this);
        OutputDebugStringA(pBuff);
    }
#endif
    
    if (pNamespace){
        m_pNamespace = pNamespace;
        m_pNamespace->AddRef();
    }

	m_InitHR = SetClass( wszCookingClassName, pCookingClass, pRawClass );

    if (m_pNamespace){
        m_pNamespace->Release();
        m_pNamespace = NULL;
    }
	
}

CWMISimpleObjectCooker::~CWMISimpleObjectCooker()
{
    
    Reset();

	// Release the cooking class
	// =========================

	if ( m_pCookingClass ){
		m_pCookingClass->Release();
    }
		
    if (m_pNamespace){
        m_pNamespace->Release();
    }

	// Delete the property cache
	// =========================
	
	for (DWORD i=0;i<m_apPropertyCache.size();i++){
	    CCookingProperty* pCookProp = m_apPropertyCache[i];
	    if (pCookProp)
    	    delete pCookProp;
	}

	if (m_wszClassName)
	    delete [] m_wszClassName;

#ifdef _VERBOSE    
    {
        char pBuff[128];
        wsprintfA(pBuff,"~Cooker %p istances left %d\n",this,m_NumInst);
        OutputDebugStringA(pBuff);
    }
#endif
	    	
}

//////////////////////////////////////////////////////////////
//
//					COM methods
//
//////////////////////////////////////////////////////////////

STDMETHODIMP CWMISimpleObjectCooker::QueryInterface(REFIID riid, void** ppv)
//////////////////////////////////////////////////////////////
//
//	Standard QueryInterface
//
//	Parameters:
//		riid	- the ID of the requested interface
//		ppv		- a pointer to the interface pointer
//
//////////////////////////////////////////////////////////////
//ok
{
    if(riid == IID_IUnknown)
        *ppv = (LPVOID)(IUnknown*)(IWMISimpleCooker*)this;
    else if(riid == IID_IWMISimpleCooker)
        *ppv = (LPVOID)(IWMISimpleCooker*)this;
	else return E_NOINTERFACE;

	((IUnknown*)*ppv)->AddRef();
	return S_OK;
}

STDMETHODIMP_(ULONG) CWMISimpleObjectCooker::AddRef()
//////////////////////////////////////////////////////////////
//
//	Standard COM AddRef
//
//////////////////////////////////////////////////////////////
//ok
{
    return InterlockedIncrement(&m_lRef);
}

STDMETHODIMP_(ULONG) CWMISimpleObjectCooker::Release()
//////////////////////////////////////////////////////////////
//
//	Standard COM Release
//
//////////////////////////////////////////////////////////////
//ok
{
    long lRef = InterlockedDecrement(&m_lRef);
    if(lRef == 0)
        delete this;

    return lRef;
}

STDMETHODIMP CWMISimpleObjectCooker::SetClass( 
		/*[in]	*/ WCHAR* wszCookingClassName,
		/*[in]  */ IWbemObjectAccess *pCookingClassAccess,
		/*[in]  */ IWbemObjectAccess *pRawClass )
{
	HRESULT	hResult = S_OK;
	IWbemClassObject * pClass = NULL;

	// Cannot override the original cooking class for now
	// ==================================================

	if ( ( NULL != m_pCookingClass ) || ( NULL == pCookingClassAccess ) )
		hResult = E_FAIL;

    // what we put here MUST be a class, Singletons are OK

    if (m_pNamespace) 
    {
		_variant_t VarGenus;
		hResult = pCookingClassAccess->Get(L"__GENUS",0,&VarGenus,NULL,NULL);
		
		if (SUCCEEDED(hResult))
		{
		    if ((CIM_SINT32 == V_VT(&VarGenus)) &&
		        WBEM_GENUS_CLASS == V_I4(&VarGenus))
			{
		    } 
			else 
			{		        
				BSTR BstrName = SysAllocString(wszCookingClassName);
				if (BstrName)
				{
					CAutoFree sfm(BstrName);
		            m_pNamespace->GetObject(BstrName,0,NULL,&pClass,NULL);	        
				}
				else
				{
                    hResult = WBEM_E_OUT_OF_MEMORY;
				}
		    }
		}
    }

    IWbemClassObject * pCookingClassAccess2;
    pCookingClassAccess2 = (pClass)?pClass:pCookingClassAccess;

	// Verify and process the cooking class
	// ====================================

	
	if ( SUCCEEDED( hResult ) )
	{
		BOOL bRet;
		bRet = IsCookingClass( pCookingClassAccess );

		if ( bRet ){
			// Save the class 
			// ==============
			
			m_pCookingClass = pCookingClassAccess;
			m_pCookingClass->AddRef();
		} else {
		   hResult = WBEM_E_INVALID_CLASS;
		}

		// Set the class name
		// ==================

		if ( SUCCEEDED( hResult ) )
		{
			m_wszClassName = new WCHAR[ wcslen( wszCookingClassName ) + 1 ];
			wcscpy( m_wszClassName, wszCookingClassName );
		}

		// Initialize the cooking properties
		// =================================

		if ( SUCCEEDED( hResult ) )
		{
			hResult = SetProperties( pCookingClassAccess2, pRawClass );
		}				
	}
	
	if (pClass){
	    pClass->Release();
	}

	return hResult;
}

WMISTATUS CWMISimpleObjectCooker::SetProperties( IWbemClassObject* pCookingClassObject, IWbemObjectAccess *pRawClass )
{
	WMISTATUS dwStatus = WBEM_NO_ERROR;

	BSTR	strPropName = NULL;
	long	lHandle = 0;
	CIMTYPE	ct;
	
	BOOL bAtLeastOne = FALSE;

    IWbemObjectAccess * pCookingClassAccess = NULL;
	dwStatus = pCookingClassObject->QueryInterface(IID_IWbemObjectAccess ,(void **)&pCookingClassAccess);
	if (FAILED(dwStatus))
	{
	    return dwStatus;
	}
    CAutoRelease rm(pCookingClassAccess );

    // get only once the qualifier set
    IWbemQualifierSet* pCookingClassQSet = NULL;
    dwStatus = pCookingClassObject->GetQualifierSet(&pCookingClassQSet);
    if (FAILED(dwStatus))
    {
        return dwStatus;
    }
	CAutoRelease rm1(pCookingClassQSet);

    //
    //  should we be using [TimeStamp|Frequency]_[Time|Sys100ns|Object] ?
    //
    BOOL bUseWellKnownIfNeeded = FALSE;
    dwStatus = pCookingClassQSet->Get(WMI_COOKER_AUTOCOOK_RAWDEFAULT,0,NULL,NULL);
    // we have already verified version and property, just test if it's there
	if ( SUCCEEDED(dwStatus) )
	{
        bUseWellKnownIfNeeded = TRUE;
    }
    else // do not propagate this error
    {
        dwStatus = WBEM_NO_ERROR;
    }
	
	// Enumerate and save the autocook properties
	// ==========================================

	pCookingClassObject->BeginEnumeration( WBEM_FLAG_NONSYSTEM_ONLY );
		
	while ( WBEM_S_NO_ERROR == pCookingClassObject->Next(0,&strPropName,NULL,&ct,NULL) &&
	        SUCCEEDED(dwStatus))
	{
		CAutoFree	afPropName( strPropName );

		DWORD dwCounterType = 0;
		DWORD dwReqProp = 0;

		// Determine if it is an autocook property
		// =======================================

		if ( IsCookingProperty( strPropName, pCookingClassObject, &dwCounterType, &dwReqProp ) )
		{
			m_dwNumProperties++;

			// The property is an autocook; save the Name, ObjectAccess handle, type and cooking object
			// ========================================================================================

			dwStatus = pCookingClassAccess->GetPropertyHandle( strPropName, &ct, &lHandle );

			if ( SUCCEEDED( dwStatus ) )
			{
#ifdef _VERBOSE				
			    {
			        char pBuff[128];
			        wsprintfA(pBuff,"%S %08x %08x\n",strPropName,dwCounterType,dwReqProp);
			        OutputDebugStringA(pBuff);
			    }
#endif			    
				CCookingProperty* pProperty = new CCookingProperty( strPropName, 
				                                                    dwCounterType, 
				                                                    lHandle, 
				                                                    ct,
				                                                    dwReqProp,
				                                                    bUseWellKnownIfNeeded);

				// Initialize the property object
				// ==============================

				IWbemQualifierSet*	pCookingPropQualifierSet = NULL;

				dwStatus = pCookingClassObject->GetPropertyQualifierSet( strPropName, &pCookingPropQualifierSet );
				CAutoRelease arQualifierSet( pCookingPropQualifierSet );

				if ( SUCCEEDED( dwStatus ) )
				{
					dwStatus = pProperty->Initialize( pCookingPropQualifierSet, pRawClass, pCookingClassQSet );
				}

				// If everything worked out then add the property to the cache
				// ===========================================================

				if ( SUCCEEDED( dwStatus ) )
				{
				    bAtLeastOne = TRUE;
				    try
				    {
                        m_apPropertyCache.push_back(pProperty);
                    }
                    catch (...)
                    {
                        dwStatus = WBEM_E_OUT_OF_MEMORY;
                    }
				}
				else
				{
					delete pProperty;
				}
			}
		}
	}

	pCookingClassObject->EndEnumeration();

	if (!bAtLeastOne && (SUCCEEDED(dwStatus))){
	    dwStatus = WBEM_E_INVALID_CLASS;
	}


	return dwStatus;
}

STDMETHODIMP CWMISimpleObjectCooker::SetCookedInstance( 
		/*[in]  */ IWbemObjectAccess *pCookedInstance,
		/*[out] */ long *plID)
{
	HRESULT	hResult = S_OK;

	CCookingInstance* pInstance = new CCookingInstance( pCookedInstance, m_apPropertyCache.size() );

	if (!pInstance || !pInstance->IsValid())
	{
	       delete pInstance;
		return WBEM_E_OUT_OF_MEMORY;
	}
	                                                       
	for ( DWORD dwProp = 0; dwProp < m_apPropertyCache.size() && SUCCEEDED(hResult); dwProp++ )
	{
		CCookingProperty* pProp = m_apPropertyCache[dwProp];

		hResult = pInstance->InitProperty( dwProp, pProp->NumberOfActiveSamples(), pProp->MinSamplesRequired() );
	}

	if (FAILED(hResult))
	{
	       delete pInstance;	
		return hResult;
	}

	// Add new cooked instance
	// =======================

	hResult = m_InstanceCache.Add( (DWORD *)plID, pInstance );

	m_NumInst++;

	return hResult;
}
        
STDMETHODIMP CWMISimpleObjectCooker::BeginCooking( 
		/*[in]  */ long lId,
		/*[in]  */ IWbemObjectAccess *pSampleInstance,
		/*[in]  */ DWORD dwRefreshStamp)
{
	HRESULT	hResult = S_OK;

	CCookingInstance*	pCookedInstance = NULL;

	// Add an initial sample to the cache
	// ==================================

	hResult = m_InstanceCache.GetData( lId, &pCookedInstance );

	if ( SUCCEEDED( hResult ) )
	{
		if ( NULL != pCookedInstance )
		{
			hResult = pCookedInstance->SetRawSourceInstance( pSampleInstance );

			if ( SUCCEEDED( hResult ) )
			{
				hResult = UpdateSamples( pCookedInstance, dwRefreshStamp );
			}
		}
		else
		{
			hResult = E_FAIL;
		}
	}

	return hResult;
}
        
STDMETHODIMP CWMISimpleObjectCooker::StopCooking( 
		/*[in]  */ long lId)
{
	HRESULT	hResult = S_OK;

	CCookingInstance*	pInstance = NULL;

    // ????
	hResult = m_InstanceCache.GetData( lId, &pInstance );

	return hResult;
}

        
STDMETHODIMP CWMISimpleObjectCooker::Recalc(DWORD dwRefreshStamp)
{
	HRESULT	hResult = S_OK;

	CCookingInstance*	pInstance = NULL;

	// Cook all of the instances which have a cached sample
	// ====================================================

	m_InstanceCache.BeginEnum();
	DWORD i=0;

	while ( S_OK == m_InstanceCache.Next( &pInstance ) )
	{	
	    // since we are inside a CritSec
	    // we need to ensire that we call
	    // EndEnum, that will release the Lock on the CritSec
		try {
			if ( pInstance )
			{
				hResult = CookInstance( pInstance, dwRefreshStamp );
#ifdef _VERBOSE					
				{
				    char pBuff[128];
				    wsprintfA(pBuff,"%S %p %d\n",pInstance->GetKey(),pInstance,i++);
				    OutputDebugStringA(pBuff);
				}
#endif				
			}
		} catch(...){
#ifdef _VERBOSE			
		    OutputDebugStringA("exception\n");
#endif		    
		}
	}

	m_InstanceCache.EndEnum();

	return hResult;
}
        
STDMETHODIMP CWMISimpleObjectCooker::Remove( 
		/*[in]  */ long lId)
{
	HRESULT	hResult = S_OK;

	// Remove the specified instance from the cache
	// ============================================

    CCookingInstance * pInst = NULL;
	hResult = m_InstanceCache.Remove( lId, &pInst );
	if (pInst){
	    delete pInst;
	    m_NumInst--;
	}

	return hResult;
}
        
STDMETHODIMP CWMISimpleObjectCooker::Reset()
{
	HRESULT	hResult = S_OK;

	// Remove all of the instances from the cache
	// ==========================================
	CCookingInstance * pInstance = NULL;
    m_InstanceCache.BeginEnum();

    while ( S_OK == m_InstanceCache.Next( &pInstance ) )
	{
		if (pInstance){
		    delete pInstance;
		    m_NumInst--;
		    pInstance = NULL;
		}
	}
        
    m_InstanceCache.EndEnum();

	hResult = m_InstanceCache.RemoveAll();

	return hResult;
}

WMISTATUS CWMISimpleObjectCooker::CookInstance( CCookingInstance* pInstance,
                                                DWORD dwRefreshStamp)
{
	WMISTATUS dwStatus = S_OK;

	if ( SUCCEEDED( dwStatus ) )
	{
		dwStatus = UpdateSamples( pInstance, dwRefreshStamp );

		// Loop through the cooking properties
		// ===================================
		
		for ( DWORD dwProp = 0; dwProp < m_apPropertyCache.size(); dwProp++ )
		{
			// Update the cooking instance property
			// ====================================
			pInstance->CookProperty( dwProp, m_apPropertyCache[dwProp] );
		}
	}

	return dwStatus;
}

WMISTATUS CWMISimpleObjectCooker::UpdateSamples( CCookingInstance* pCookedInstance, DWORD dwRefreshStamp )
{
	WMISTATUS dwStatus = WBEM_NO_ERROR;

	IWbemObjectAccess* pRawInstance = NULL;

	if ( NULL == pCookedInstance )
	{
		dwStatus = WBEM_E_INVALID_PARAMETER;
	}

	if ( SUCCEEDED( dwStatus ) )
	{
		dwStatus = pCookedInstance->GetRawSourceInstance( &pRawInstance );
		CAutoRelease	arRawInstance( pRawInstance );

		if ( NULL == pRawInstance )
		{
			dwStatus = WBEM_E_FAILED;
		}

#ifdef _VERBOSE
        {
            WCHAR pBuff[256];
            _variant_t Var;
            HRESULT hr = pRawInstance->Get(L"__RELPATH",0,&Var,NULL,NULL);
            wsprintfW(pBuff,L"%p hr %08x __RELPATH %s Key %s\n",pRawInstance,hr,V_BSTR(&Var),pCookedInstance->GetKey());
            OutputDebugStringW(pBuff);
        }
#endif

		for ( DWORD dwProp = 0; ( SUCCEEDED( dwStatus ) ) && dwProp < m_apPropertyCache.size(); dwProp++ )
		{
			CCookingProperty* pProp = m_apPropertyCache[dwProp];

			CProperty* pRawProp		= pProp->GetRawCounterProperty();
			CProperty* pBaseProp	= pProp->GetBaseProperty();
			CProperty* pTimeProp	= pProp->GetTimeProperty();

			__int64 nRawCounter = 0;
			__int64 nRawBase = 0;
			__int64 nTimeStamp = 0;

			dwStatus = GetPropValue( pRawProp, pRawInstance, &nRawCounter );

			if ( pBaseProp )
			{
				GetPropValue( pBaseProp, pRawInstance, &nRawBase );
			} 
			else if (pProp->IsReq(REQ_BASE))
			{
			    nRawBase = 1;
			}

			if ( pTimeProp )
			{
				GetPropValue( pTimeProp, pRawInstance, &nTimeStamp );
			} 
			else if (pProp->IsReq(REQ_TIME)) 
			{
			    LARGE_INTEGER li;
			    QueryPerformanceCounter(&li);
			    nTimeStamp = li.QuadPart;
			}

			dwStatus = pCookedInstance->AddSample( dwRefreshStamp, dwProp, nRawCounter, nRawBase, nTimeStamp );

#ifdef _VERBOSE	
			{
			    char pBuff[128];
			    wsprintfA(pBuff,"Prop %d status %08x\n"
			                    " counter %I64u base %I64u time %I64u\n",
			                    dwProp, dwStatus, nRawCounter, nRawBase, nTimeStamp);
			    OutputDebugStringA(pBuff);
			}
#endif	

		}
	}

	return dwStatus;
}