/*++

Copyright (C) 1996-1999 Microsoft Corporation

Module Name:

    grphitem.cpp

Abstract:

    <abstract>

--*/


#ifndef _LOG_INCLUDE_DATA 
#define _LOG_INCLUDE_DATA 0
#endif

#include <math.h>
#include <limits.h>     // for INT_MAX 
#include <pdhp.h>
#include "polyline.h"
#include "visuals.h"
#include "grphitem.h"
#include "unihelpr.h"
#include "utils.h"
#include "pdhmsg.h"

#define MAX_DOUBLE_TEXT_SIZE (64)

// Construction/Destruction
CGraphItem::CGraphItem (
    CSysmonControl  *pCtrl )
:   m_cRef ( 0 ),
    m_pCtrl ( pCtrl ),
    m_hCounter ( NULL ),
    m_hPen ( NULL ),
    m_hBrush ( NULL ),
    m_pCounter ( NULL ),
    m_pInstance ( NULL),
    m_pRawCtr ( NULL ),
    m_pFmtCtr ( NULL ),
    m_dFmtMax ( 0 ),
    m_dFmtMin ( 0 ),
    m_dFmtAvg ( 0 ),
    m_lFmtStatus ( 0 ),

    m_pLogData ( NULL ),
    m_pImpIDispatch ( NULL ),

    m_rgbColor ( RGB(0,0,0) ),
    m_iWidth ( 1 ),
    m_iStyle ( 0 ),      
    m_iScaleFactor ( INT_MAX ),
    m_dScale ( (double)1.0 ),

    m_pNextItem ( NULL ),
    m_bUpdateLog ( TRUE ),
    m_fGenerated ( FALSE )
/*++

Routine Description:

    Constructor for the CGraphItem class. It initializes the member variables.

Arguments:

    None.

Return Value:

    None.

--*/
{
    ZeroMemory ( &m_CounterInfo, sizeof (m_CounterInfo ) );
    m_CounterInfo.CStatus = PDH_CSTATUS_INVALID_DATA;
    
    return;
}


CGraphItem::~CGraphItem (
    VOID
    )
/*++

Routine Description:

    Destructor for the CGraphItem class. It frees any objects, storage, and
    interfaces that were created. If the item is part of a query it is removed
    from the query.

Arguments:

    None.

Return Value:

    None.

--*/
{
    if (m_hCounter != NULL)
        RemoveFromQuery();

    if (m_hPen != NULL)
        DeleteObject(m_hPen);

    if (m_hBrush != NULL)
        DeleteObject(m_hBrush);

    if (m_pImpIDispatch != NULL)
        delete m_pImpIDispatch;
}

HRESULT
CGraphItem::SaveToStream (
    IN LPSTREAM pIStream,
    IN BOOL fWildCard,
    IN INT iVersMaj, 
    IN INT // iVersMin 
    )
/*++

Routine Description:

    SaveToStream writes the graph item's properties to the provided stream.

Arguments:

    pIStream - Pointer to stream interface
    pszPath - Path name to save item under

Return Value:

    HRESULT - S_OK or stream error

--*/
{
    LPWSTR  pszWidePath;
    TCHAR   szPath[MAX_PATH];
    LPTSTR  szEnglishBuf = NULL;
    DWORD   dwEnglishBufSize = 0;
    LPTSTR  pNewBuf;
    DWORD   dwBufSize;
    LPTSTR  pszPath;
    HRESULT hr = S_OK;
    PDH_STATUS pdhStatus; 
    

    USES_CONVERSION

    // Get Ansi path name
    FormPath(szPath, fWildCard );
    
    pszPath = szPath;
    //
    // Initialize the locale path buffer
    //
    if (dwEnglishBufSize == 0) {
        dwEnglishBufSize = (MAX_PATH + 1) * sizeof(TCHAR);

        szEnglishBuf = (LPTSTR) malloc(dwEnglishBufSize);
        if (szEnglishBuf == NULL) {
            dwEnglishBufSize = 0;
        }
    }

    if (szEnglishBuf != NULL) {
        //
        // Translate counter name from Localization into English
        //
        dwBufSize = dwEnglishBufSize;

        pdhStatus = PdhTranslate009Counter(
                        szPath,
                        szEnglishBuf,
                        &dwBufSize);

        if (pdhStatus == PDH_MORE_DATA) {
            pNewBuf = (LPTSTR)realloc(szEnglishBuf, dwBufSize);
            if (pNewBuf != NULL) {
                szEnglishBuf = pNewBuf;
                dwEnglishBufSize = dwBufSize;

                pdhStatus = PdhTranslate009Counter(
                                szPath,
                                szEnglishBuf,
                                &dwBufSize);
            }
        }

        if (pdhStatus == ERROR_SUCCESS) {
            pszPath = szEnglishBuf;
        }
    }

    pszWidePath = T2W(pszPath);

    if ( SMONCTRL_MAJ_VERSION == iVersMaj ) {
        GRAPHITEM_DATA3 ItemData;

        // Move properties to storage structure
        ItemData.m_rgbColor = m_rgbColor;
        ItemData.m_iWidth = m_iWidth;
        ItemData.m_iStyle = m_iStyle;
        ItemData.m_iScaleFactor = m_iScaleFactor;
 
        assert( 0 < lstrlen(pszWidePath ) );
        
        ItemData.m_nPathLength = lstrlen(pszWidePath);
        
        // Write structure to stream
        hr = pIStream->Write(&ItemData, sizeof(ItemData), NULL);
        if (FAILED(hr)) {
            goto ErrorOut;
        }

        // Write path name to stream
        hr = pIStream->Write(pszWidePath, ItemData.m_nPathLength*sizeof(WCHAR), NULL);
        if (FAILED(hr)) {
            goto ErrorOut;
        }
    }

ErrorOut:
    if (szEnglishBuf != NULL) {
        free(szEnglishBuf);
    }
    return hr;
}

HRESULT
CGraphItem::NullItemToStream (
    IN LPSTREAM pIStream,
    IN INT,// iVersMaj, 
    IN INT // iVersMin
    )
/*++

Routine Description:

    NulItemToStream writes a graph item structiure with a null path name
    to the stream. This is used to marked the end of the counter data in
    the control's saved state.

Arguments:

    pIStream - Pointer to stream interface

Return Value:

    HRESULT - S_OK or stream error

--*/
{
    GRAPHITEM_DATA3 ItemData;

    // Zero path length, other fields needn't be initialized
    ItemData.m_nPathLength = 0;

    // Write structure to stream
    return pIStream->Write(&ItemData, sizeof(ItemData), NULL);
}

HRESULT
CGraphItem::SaveToPropertyBag (
    IN IPropertyBag* pIPropBag,
    IN INT iIndex,
    IN BOOL bUserMode,
    IN INT, // iVersMaj, 
    IN INT // iVersMin 
    )
/*++

Routine Description:

    SaveToPropertyBag writes the graph item's properties to the provided property bag
    interface.  The history data is saved as part of the properties.

Arguments:

    pIPropBag - Pointer to property bag interface
    fWildCard
    iVersMaj
    iVersMin

Return Value:

    HRESULT - S_OK or property bag error

--*/
{
    HRESULT hr = S_OK;

    TCHAR   szPath[MAX_PATH];
    PHIST_CONTROL pHistCtrl;
    VARIANT vValue;
    TCHAR   szCounterName[16];
    TCHAR   szPropertyName[16+16];
    DWORD   dwCounterNameBytes;
    DWORD   dwCounterNameLength;
    LPTSTR  pszNext;
    LPTSTR  szEnglishBuf = NULL;
    DWORD   dwEnglishBufSize = 0;
    LPTSTR  pszPath;
    DWORD   dwBufSize;
    LPTSTR  pNewBuf;
    PDH_STATUS pdhStatus;

    USES_CONVERSION

    // Write properties

    // Write path name

    _stprintf ( szCounterName, _T("%s%05d."), _T("Counter"), iIndex );
    dwCounterNameLength = lstrlen (szCounterName); 
    dwCounterNameBytes = dwCounterNameLength * sizeof (TCHAR); 

    //
    // Generate full path name. (machine\object\instance\counter format)
    //
    FormPath(szPath, FALSE);

    pszPath = szPath;
    //
    // Initialize the locale path buffer
    //
    if (dwEnglishBufSize == 0) {
        dwEnglishBufSize = (MAX_PATH + 1) * sizeof(TCHAR);

        szEnglishBuf = (LPTSTR) malloc(dwEnglishBufSize);
        if (szEnglishBuf == NULL) {
            dwEnglishBufSize = 0;
        }
    }

    if (szEnglishBuf != NULL) {
        //
        // Translate counter name from Localization into English
        //
        dwBufSize = dwEnglishBufSize;

        pdhStatus = PdhTranslate009Counter(
                        szPath,
                        szEnglishBuf,
                        &dwBufSize);

        if (pdhStatus == PDH_MORE_DATA) {
            pNewBuf = (LPTSTR)realloc(szEnglishBuf, dwBufSize);
            if (pNewBuf != NULL) {
                szEnglishBuf = pNewBuf;
                dwEnglishBufSize = dwBufSize;

                pdhStatus = PdhTranslate009Counter(
                                szPath,
                                szEnglishBuf,
                                &dwBufSize);
            }
        }

        if (pdhStatus == ERROR_SUCCESS) {
            pszPath = szEnglishBuf;
        }
    }

    //
    // Save the counter path into property bag
    //
    memcpy ( szPropertyName, szCounterName, dwCounterNameBytes );
    pszNext = szPropertyName + dwCounterNameLength;
    lstrcpy ( pszNext, _T("Path") );
    
    hr = StringToPropertyBag (
            pIPropBag,
            szPropertyName,
            pszPath );


    if (szEnglishBuf != NULL) {
        free(szEnglishBuf);
    }

    // Write visual properties

    if ( SUCCEEDED( hr ) ){
        lstrcpy ( pszNext, _T("Color") );

        hr = IntegerToPropertyBag ( pIPropBag, szPropertyName, m_rgbColor );
    }

    if ( SUCCEEDED( hr ) ){
        lstrcpy ( pszNext, _T("Width") );

        hr = IntegerToPropertyBag ( pIPropBag, szPropertyName, m_iWidth );
    }

    if ( SUCCEEDED( hr ) ){
        lstrcpy ( pszNext, _T("LineStyle") );

        hr = IntegerToPropertyBag ( pIPropBag, szPropertyName, m_iStyle );
    }

    if ( SUCCEEDED( hr ) ){
        INT iLocalFactor = m_iScaleFactor;
        
        lstrcpy ( pszNext, _T("ScaleFactor") );
        
        if ( INT_MAX == iLocalFactor ) {
            // Save actual scale factor in case the counter cannot be 
            // validated when the property bag file is opened.
            // lDefaultScale is 0 if never initialized.
            iLocalFactor = m_CounterInfo.lDefaultScale;
        }
                    
        hr = IntegerToPropertyBag ( pIPropBag, szPropertyName, iLocalFactor );
    }

    // Write history data only if live display, data exists and not in design mode.  
    // Log data is rebuilt from the log file.  
    pHistCtrl = m_pCtrl->HistoryControl();

    if ( ( pHistCtrl->nSamples > 0) 
#if !_LOG_INCLUDE_DATA 
        && ( !pHistCtrl->bLogSource )
#endif
        && bUserMode ) {
        LPTSTR pszData = NULL;
        DWORD dwMaxStrLen = ( pHistCtrl->nMaxSamples * MAX_DOUBLE_TEXT_SIZE ) + 1;
        
        pszData = new TCHAR[ dwMaxStrLen ];      
        
        if  ( NULL == pszData ) {
            hr = E_OUTOFMEMORY;
        }

        // Write the current statistics.
        if ( SUCCEEDED(hr) ) {

            double dMin;
            double dMax;
            double dAvg;
            LONG lStatus;

            hr = GetStatistics ( &dMax, &dMin, &dAvg, &lStatus );

            if (SUCCEEDED(hr) && IsSuccessSeverity(lStatus)) {
                lstrcpy ( pszNext, _T("Minimum") );

                hr = DoubleToPropertyBag ( pIPropBag, szPropertyName, dMin );

                if ( SUCCEEDED(hr) ) {
                    lstrcpy ( pszNext, _T("Maximum") );

                    hr = DoubleToPropertyBag ( pIPropBag, szPropertyName, dMax );
                }
                if ( SUCCEEDED(hr) ) {
                    lstrcpy ( pszNext, _T("Average") );

                    hr = DoubleToPropertyBag ( pIPropBag, szPropertyName, dAvg );
                }
                if ( SUCCEEDED(hr) ) {
                    lstrcpy ( pszNext, _T("StatisticStatus") );

                    hr = IntegerToPropertyBag ( pIPropBag, szPropertyName, lStatus );
                }
            }
        }
        
        if ( SUCCEEDED(hr) ) {

            INT i;
            LPTSTR  pszTemp;
            HRESULT hrConvert = S_OK;
            double  dblValue;
            DWORD   dwTmpStat;
            DWORD   dwCurrentDataLength;
            DWORD   dwTempLength;
            LPTSTR  pszDataNext;

            lstrcpy ( pszData, _T("") );
            dwCurrentDataLength = 0;
            pszDataNext = pszData;

            for ( i = 0; 
                  ( S_OK == hrConvert ) && ( i < pHistCtrl->nSamples ); 
                  i++ ) {
                
                if ( ERROR_SUCCESS != HistoryValue(i, &dblValue, &dwTmpStat) ) {
                    dblValue = -1.0;
                } else if (!IsSuccessSeverity(dwTmpStat)) {
                    dblValue = -1.0;
                }


                VariantInit( &vValue );
                vValue.vt = VT_R8;
                vValue.dblVal = dblValue;

                hrConvert = VariantChangeTypeEx( &vValue, &vValue, LCID_SCRIPT, VARIANT_NOUSEROVERRIDE, VT_BSTR );
    
                pszTemp = W2T( vValue.bstrVal);
                dwTempLength = lstrlen ( pszTemp );
    
                // Extra TCHAR for NULL terminator
                if ( dwTempLength + dwCurrentDataLength + sizeof(TCHAR) > dwMaxStrLen ) {
                    TCHAR* pszNewData;
                    dwMaxStrLen *= 2;
                    // Allocate a new buffer
                    pszNewData = new TCHAR[ dwMaxStrLen ];      
        
                    if  ( NULL != pszNewData ) {
                        memcpy ( pszNewData, pszData, dwCurrentDataLength * sizeof (TCHAR) );
                        delete pszData;
                        pszData = pszNewData;
                        pszDataNext = pszData;
                    } else {
                        hr = E_OUTOFMEMORY;
                    }
                }

                if ( SUCCEEDED(hr)) {
                    if ( i > 0 ) {
                        lstrcpy ( pszDataNext, _T("\t") );
                        dwCurrentDataLength += 1;       // char count for _T("\t");
                        pszDataNext += 1;
                    }
                    memcpy ( pszDataNext, pszTemp, dwTempLength * sizeof(TCHAR) );
                    dwCurrentDataLength += dwTempLength;
                    pszDataNext += dwTempLength;
                }

                VariantClear( &vValue );
            }
            lstrcpy ( pszDataNext, _T("") );
        }

        lstrcpy ( pszNext, _T("Data") );

        hr = StringToPropertyBag (
                pIPropBag,
                szPropertyName,
                pszData );
                    
        if ( NULL != pszData ) {
            delete ( pszData );
        }
    }

    return hr;
}

HRESULT
CGraphItem::LoadFromPropertyBag (
    IN IPropertyBag* pIPropBag,
    IN IErrorLog*   pIErrorLog,
    IN INT iIndex,
    IN INT, // iVersMaj, 
    IN INT, // iVersMin 
    IN INT  iSampleCount
    )
/*++

Routine Description:

    LoadFromPropertyBag loads the graph item's properties from the provided property bag
    interface.  
Arguments:

    pIPropBag - Pointer to property bag interface
    iVersMaj
    iVersMin

Return Value:

    HRESULT - S_OK or property bag error

--*/
{
    HRESULT hr = S_OK;

    TCHAR   szCounterName[16];
    TCHAR   szPropertyName[16+16];
    OLE_COLOR clrValue;
    INT     iValue;
    LPTSTR  pszData = NULL;
    int     iBufSizeCurrent = 0;
    int     iBufSize;

    USES_CONVERSION

    _stprintf ( szCounterName, _T("%s%05d."), _T("Counter"), iIndex );

    // Read visual properties

    lstrcpy ( szPropertyName, szCounterName );
    lstrcat ( szPropertyName, _T("Color") );
    hr = OleColorFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, clrValue );
    if ( SUCCEEDED(hr) ) {
        hr = put_Color ( clrValue );
    }

    lstrcpy ( szPropertyName, szCounterName );
    lstrcat ( szPropertyName, _T("Width") );
    hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, iValue );
    if ( SUCCEEDED(hr) ) {
        hr = put_Width ( iValue );
    }

    lstrcpy ( szPropertyName, szCounterName );
    lstrcat ( szPropertyName, _T("LineStyle") );
    hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, iValue );
    if ( SUCCEEDED(hr) ) {
        hr = put_LineStyle ( iValue );
    }
    
    lstrcpy ( szPropertyName, szCounterName );
    lstrcat ( szPropertyName, _T("ScaleFactor") );
    hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, iValue );
    if ( SUCCEEDED(hr) ) {
        hr = put_ScaleFactor ( iValue );
    }

    if ( 0 < iSampleCount ) {
                
        if ( NULL != m_pFmtCtr )
            delete m_pFmtCtr;

        m_pFmtCtr = new double[MAX_GRAPH_SAMPLES];      
    
        if ( NULL == m_pFmtCtr ) {
            hr = E_OUTOFMEMORY;
        } else {
            INT iFmtIndex;
            for (iFmtIndex = 0; iFmtIndex < MAX_GRAPH_SAMPLES; iFmtIndex++ ) {
                m_pFmtCtr[iFmtIndex] = -1.0;
            }
        }

        if ( SUCCEEDED(hr) ) {
            lstrcpy ( szPropertyName, szCounterName );
            lstrcat ( szPropertyName, _T("Data") );

            iBufSize = iBufSizeCurrent;

            hr = StringFromPropertyBag (
                    pIPropBag,
                    pIErrorLog,
                    szPropertyName,
                    pszData,
                    iBufSize );

            if ( SUCCEEDED(hr) && 
                 iBufSize > iBufSizeCurrent ) {
                if ( NULL != pszData ) {
                    delete pszData;
                }
                pszData = new TCHAR[ iBufSize ]; 

                if ( NULL == pszData ) {
                    hr = E_OUTOFMEMORY;
                } else {
                    lstrcpy ( pszData, _T("") );
                    
                    iBufSizeCurrent = iBufSize;

                    hr = StringFromPropertyBag (
                            pIPropBag,
                            pIErrorLog,
                            szPropertyName,
                            pszData,
                            iBufSize );
                }
            }
        }        

        // Read the samples in buffer order.
        if ( NULL != pszData && SUCCEEDED ( hr ) ) {
            INT iDataIndex;
            double dValue = 0;
            TCHAR* pNextData;
            TCHAR* pDataEnd;
            
            pNextData = pszData;
            pDataEnd = pszData + lstrlen(pszData);

            for ( iDataIndex = 0; iDataIndex < iSampleCount; iDataIndex++ ) {
                if ( pNextData < pDataEnd ) {
                    hr = GetNextValue ( pNextData, dValue );
                } else {
                    hr = E_FAIL;
                }

                if ( SUCCEEDED(hr) ) {
                    SetHistoryValue ( iDataIndex, dValue );                    
                } else {
                    SetHistoryValue ( iDataIndex, -1.0 );                    
                    // iSampleCount = 0;
                    // Control loaded fine, just no data.
                    hr = NOERROR;
                }
            }        
        }
        
        if ( NULL != pszData ) {
            delete pszData;
        }
        
        // Read the current statistics.
        lstrcpy ( szPropertyName, szCounterName );
        lstrcat ( szPropertyName, _T("Maximum") );
        hr = DoubleFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, m_dFmtMax );

        lstrcpy ( szPropertyName, szCounterName );
        lstrcat ( szPropertyName, _T("Minimum") );            
        hr = DoubleFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, m_dFmtMin );

        lstrcpy ( szPropertyName, szCounterName );
        lstrcat ( szPropertyName, _T("Average") );
        hr = DoubleFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, m_dFmtAvg );

        lstrcpy ( szPropertyName, szCounterName );
        lstrcat ( szPropertyName, _T("StatisticStatus") );
        hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, szPropertyName, (INT&)m_lFmtStatus );
    }

    return hr;
}

HRESULT
CGraphItem::AddToQuery (
    IN HQUERY hQuery
    )
/*++

Routine Description:

    AddToQuery adds a counter to the provided query based on the item's
    pathname. It also allocates an array of raw counter value structures for
    holding the counter's sample history.

Arguments:

    hQuery - Handle to query

Return Value:

    Boolean status - TRUE = success

--*/
{
    HCOUNTER    hCounter;
    INT         i;
    HRESULT     hr;
    TCHAR       achPath[MAX_PATH];
    PDH_COUNTER_INFO    ci;
    DWORD       size;

    PHIST_CONTROL pHistCtrl = m_pCtrl->HistoryControl();

    // Can't add if already in query
    if (m_hCounter != NULL)
        return E_FAIL;

    // Allocate memory for maximum sample count
    if (pHistCtrl->nMaxSamples > 0) {

        // if log data
        if (pHistCtrl->bLogSource) {

            // allocate space for formatted values
            m_pLogData =  new LOG_ENTRY_DATA[pHistCtrl->nMaxSamples];
            if (m_pLogData == NULL)
                return E_OUTOFMEMORY;

            // Clear the statistics
            m_dLogMax = 0.0;
            m_dLogMin = 0.0;
            m_dLogAvg = 0.0;
        }
        else {
            // else allocate raw value space
            m_pRawCtr = new PDH_RAW_COUNTER[pHistCtrl->nMaxSamples];
            if ( NULL == m_pRawCtr )
                return E_OUTOFMEMORY;

            // Clear all status flags
            for (i=0; i < pHistCtrl->nMaxSamples; i++)
                m_pRawCtr[i].CStatus = PDH_CSTATUS_INVALID_DATA;
        }
    }

    // Create the counter object
    FormPath(achPath, FALSE);
    hr = PdhAddCounter(hQuery, achPath, 0, &hCounter);
    if (IsErrorSeverity(hr)) {
        delete m_pRawCtr;
        m_pRawCtr = NULL;
        return hr;
    }

    size = sizeof(ci);
    hr = PdhGetCounterInfo (
        hCounter,
        FALSE,
        &size,
        &ci);

    if (hr == ERROR_SUCCESS)  {
        m_CounterInfo = ci;
        if ( INT_MAX == m_iScaleFactor ) {
            m_dScale = pow ((double)10.0f, (double)ci.lDefaultScale);
        }
    }

    m_hCounter = hCounter;

    return NOERROR;
}


HRESULT
CGraphItem::RemoveFromQuery (
    VOID
    )
/*++

Routine Description:

    RemoveFromQuery deletes the item's counter and releases its history array.

Arguments:

    None.

Return Value:

    Boolean status - TRUE = success

--*/
{
    // If no counter handle, not attached to query
    if (m_hCounter == NULL)
        return S_FALSE;

    // Delete the counter
    PdhRemoveCounter(m_hCounter);
    m_hCounter = NULL;

    // Free the buffers
    if (m_pLogData) {
        delete m_pLogData;
        m_pLogData = NULL;
    }

    if (m_pRawCtr) {
        delete m_pRawCtr;
        m_pRawCtr = NULL;
    }

    if (m_pFmtCtr) {
        delete m_pFmtCtr;
        m_pFmtCtr = NULL;
    }

    return NOERROR;
}

void
CGraphItem::ClearHistory ( void )
/*++

Routine Description:

    ClearHistory resets the raw counter buffer values to Invalid.

Arguments:
    None.

Return Value:
    None.
--*/
{
    INT i;

    // Clear all status flags
    if ( NULL != m_pRawCtr ) {
        for (i=0; i < m_pCtrl->HistoryControl()->nMaxSamples; i++) {
            m_pRawCtr[i].CStatus = PDH_CSTATUS_INVALID_DATA;
        }
    }
}

VOID
CGraphItem::UpdateHistory (
    IN BOOL bValidSample
    )
/*++

Routine Description:

    UpdateHistory reads the raw value for the counter and stores it in the
    history slot specified by the history control.

Arguments:

    bValidSample - True if raw value is available, False if missed sample

Return Value:

    None.

--*/
{
    DWORD   dwCtrType;

    // Make sure there is a counter handle
    if (m_hCounter == NULL)
        return;

    if (bValidSample) {
        // Read the raw value
        if ( NULL != m_pRawCtr ) {
            PdhGetRawCounterValue(m_hCounter, &dwCtrType,
                                &m_pRawCtr[m_pCtrl->HistoryControl()->iCurrent]);
        }
    } else {
        // Mark value failed
        if ( NULL != m_pRawCtr ) {
            m_pRawCtr[m_pCtrl->HistoryControl()->iCurrent].CStatus = PDH_CSTATUS_INVALID_DATA;
        }
    }
}

PDH_STATUS
CGraphItem::HistoryValue (
    IN  INT iIndex,
    OUT double *pdValue,
    OUT DWORD *pdwStatus
    )
/*++

Routine Description:

    HistoryValue computes a formated sample value from the selected raw history
    sample. The calculation is actually based on the the specified sample plus
    the preceding sample.

Arguments:
    iIndex - Index of desired sample (0 = current, 1 = previous, ...)
    pdValue - Pointer to return value
    pdwStatus - Pointer to return counter status (PDH_CSTATUS_...)

Return Value:

    Error status

--*/
{
    PDH_STATUS  stat = ERROR_INVALID_PARAMETER;
    INT     iPrevIndex;
    INT     iCurrIndex;
    PDH_FMT_COUNTERVALUE    FmtValue;
    PHIST_CONTROL   pHistCtrl = m_pCtrl->HistoryControl();


    // Check for negative index
    if ( iIndex >= 0 ) {
        // If sample not available from cache or data, return invalid data status
        if ( NULL == m_pFmtCtr 
                && ( m_hCounter == NULL || iIndex + 1 >= pHistCtrl->nSamples ) )
        {
            *pdwStatus = PDH_CSTATUS_INVALID_DATA;
            *pdValue = 0.0;
            stat = ERROR_SUCCESS;
        } else {
        
            // if log source, index back from last sample
            if (m_pCtrl->IsLogSource()) {
                *pdValue = m_pLogData[pHistCtrl->nSamples - iIndex - 1].m_dAvg;
                *pdwStatus = (*pdValue >= 0.0) ? PDH_CSTATUS_VALID_DATA : PDH_CSTATUS_INVALID_DATA;
                stat = ERROR_SUCCESS;
            } else {
                // Determine history array index of sample
                iCurrIndex = pHistCtrl->iCurrent - iIndex;
                if (iCurrIndex < 0)
                    iCurrIndex += pHistCtrl->nMaxSamples;

                // Check to determine if loading from property bag
                if ( NULL == m_pFmtCtr ) {
                    // Need previous sample as well
                    if (iCurrIndex > 0)
                        iPrevIndex = iCurrIndex - 1;
                    else
                        iPrevIndex = pHistCtrl->nMaxSamples - 1;

                    // Compute the formatted value
                    if ( NULL != m_pRawCtr ) {
                        stat = PdhCalculateCounterFromRawValue(m_hCounter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100,
                                                &m_pRawCtr[iCurrIndex], &m_pRawCtr[iPrevIndex],
                                                &FmtValue);
                        // Return value and status
                        *pdValue = FmtValue.doubleValue;
                        *pdwStatus = FmtValue.CStatus;
                    } else {
                        stat = ERROR_GEN_FAILURE;       // Todo:  More specific error
                    }
                } else {
                    // Loading from property bag
                    *pdValue = m_pFmtCtr[iCurrIndex];
                    if ( 0 <= m_pFmtCtr[iCurrIndex] ) {
                        *pdwStatus = ERROR_SUCCESS;
                    } else {
                        *pdwStatus = PDH_CSTATUS_INVALID_DATA;
                    }
                    stat = ERROR_SUCCESS;
                }
            }
        }
    }
    return stat;
}

void
CGraphItem::SetHistoryValue (
    IN  INT iIndex,
    OUT double dValue
    )
/*++

Routine Description:

    SetHistoryValue loads a formated sample value for the specified sample index.
    This method is used when loading the control from a property bag.

Arguments:
    iIndex - Index of desired sample (0 = current, 1 = previous, ...)
    dValue - Value

Return Value:

    Error status

--*/
{
    PHIST_CONTROL   pHistCtrl = m_pCtrl->HistoryControl();
    INT iRealIndex;

    // Check for negative index
    if ( (iIndex < 0) || ( iIndex >= pHistCtrl->nMaxSamples) ) {
        return;
    }

    if ( NULL == m_pFmtCtr ) {
        return;
    }
 
    // if log source, index back from last sample
    if (m_pCtrl->IsLogSource()) {
        return;
    } else {
        // Determine history array index of sample
        iRealIndex = pHistCtrl->iCurrent - iIndex;
        if (iRealIndex < 0)
            iRealIndex += pHistCtrl->nSamples;

        m_pFmtCtr[iRealIndex] = dValue;
    }

    return;
}

PDH_STATUS
CGraphItem::GetLogEntry(
    const INT iIndex,
    double *dMin,
    double *dMax,
    double *dAvg,
    DWORD   *pdwStatus
    )
{
    INT iLocIndex = iIndex;

    *dMin = -1.0;
    *dMax = -1.0;
    *dAvg = -1.0;
    *pdwStatus = PDH_CSTATUS_INVALID_DATA;

    if (m_pLogData == NULL)
        return PDH_NO_DATA;

    if (iLocIndex < 0 || iLocIndex >= m_pCtrl->HistoryControl()->nMaxSamples)
        return PDH_INVALID_ARGUMENT;

    // Subtract 1 because array is zero-based
    // Subtract another 1 because ??
    iLocIndex = ( m_pCtrl->HistoryControl()->nMaxSamples - 2 ) - iIndex;

    if (m_pLogData[iLocIndex].m_dMax < 0.0) {
        *pdwStatus = PDH_CSTATUS_INVALID_DATA;
    } else {
        *dMin = m_pLogData[iLocIndex].m_dMin;
        *dMax = m_pLogData[iLocIndex].m_dMax;
        *dAvg = m_pLogData[iLocIndex].m_dAvg;
        *pdwStatus = PDH_CSTATUS_VALID_DATA;
    }

    return ERROR_SUCCESS;
}

PDH_STATUS
CGraphItem::GetLogEntryTimeStamp(
    const INT   iIndex,
    LONGLONG&   rLastTimeStamp,
    DWORD       *pdwStatus
    )
{
    INT iLocIndex = iIndex;

    rLastTimeStamp = 0;
    *pdwStatus = PDH_CSTATUS_INVALID_DATA;

    if (m_pLogData == NULL)
        return PDH_NO_DATA;

    if (iIndex < 0 || iIndex >= m_pCtrl->HistoryControl()->nMaxSamples)
        return PDH_INVALID_ARGUMENT;

    if ( ( MIN_TIME_VALUE == *((LONGLONG*)&m_pLogData[iLocIndex].m_LastTimeStamp) )
            || ( 0 > *((LONGLONG*)&m_pLogData[iLocIndex].m_dMax) ) ) {
        *pdwStatus = PDH_CSTATUS_INVALID_DATA;
    } else {            
        *pdwStatus = PDH_CSTATUS_VALID_DATA;
    }

    rLastTimeStamp = *((LONGLONG*)&m_pLogData[iLocIndex].m_LastTimeStamp);

    return ERROR_SUCCESS;
}

void
CGraphItem::SetLogEntry(
    const INT iIndex,
    const double dMin,
    const double dMax,
    const double dAvg )
{  
    if (m_pLogData) {
        m_pLogData[iIndex].m_dMin = dMin;
        m_pLogData[iIndex].m_dMax = dMax;                         
        m_pLogData[iIndex].m_dAvg = dAvg;
    }
}

void
CGraphItem::SetLogEntryTimeStamp (
    const INT iIndex,
    const FILETIME& rLastTimeStamp )
{  
    if (m_pLogData) {
        m_pLogData[iIndex].m_LastTimeStamp.dwLowDateTime = rLastTimeStamp.dwLowDateTime;
        m_pLogData[iIndex].m_LastTimeStamp.dwHighDateTime = rLastTimeStamp.dwHighDateTime;
    }
}


HRESULT
CGraphItem::GetValue(
    OUT double *pdValue,
    OUT long *plStat
    )
/*++

Routine Description:

    get_Value returns the most recent sample value for the counter.

Arguments:
    pdValue - Pointer to returned value
    dlStatus - Pointer to returned counter status (PDH_CSTATUS_...)

Return Value:

    HRESULT

--*/
{
    DWORD   dwTmpStat;

    // Convert PDH status to HRESULT
    if (HistoryValue(0, pdValue, &dwTmpStat) != 0)
        return E_FAIL;

    *plStat = dwTmpStat;
    return NOERROR;
}


HRESULT
CGraphItem::GetStatistics (
    OUT double *pdMax,
    OUT double *pdMin,
    OUT double *pdAvg,
    OUT LONG  *plStatus
    )
/*++

Routine Description:

    GetStatistics computes the max, min, and average values for the sample
    history.

Arguments:

    pdMax - Pointer to returned max value
    pdMax - Pointer to returned min value
    pdMax - Pointer to returned average value
    plStatus - Pointer to return counter status (PDH_CSTATUS_...)

Return Value:

    HRESULT

--*/
{
    HRESULT hr = NOERROR;
    PDH_STATUS  stat = ERROR_SUCCESS;
    PDH_STATISTICS  StatData;
    INT     iFirst;
    PHIST_CONTROL pHistCtrl;

    // If no data collected, return invalid data status
    if ( NULL == m_hCounter ) {
        *plStatus = PDH_CSTATUS_INVALID_DATA;
    } else {
        if (m_pCtrl->IsLogSource()) {

            if (m_pLogData) {
                *pdMax = m_dLogMax;
                *pdMin = m_dLogMin;
                *pdAvg = m_dLogAvg;

                *plStatus = PDH_CSTATUS_VALID_DATA;
            } else {
                *plStatus = PDH_CSTATUS_INVALID_DATA;
            }
        } else {

            if ( NULL == m_pFmtCtr ) {
                pHistCtrl = m_pCtrl->HistoryControl();

                ZeroMemory ( &StatData, sizeof ( PDH_STATISTICS ) );

                // Determine index of oldest sample
                if (pHistCtrl->iCurrent < pHistCtrl->nSamples - 1) {
                    iFirst = pHistCtrl->iCurrent + 1;
                } else {
                    iFirst = 0;
                }

                // Compute statistics over all samples
                //  Note that max sample count is passed (i.e., buffer length)
                //  not the number of actual samples
                if ( NULL != m_pRawCtr ) {
                    stat = PdhComputeCounterStatistics (m_hCounter, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100,
                            iFirst, pHistCtrl->nMaxSamples, m_pRawCtr, &StatData );
                    if ( 0 != stat )
                        hr = E_FAIL;
                } else {
                    hr = E_FAIL;
                }
                
                if ( SUCCEEDED ( hr ) ) {
                    *plStatus = StatData.mean.CStatus;
                    *pdMin = StatData.min.doubleValue;
                    *pdMax = StatData.max.doubleValue;
                    *pdAvg = StatData.mean.doubleValue;
                }
            } else {
                // Data is cached from property bag.
                *pdMax = m_dFmtMax;
                *pdMin = m_dFmtMin;
                *pdAvg = m_dFmtAvg;
                *plStatus = m_lFmtStatus;
            }
        }
    }

    return hr;
}

void
CGraphItem::SetStatistics (
    IN double dMax,
    IN double dMin,
    IN double dAvg,
    IN LONG   lStatus
    )
/*++

Routine Description:

    SetStatistics sets the max, min, and average values for the sample
    history.  It is used by LoadFromPropertyBag only.

Arguments:

    dMax - max value
    dMin - min value
    dAvg - average value
    lStatus - counter status (PDH_CSTATUS_...)

Return Value:

    HRESULT

--*/
{
    if (!m_pCtrl->IsLogSource()) {
        m_dFmtMax = dMax;
        m_dFmtMin = dMin;
        m_dFmtAvg = dAvg;
        m_lFmtStatus = lStatus;
    }
}


/*
 * CGraphItem::QueryInterface
 * CGraphItem::AddRef
 * CGraphItem::Release
 */

STDMETHODIMP CGraphItem::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {

    *ppv = NULL;

    if (riid == IID_ICounterItem || riid == IID_IUnknown)
        {
        *ppv = this;
        }
    else if (riid == DIID_DICounterItem)
        {
        if (m_pImpIDispatch == NULL)
            {
            m_pImpIDispatch = new CImpIDispatch(this, this);
            if (m_pImpIDispatch == NULL)
                return E_OUTOFMEMORY;

            m_pImpIDispatch->SetInterface(DIID_DICounterItem, this);
            *ppv = m_pImpIDispatch;
            }
        else
            {
            *ppv = m_pImpIDispatch;
            }
        }
    else
        {
        return E_NOINTERFACE;
        }

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

STDMETHODIMP_(ULONG) CGraphItem::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CGraphItem::Release(void)
    {
    if (--m_cRef == 0)
        {
        delete this;
        return 0;
        }

    return m_cRef;
    }

// Get/Put Color
STDMETHODIMP CGraphItem::put_Color (
    IN OLE_COLOR Color
    )
{
    COLORREF rgbColor;
    HRESULT hReturn;

    hReturn = OleTranslateColor(Color, NULL, &rgbColor);    

    if ( S_OK == hReturn ) {
        m_rgbColor = rgbColor;

        InvalidatePen();
        InvalidateBrush();
    }

    return hReturn;
}

STDMETHODIMP CGraphItem::get_Color (
    OUT OLE_COLOR *pColor
    )
{
    *pColor = m_rgbColor;
    return NOERROR;
}

// Get/Put Width
STDMETHODIMP CGraphItem::put_Width (
    IN INT iWidthInPixels)
{
    if ( ( iWidthInPixels > 0 ) && (iWidthInPixels <= NumWidthIndices() ) ) {
        m_iWidth = iWidthInPixels;
        
        InvalidatePen();
        return NOERROR;
    } else {
        return E_INVALIDARG;
    }
}

STDMETHODIMP CGraphItem::get_Width (
    OUT INT* piWidthInPixels
    )
{
    *piWidthInPixels = m_iWidth;
    return NOERROR;
}

// Get/Put Line Style
STDMETHODIMP CGraphItem::put_LineStyle (
    IN INT iLineStyle
    )
{
    if ( ( iLineStyle >= 0 ) && (iLineStyle < NumStyleIndices() ) ) {
        m_iStyle = iLineStyle;
        InvalidatePen();
        return NOERROR;
    } else {
        return E_INVALIDARG;
    }
}

STDMETHODIMP CGraphItem::get_LineStyle (
    OUT INT* piLineStyle
    )
{
    *piLineStyle = m_iStyle;
    return NOERROR;
}

// Get/Put Scale
STDMETHODIMP CGraphItem::put_ScaleFactor (
    IN INT iScaleFactor
    )
{
    HRESULT hr = NOERROR;

    if ( ( INT_MAX == iScaleFactor ) 
        || ( ( iScaleFactor >= PDH_MIN_SCALE ) && (iScaleFactor <= PDH_MAX_SCALE) ) ) {
        PDH_COUNTER_INFO    ci;
        DWORD       size;

        m_iScaleFactor = iScaleFactor;

        if ( INT_MAX == iScaleFactor  )
        {
            if ( NULL != Handle() ) {
                size = sizeof(ci);
                hr = PdhGetCounterInfo (
                    Handle(),
                    FALSE,
                    &size,
                    &ci);

                if (hr == ERROR_SUCCESS)  {
                    m_dScale = pow ((double)10.0f, (double)ci.lDefaultScale);
                    m_CounterInfo = ci;
    
                }
            } else {
                // m_dScale remains at previous value (default=1)
                hr = PDH_INVALID_HANDLE;
            }
        }
        else
        {
            m_dScale = pow ((double)10.0, (double)iScaleFactor);
            hr = NOERROR;
        }
    } else {
        hr = E_INVALIDARG;
    }

    return hr;
}

STDMETHODIMP CGraphItem::get_ScaleFactor (
    OUT INT* piScaleFactor
    )
{
    *piScaleFactor = m_iScaleFactor;
    return NOERROR;
}


STDMETHODIMP CGraphItem::get_Path (
    OUT BSTR* pstrPath
    )
{
    TCHAR achPath[MAX_PATH];

    USES_CONVERSION

    FormPath(achPath, FALSE);
    *pstrPath = SysAllocString(T2W(achPath));

    if ( NULL == *pstrPath ) {
        return E_OUTOFMEMORY;
    } else {
        return NOERROR;
    }
}

STDMETHODIMP CGraphItem::get_Value (
    OUT double* pdValue
    )
{
    DWORD   dwTmpStat;
    double  dValue;

    // Convert PDH status to HRESULT
    if (HistoryValue(0, &dValue, &dwTmpStat) != 0) {
        dValue = -1.0;
    }
    else
        if (!IsSuccessSeverity(dwTmpStat)) {
        dValue = -1.0;
    }

    *pdValue = dValue;

    return NOERROR;
}

HPEN CGraphItem::Pen(void)
{
    // if pen not valid
    if (m_hPen == NULL)
    {
        // create a new one based on current attributes
        m_hPen = CreatePen(m_iStyle, m_iWidth, m_rgbColor);

        // if can't do it, use a stock object (this can't fail)
        if (m_hPen == NULL)
            m_hPen = (HPEN)GetStockObject(BLACK_PEN);
    }

    return m_hPen;
}

HBRUSH CGraphItem::Brush(void)
{
    // if brush is not valid
    if (m_hBrush == NULL)
    {
        m_hBrush = CreateSolidBrush(m_rgbColor);

        if (m_hBrush == NULL)
            m_hBrush = (HBRUSH)GetStockObject(BLACK_BRUSH);
    }

    return m_hBrush;
}

void CGraphItem::InvalidatePen(void)
{
    if (m_hPen != NULL)
    {
        DeleteObject(m_hPen);
        m_hPen = NULL;
    }
}

void CGraphItem::InvalidateBrush(void)
{
    if (m_hBrush != NULL)
    {
        DeleteObject(m_hBrush);
        m_hBrush = NULL;
    }
}


CGraphItem*
CGraphItem::Next (
    void
    )
{
    PCInstanceNode pInstance;
    PCObjectNode   pObject;
    PCMachineNode  pMachine;

    if (m_pNextItem)
        return m_pNextItem;
    else if ( NULL != m_pInstance->Next()) {
        pInstance = m_pInstance->Next();
        return pInstance->FirstItem();
    } else if ( NULL != m_pInstance->m_pObject->Next()) {
        pObject = m_pInstance->m_pObject->Next();
        return pObject->FirstInstance()->FirstItem();
    } else if ( NULL != m_pInstance->m_pObject->m_pMachine->Next()) {
        pMachine = m_pInstance->m_pObject->m_pMachine->Next();
        return pMachine->FirstObject()->FirstInstance()->FirstItem();
    } else {
        return NULL;
    }
}


void
CGraphItem::FormPath (
    LPTSTR  pszPath,
    BOOL    fWildCard
    )
{
    LPTSTR pszNext = pszPath;

    if ( m_fLocalMachine )
        pszPath[0] = 0;
    else
        lstrcpy ( pszPath, Machine()->Name() );
    
    pszNext = pszNext + lstrlen ( pszPath );

    lstrcpy ( pszNext, _T("\\") );
    pszNext += 1;                // Length of _T("\\");

    lstrcpy ( pszNext, Object()->Name() );
    pszNext += lstrlen ( Object()->Name() );

    if (fWildCard) {
        lstrcpy ( pszNext,_T("(*)") );
        pszNext += 3;           // Length of _T("(*)");
    }
    else if ( Instance()->Name()[0] ) {
        lstrcpy ( pszNext,_T("("));
        pszNext += 1;                // Length of _T("(");

        lstrcpy ( pszNext,Instance()->Name() );
        pszNext += lstrlen ( Instance()->Name() );

        lstrcpy ( pszNext,_T(")") );
        pszNext += 1;                // Length of _T("(");
    }

    lstrcpy ( pszNext, _T("\\") );
    pszNext += 1;                // Length of _T("\\");

    lstrcpy ( pszNext,Counter()->Name() );
}


void
CGraphItem::Delete (
    BOOL bPropogateUp
    )
//
// This method just provides a convenient access to the DeleteCounter method
// of the control when you only have a pointer to the graph item.
//
{
    m_pCtrl->DeleteCounter(this, bPropogateUp);
}



HRESULT
CGraphItem::GetNextValue (
    TCHAR*& pszNext,
    double& rdValue )
{
    HRESULT hr = NOERROR;
    TCHAR szValue[MAX_DOUBLE_TEXT_SIZE + 1];
    INT iDataLen;
    INT iLen;

    VARIANT vValue;
    
    rdValue = -1.0;

    iDataLen = wcscspn (pszNext, L"\t");

    iLen = min ( iDataLen, MAX_DOUBLE_TEXT_SIZE );

    lstrcpyn ( szValue, pszNext, iLen + 1 );

    szValue[iLen] = L'\0';

    VariantInit( &vValue );
    vValue.vt = VT_BSTR;

    vValue.bstrVal = SysAllocString ( szValue );
    hr = VariantChangeTypeEx( &vValue, &vValue, LCID_SCRIPT, VARIANT_NOUSEROVERRIDE, VT_R8 );

    if ( SUCCEEDED(hr) ) {
        rdValue = vValue.dblVal;
    }

    pszNext += iDataLen + 1  ;
    VariantClear( &vValue );

    return hr;

}

BOOL
CGraphItem::IsRateCounter ( void )
{
    BOOL bReturn = FALSE;
    DWORD   dwRateMask = PERF_TYPE_COUNTER | PERF_COUNTER_RATE;
    DWORD   dwFractionMask = PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION;

    if ( dwRateMask == ( m_CounterInfo.dwType & dwRateMask ) ) {
        bReturn = TRUE;
    }
    else if ( dwFractionMask == ( m_CounterInfo.dwType & dwFractionMask ) ) {
        bReturn = TRUE;
    }

    return bReturn;
}