#define UNICODE
#define _OLE32_

#include <windows.h>
#include <shlobj.h>

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <eh.h>

#include "minici.hxx"

typedef HRESULT (STDAPICALLTYPE * LPStgOpenStorageEx) (
    const WCHAR* pwcsName,
    DWORD grfMode,
    DWORD stgfmt,              // enum
    DWORD grfAttrs,             // reserved
    STGOPTIONS * pStgOptions,
    void * reserved,
    REFIID riid,
    void ** ppObjectOpen );

typedef HRESULT (STDAPICALLTYPE * PSHGetDesktopFolder) (
    IShellFolder ** ppshf );

typedef HRESULT (STDAPICALLTYPE * PSHBindToParent) (
    LPCITEMIDLIST pidl,
    REFIID riid,
    void **ppv,
    LPCITEMIDLIST *ppidlLast);

PSHGetDesktopFolder pShGetDesktopFolder = 0;
PSHBindToParent pShBindToParent = 0;

void Usage()
{
    printf( "usage: enumprop [-s] filename\n" );
    printf( "       -s   -- use the shell's property code instead of OLE\n" );
    exit( 1 );
} //Usage

//+-------------------------------------------------------------------------
//
//  Function:   Render
//
//  Synopsis:   Prints an item in a safearray
//
//  Arguments:  [vt]  - type of the element
//              [pa]  - pointer to the item
//
//--------------------------------------------------------------------------

void PrintSafeArray( VARTYPE vt, LPSAFEARRAY pa );

void Render( VARTYPE vt, void * pv )
{
    if ( VT_ARRAY & vt )
    {
        PrintSafeArray( vt - VT_ARRAY, *(SAFEARRAY **) pv );
        return;
    }

    switch ( vt )
    {
        case VT_UI1: wprintf( L"%u", (unsigned) *(BYTE *)pv ); break;
        case VT_I1: wprintf( L"%d", (int) *(CHAR *)pv ); break;
        case VT_UI2: wprintf( L"%u", (unsigned) *(USHORT *)pv ); break;
        case VT_I2: wprintf( L"%d", (int) *(SHORT *)pv ); break;
        case VT_UI4:
        case VT_UINT: wprintf( L"%u", (unsigned) *(ULONG *)pv ); break;
        case VT_I4:
        case VT_ERROR:
        case VT_INT: wprintf( L"%d", *(LONG *)pv ); break;
        case VT_UI8: wprintf( L"%I64u", *(unsigned __int64 *)pv ); break;
        case VT_I8: wprintf( L"%I64d", *(__int64 *)pv ); break;
        case VT_R4: wprintf( L"%f", *(float *)pv ); break;
        case VT_R8: wprintf( L"%lf", *(double *)pv ); break;
        case VT_DECIMAL:
        {
            double dbl;
            VarR8FromDec( (DECIMAL *) pv, &dbl );
            wprintf( L"%lf", dbl );
            break;
        }
        case VT_CY:
        {
            double dbl;
            VarR8FromCy( * (CY *) pv, &dbl );
            wprintf( L"%lf", dbl );
            break;
        }
        case VT_BOOL: wprintf( *(VARIANT_BOOL *)pv ? L"TRUE" : L"FALSE" ); break;
        case VT_BSTR: wprintf( L"%ws", *(BSTR *) pv ); break;
        case VT_VARIANT:
        {
            PROPVARIANT * pVar = (PROPVARIANT *) pv;
            Render( pVar->vt, & pVar->lVal );
            break;
        }
        case VT_DATE:
        {
            SYSTEMTIME st;
            VariantTimeToSystemTime( *(DATE *)pv, &st );
            BOOL pm = st.wHour >= 12;

            if ( st.wHour > 12 )
                st.wHour -= 12;
            else if ( 0 == st.wHour )
                st.wHour = 12;

            wprintf( L"%2d-%02d-%04d %2d:%02d%wc",
                    (DWORD) st.wMonth,
                    (DWORD) st.wDay,
                    (DWORD) st.wYear,
                    (DWORD) st.wHour,
                    (DWORD) st.wMinute,
                    pm ? L'p' : L'a' );
            break;
        }
        case VT_EMPTY:
        case VT_NULL:
            break;
        default :
        {
            wprintf( L"(vt 0x%x)", (int) vt );
            break;
        }
    }
} //Render

//+-------------------------------------------------------------------------
//
//  Function:   PrintSafeArray
//
//  Synopsis:   Prints items in a safearray
//
//  Arguments:  [vt]  - type of elements in the safearray
//              [pa]  - pointer to the safearray
//
//--------------------------------------------------------------------------

void PrintSafeArray( VARTYPE vt, LPSAFEARRAY pa )
{
    // Get the dimensions of the array

    UINT cDim = SafeArrayGetDim( pa );
    if ( 0 == cDim )
        return;

    XPtr<LONG> xDim( cDim );
    XPtr<LONG> xLo( cDim );
    XPtr<LONG> xUp( cDim );

    for ( UINT iDim = 0; iDim < cDim; iDim++ )
    {
        HRESULT hr = SafeArrayGetLBound( pa, iDim + 1, &xLo[iDim] );
        if ( FAILED( hr ) )
            return;

        xDim[ iDim ] = xLo[ iDim ];

        hr = SafeArrayGetUBound( pa, iDim + 1, &xUp[iDim] );
        if ( FAILED( hr ) )
            return;

        wprintf( L"{" );
    }

    // slog through the array

    UINT iLastDim = cDim - 1;
    BOOL fDone = FALSE;

    while ( !fDone )
    {
        // inter-element formatting

        if ( xDim[ iLastDim ] != xLo[ iLastDim ] )
            wprintf( L"," );

        // Get the element and render it

        void *pv;
        SafeArrayPtrOfIndex( pa, xDim.Get(), &pv );
        Render( vt, pv );

        // Move to the next element and carry if necessary

        ULONG cOpen = 0;

        for ( LONG iDim = iLastDim; iDim >= 0; iDim-- )
        {
            if ( xDim[ iDim ] < xUp[ iDim ] )
            {
                xDim[ iDim ] = 1 + xDim[ iDim ];
                break;
            }

            wprintf( L"}" );

            if ( 0 == iDim )
                fDone = TRUE;
            else
            {
                cOpen++;
                xDim[ iDim ] = xLo[ iDim ];
            }
        }

        for ( ULONG i = 0; !fDone && i < cOpen; i++ )
            wprintf( L"{" );
    }
} //PrintSafeArray

//+-------------------------------------------------------------------------
//
//  Function:   PrintVectorItems
//
//  Synopsis:   Prints items in a PROPVARIANT vector
//
//  Arguments:  [pVal]  - The array of values
//              [cVals] - The count of values
//              [pcFmt] - The format string
//
//--------------------------------------------------------------------------

template<class T> void PrintVectorItems(
    T *     pVal,
    ULONG   cVals,
    char *  pcFmt )
{
    printf( "{ " );

    for( ULONG iVal = 0; iVal < cVals; iVal++ )
    {
        if ( 0 != iVal )
            printf( "," );
        printf( pcFmt, *pVal++ );
    }

    printf( " }" );
} //PrintVectorItems

//+-------------------------------------------------------------------------
//
//  Function:   DisplayValue
//
//  Synopsis:   Displays a PROPVARIANT value.  Limited formatting is done.
//
//  Arguments:  [pVar] - The value to display
//
//--------------------------------------------------------------------------

void DisplayValue( PROPVARIANT const * pVar )
{
    if ( 0 == pVar )
    {
        wprintf( L"NULL" );
        return;
    }

    // Display the most typical variant types

    PROPVARIANT const & v = *pVar;

    switch ( v.vt )
    {
        case VT_EMPTY : wprintf( L"vt_empty" ); break;
        case VT_NULL : wprintf( L"vt_null" ); break;
        case VT_I4 : wprintf( L"%10d", v.lVal ); break;
        case VT_UI1 : wprintf( L"%10d", v.bVal ); break;
        case VT_I2 : wprintf( L"%10d", v.iVal ); break;
        case VT_R4 : wprintf( L"%10f", v.fltVal ); break;
        case VT_R8 : wprintf( L"%10lf", v.dblVal ); break;
        case VT_BOOL : wprintf( v.boolVal ? L"TRUE" : L"FALSE" ); break;
        case VT_I1 : wprintf( L"%10d", v.cVal ); break;
        case VT_UI2 : wprintf( L"%10u", v.uiVal ); break;
        case VT_UI4 : wprintf( L"%10u", v.ulVal ); break;
        case VT_INT : wprintf( L"%10d", v.lVal ); break;
        case VT_UINT : wprintf( L"%10u", v.ulVal ); break;
        case VT_I8 : wprintf( L"%20I64d", v.hVal ); break;
        case VT_UI8 : wprintf( L"%20I64u", v.hVal ); break;
        case VT_ERROR : wprintf( L"%#x", v.scode ); break;
        case VT_LPSTR : wprintf( L"%S", v.pszVal ); break;
        case VT_LPWSTR : wprintf( L"%ws", v.pwszVal ); break;
        case VT_BSTR : wprintf( L"%ws", v.bstrVal ); break;
        case VT_CY:
        {
            double dbl;
            VarR8FromCy( v.cyVal, &dbl );
            wprintf( L"%lf", dbl );
            break;
        }
        case VT_DECIMAL :
        {
            double dbl;
            VarR8FromDec( (DECIMAL *) &v.decVal, &dbl );
            wprintf( L"%lf", dbl );
            break;
        }
        case VT_FILETIME :
        case VT_DATE :
        {
            SYSTEMTIME st;

            if ( VT_DATE == v.vt )
            {
                VariantTimeToSystemTime( v.date, &st );
            }
            else
            {
#if 0
                FILETIME ft;
                FileTimeToLocalFileTime( &v.filetime, &ft );
                FileTimeToSystemTime( &ft, &st );
#else
                FileTimeToSystemTime( &v.filetime, &st );
#endif
            }

            BOOL pm = st.wHour >= 12;

            if ( st.wHour > 12 )
                st.wHour -= 12;
            else if ( 0 == st.wHour )
                st.wHour = 12;

            wprintf( L"%2d-%02d-%04d %2d:%02d%wc",
                    (DWORD) st.wMonth,
                    (DWORD) st.wDay,
                    (DWORD) st.wYear,
                    (DWORD) st.wHour,
                    (DWORD) st.wMinute,
                    pm ? L'p' : L'a' );
            break;
        }
        case VT_VECTOR | VT_I1:
            PrintVectorItems( v.cac.pElems, v.cac.cElems, "%d" ); break;
        case VT_VECTOR | VT_I2:
            PrintVectorItems( v.cai.pElems, v.cai.cElems, "%d" ); break;
        case VT_VECTOR | VT_I4:
            PrintVectorItems( v.cal.pElems, v.cal.cElems, "%d" ); break;
        case VT_VECTOR | VT_I8:
            PrintVectorItems( v.cah.pElems, v.cah.cElems, "%I64d" ); break;
        case VT_VECTOR | VT_UI1:
            PrintVectorItems( v.caub.pElems, v.caub.cElems, "%u" ); break;
        case VT_VECTOR | VT_UI2:
            PrintVectorItems( v.caui.pElems, v.caui.cElems, "%u" ); break;
        case VT_VECTOR | VT_UI4:
            PrintVectorItems( v.caul.pElems, v.caul.cElems, "%u" ); break;
        case VT_VECTOR | VT_ERROR:
            PrintVectorItems( v.cascode.pElems, v.cascode.cElems, "%#x" ); break;
        case VT_VECTOR | VT_UI8:
            PrintVectorItems( v.cauh.pElems, v.cauh.cElems, "%I64u" ); break;
        case VT_VECTOR | VT_BSTR:
            PrintVectorItems( v.cabstr.pElems, v.cabstr.cElems, "%ws" ); break;
        case VT_VECTOR | VT_LPSTR:
            PrintVectorItems( v.calpstr.pElems, v.calpstr.cElems, "%S" ); break;
        case VT_VECTOR | VT_LPWSTR:
            PrintVectorItems( v.calpwstr.pElems, v.calpwstr.cElems, "%ws" ); break;
        case VT_VECTOR | VT_R4:
            PrintVectorItems( v.caflt.pElems, v.caflt.cElems, "%f" ); break;
        case VT_VECTOR | VT_R8:
            PrintVectorItems( v.cadbl.pElems, v.cadbl.cElems, "%lf" ); break;
        default : 
        {
            if ( VT_ARRAY & v.vt )
                PrintSafeArray( v.vt - VT_ARRAY, v.parray );
            else
                wprintf( L"vt 0x%05x", v.vt );
            break;
        }
    }
} //DisplayValue

void DumpProps(
    XInterface<IPropertySetStorage> & xPropSetStorage,
    BOOL                              fBonusProperties )
{
    // Get enumerator for property set

    XInterface<IEnumSTATPROPSETSTG> xPropSetEnum;

    HRESULT hr = xPropSetStorage->Enum( xPropSetEnum.GetPPointer() );
    if ( FAILED( hr ) )
    {
        printf( "IPropertySetStorage::Enum failed: %#x\n", hr );
        exit( 1 );
    }

    STATPROPSETSTG propset;
    BOOL fUserProp = !fBonusProperties;
    
    while( ( (hr = xPropSetEnum->Next(1, &propset, NULL)) == S_OK ) ||
           !fUserProp)
    {
        GUID FormatID;
        if ( S_OK == hr )
        {
            FormatID = propset.fmtid;
        }
        else
        {
            FormatID = FMTID_UserDefinedProperties;
            fUserProp = TRUE;
        }
        
        XInterface<IPropertyStorage> xPropStorage;

        hr = xPropSetStorage->Open( FormatID,
                                    STGM_READ | STGM_SHARE_EXCLUSIVE,
                                    xPropStorage.GetPPointer() );

        if ( ( ( E_FAIL == hr ) || ( STG_E_FILENOTFOUND == hr ) ) &&
             ( FMTID_UserDefinedProperties == FormatID ) )
        {
            printf( "IPropertySetStorage::Open failed with %#x\n", hr );
            hr = S_OK;
            continue;
        }
        else if ( FAILED( hr ) )
        {
            printf( "IPropertySetStorage::Open failed badly with %#x\n", hr );
            exit( 1 );
        }

        printf( "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
                FormatID.Data1,
                FormatID.Data2,
                FormatID.Data3,
                FormatID.Data4[0], FormatID.Data4[1],
                FormatID.Data4[2], FormatID.Data4[3],
                FormatID.Data4[4], FormatID.Data4[5],
                FormatID.Data4[6], FormatID.Data4[7] );

        XInterface<IEnumSTATPROPSTG> xEnumStatPropStg;
        
        // Get enumerator for property

        hr = xPropStorage->Enum( xEnumStatPropStg.GetPPointer() );

        if ( FAILED( hr ) )
        {
            printf( "IPropertyStorage::Enum failed %#x\n", hr );
            continue;
        }
        
        PROPVARIANT prop;
        PropVariantInit( &prop );
        
        // Get the locale for properties

        PROPSPEC ps;
        ps.ulKind = PRSPEC_PROPID;
        ps.propid = PID_LOCALE;

        hr = xPropStorage->ReadMultiple( 1, 
                                         &ps,
                                         &prop );

        if ( SUCCEEDED( hr ) )
        {
            if ( VT_EMPTY == prop.vt )
            {
                printf( "  no lcid, so using system default\n" );
            }
            else
            {
                printf( "  locale: %d (%#x)\n", prop.ulVal, prop.ulVal );
                PropVariantClear(&prop);
            }
        }
        else
        {
            printf( "  can't read the locale: %#x\n", hr );
        }
        
        // Get the code page for properties

        PROPSPEC psCodePage = { PRSPEC_PROPID, PID_CODEPAGE };
        
        hr = xPropStorage->ReadMultiple(1, &psCodePage, &prop);
        
        if ( SUCCEEDED( hr ) )
        {
            if(VT_I2 == prop.vt)
                printf( "  codepage: %d (%#x)\n", (UINT)prop.uiVal, (UINT) prop.uiVal );
            else
                printf( "  vt of codepage: %d (%#x)\n", prop.vt, prop.vt );

            PropVariantClear( &prop );
        }
        else
        {
            printf( "  no codepage, assume ansi\n" );
        }
        
        // Enumerate all properties in the property set

        STATPROPSTG statPS;
        ULONG ul;

        if ( S_OK != hr )
        {
            hr = S_OK;
        }

        while ( ( S_OK == xEnumStatPropStg->Next( 1, &statPS, &ul ) ) &&
                ( 1 == ul ) &&
                ( SUCCEEDED( hr ) ) )
        {
            if ( 0 != statPS.lpwstrName )
            {
                printf( "  name: '%ws', ", statPS.lpwstrName );

                ps.ulKind = PRSPEC_LPWSTR;
                ps.lpwstr = statPS.lpwstrName;
            }
            else
            {
                printf( "  pid: %d (%#x), ", statPS.propid, statPS.propid );

                ps.ulKind = PRSPEC_PROPID;
                ps.propid = statPS.propid;
            }

            hr = xPropStorage->ReadMultiple( 1, 
                                             &ps,
                                             &prop );

            if ( SUCCEEDED( hr ) )
            {
                if ( S_FALSE == hr )
                    printf( "readmultiple returned S_FALSE!\n" );

                printf( "vt: %d (%#x), ", prop.vt, prop.vt );

                DisplayValue( &prop );
                printf( "\n" );

                PropVariantClear( &prop );
            }
            else
            {
                printf( "  IPropertyStorage::ReadMultiple failed: %#x\n", hr );
                hr = S_OK;
            }
        }
    }
} //DumpProps

HRESULT BindToItemByName(
    WCHAR const * pszFile,
    REFIID        riid,
    void **       ppv )
{
    XInterface<IShellFolder> xDesktop;
    HRESULT hr = pShGetDesktopFolder( xDesktop.GetPPointer() );

    if ( SUCCEEDED( hr ) )
    {
        XInterface<IBindCtx> xBindCtx;

        hr = CreateBindCtx( 0, xBindCtx.GetPPointer() );
        if ( FAILED( hr ) )
            return hr;

        BIND_OPTS bo = {sizeof(bo), 0};
        bo.grfFlags = BIND_JUSTTESTEXISTENCE;   // skip all junctions

        hr = xBindCtx->SetBindOptions( &bo );

        if ( FAILED( hr ) )
            return hr;

        LPITEMIDLIST pidl;

        // cast needed for bad interface def
    
        hr = xDesktop->ParseDisplayName( 0,
                                         xBindCtx.GetPointer(),
                                         (LPWSTR) pszFile,
                                         0,
                                         &pidl,
                                         0 );
        if ( SUCCEEDED( hr ) )
        {
            XInterface<IShellFolder> xSF;
            LPCITEMIDLIST pidlChild;
    
            hr = pShBindToParent( pidl,
                                  IID_IShellFolder,
                                  xSF.GetQIPointer(),
                                  &pidlChild );
            if (SUCCEEDED(hr))
                hr = xSF->BindToObject( pidlChild, 0, riid, ppv );
            else
                printf( "SHBindToParent failed: %#x\n", hr );
    
            CoTaskMemFree( pidl );
        }
        else
        {
            printf( "IShellFolder::ParseDisplayNamed failed %#x\n", hr );
        }
    }
    else
    {
        printf( "SHGetDesktopFolder failed: %#x\n", hr );
    }

    return hr;
} //BindToItemByName

extern "C" int __cdecl wmain( int argc, WCHAR * argv[] )
{
    if ( 2 != argc && 3 != argc )
        Usage();

    BOOL fUseOLE = TRUE;

    if ( ( 3 == argc ) && !_wcsicmp( L"-s", argv[1] ) )
        fUseOLE = FALSE;

    WCHAR awcPath[MAX_PATH];

    _wfullpath( awcPath, argv[ (2 == argc) ? 1 : 2 ], MAX_PATH );

    HRESULT hr = CoInitialize( 0 );

    if ( FAILED( hr ) )
    {
        printf( "can't init com: %#x\n", hr );
        exit( 1 );
    }

    if ( fUseOLE )
    {
        BOOL fWindows2000Plus = FALSE;

        OSVERSIONINFOA ovi;
        ovi.dwOSVersionInfoSize = sizeof ovi;
        GetVersionExA( &ovi );

        if ( ( VER_PLATFORM_WIN32_NT == ovi.dwPlatformId ) &&
             ( ovi.dwMajorVersion >= 5 ) )
            fWindows2000Plus = TRUE;

        HINSTANCE h = LoadLibraryA( "ole32.dll" );
        if ( 0 == h )
        {
            printf( "can't load ole32.dll\n" );
            exit( 1 );
        }

        LPStgOpenStorageEx pOpen = (LPStgOpenStorageEx) GetProcAddress( h, "StgOpenStorageEx" );

        // Note: on some platforms closing the IStorage before finishing with
        // the IPropertySetStorage will result in the object going away.  It's a bug
        // in OLE.
    
        XInterface<IStorage> xStorage;
        XInterface<IPropertySetStorage> xPropSetStorage;
    
        if ( fWindows2000Plus && 0 != pOpen )
        {
            HRESULT hr = pOpen( awcPath,
                                STGM_DIRECT |
                                    STGM_READ |
                                    STGM_SHARE_DENY_WRITE,
                                STGFMT_ANY,
                                0,
                                0,
                                0,
                                IID_IPropertySetStorage,      
                                xPropSetStorage.GetQIPointer() );
            if ( FAILED( hr ) )
            {
                printf( "failed to openEx the file: %#x\n", hr );
                exit( 1 );
            }
        }
        else
        {
            HRESULT hr = StgOpenStorage( awcPath,
                                         0,
                                         STGM_READ | STGM_SHARE_DENY_WRITE,
                                         0,
                                         0, 
                                         xStorage.GetPPointer() );
            if ( FAILED( hr ) )
            {
                printf( "StgOpenStorage failed to open the file: %#x\n", hr );
                exit( 1 );
            }

            // Rely on iprop.dll on Win9x, since OLE32 doesn't have the code
        
            hr = StgCreatePropSetStg( xStorage.GetPointer(),
                                      0,
                                      xPropSetStorage.GetPPointer() );
            
            if ( FAILED( hr ) )
            {
                printf( "StgCreatePropSetStg failed: %#x\n", hr );
                exit( 1 );
            }
        }
    
        DumpProps( xPropSetStorage, TRUE );

        FreeLibrary( h );
    }
    else
    {
        HINSTANCE h = LoadLibrary( L"shell32.dll" );
        if ( 0 == h )
        {
            printf( "can't load shell32.dll\n" );
            exit( 1 );
        }

        pShGetDesktopFolder = (PSHGetDesktopFolder) GetProcAddress( h, "SHGetDesktopFolder" );
        if ( 0 == pShGetDesktopFolder )
        {
            printf( "can't find SHGetDesktopFolder in shell32.dll\n" );
            exit( 1 );
        }

        pShBindToParent = (PSHBindToParent) GetProcAddress( h, "SHBindToParent" );
        if ( 0 == pShBindToParent )
        {
            printf( "can't find SHBindToParent in shell32.dll\n" );
            exit( 1 );
        }

        XInterface<IPropertySetStorage> xPropSetStorage;
    
        CLSID clsidPSS = IID_IPropertySetStorage;

        hr = BindToItemByName( awcPath,
                               clsidPSS,
                               xPropSetStorage.GetQIPointer() );
        if ( FAILED( hr ) )
            printf( "couldn't bind to item %ws by name: %#x\n", awcPath, hr );
        else
            DumpProps( xPropSetStorage, FALSE );

        FreeLibrary( h );
    }

    CoUninitialize();

    return 0;
} //wmain