/*++

    Implements IEnumIDList.

--*/

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

#include "pstore.h"

#include "utility.h"

#include "enumid.h"
#include "shfolder.h"



extern LONG       g_DllRefCount;



CEnumIDList::CEnumIDList(
    LPITEMIDLIST pidl,
    BOOL bEnumItems
    )
{


    m_pIEnumProviders = NULL;
    m_pIPStoreProvider = NULL;
    m_pIEnumTypes = NULL;
    m_pIEnumTypesGlobal = NULL;
    m_pIEnumSubtypes = NULL;
    m_pIEnumItems = NULL;

    m_bEnumItems = bEnumItems;


    //
    // get the shell's IMalloc pointer
    // we'll keep this until we get destroyed
    //

    if(FAILED(SHGetMalloc(&m_pMalloc)))
        delete this;

    m_KeyType = PST_KEY_CURRENT_USER;

    if(pidl == NULL) {

        if( PStoreEnumProviders(0, &m_pIEnumProviders) != S_OK )
            m_pIEnumProviders = NULL;

        m_dwType = PIDL_TYPE_PROVIDER;  // top-level
    } else {
        m_dwType = GetLastPidlType(pidl) + 1;   // parent + 1
    }

    //
    // get provider interface
    //

    if(m_dwType > PIDL_TYPE_PROVIDER) {
        PST_PROVIDERID      *ProviderId = GetPidlGuid(pidl);
        if(PStoreCreateInstance(&m_pIPStoreProvider, ProviderId, NULL, 0) != S_OK)
            m_pIPStoreProvider = NULL;
    }

    //
    // prepare two type enumerators, if appropriate:
    // PST_KEY_CURRENT_USER and PST_KEY_LOCAL_MACHINE
    //

    if(m_dwType == PIDL_TYPE_TYPE && m_pIPStoreProvider) {
        m_pIPStoreProvider->EnumTypes(PST_KEY_CURRENT_USER, 0, &m_pIEnumTypes);
        m_pIPStoreProvider->EnumTypes(PST_KEY_LOCAL_MACHINE, 0, &m_pIEnumTypesGlobal);
    }

    //
    // prepare subtype enumerator
    //

    if(m_dwType == PIDL_TYPE_SUBTYPE && m_pIPStoreProvider) {
        GUID    *pguidType;

        m_KeyType = GetLastPidlKeyType(pidl);
        pguidType = GetLastPidlGuid(pidl);
        CopyMemory(&m_guidType, pguidType, sizeof(GUID));

        m_pIPStoreProvider->EnumSubtypes(
                m_KeyType,
                pguidType,
                0,
                &m_pIEnumSubtypes
                );
    }

    //
    // prepare item enumerator if appropriate.
    //

    if(m_dwType == PIDL_TYPE_ITEM && m_bEnumItems && m_pIPStoreProvider) {
        GUID    *pguidType;
        GUID    *pguidSubtype;

        m_KeyType = GetLastPidlKeyType(pidl);
        pguidSubtype = GetLastPidlGuid(pidl);
        CopyMemory(&m_guidSubtype, pguidSubtype, sizeof(GUID));

        pguidType = GetPidlGuid(SearchPidlByType(pidl, PIDL_TYPE_TYPE));
        CopyMemory(&m_guidType, pguidType, sizeof(GUID));

        m_pIPStoreProvider->EnumItems(
                m_KeyType,
                pguidType,
                pguidSubtype,
                0,
                &m_pIEnumItems
                );
    }

    m_ulCurrent = 0;
    m_ObjRefCount = 1;

    InterlockedIncrement(&g_DllRefCount);
}


CEnumIDList::~CEnumIDList()
{
    //
    // free any interfaces we established.
    //

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

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

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

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

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

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

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

    InterlockedDecrement(&g_DllRefCount);
}



STDMETHODIMP
CEnumIDList::QueryInterface(
    REFIID riid,
    LPVOID *ppReturn
    )
{
    *ppReturn = NULL;

    if(IsEqualIID(riid, IID_IUnknown))
        *ppReturn = (IUnknown*)(CEnumIDList*)this;
    else if(IsEqualIID(riid, IID_IEnumIDList))
        *ppReturn = (CEnumIDList*)this;

    if(*ppReturn == NULL)
        return E_NOINTERFACE;

    (*(LPUNKNOWN*)ppReturn)->AddRef();
    return S_OK;
}


STDMETHODIMP_(DWORD)
CEnumIDList::AddRef()
{
    return InterlockedIncrement(&m_ObjRefCount);
}


STDMETHODIMP_(DWORD)
CEnumIDList::Release()
{
    LONG lDecremented = InterlockedDecrement(&m_ObjRefCount);

    if(lDecremented == 0)
        delete this;

    return lDecremented;
}

STDMETHODIMP
CEnumIDList::Next(
    ULONG ulElements,
    LPITEMIDLIST *pPidl,
    ULONG *pulFetched
    )
/*++

    Retrieves the specified number of item identifiers in the enumeration
    sequence and advances the current position.

    Returns the NOERROR value if successful,
    Returns S_FALSE value if there are no more items in the enumeration
    or an OLE-defined error value if an error occurs.

--*/
{
    HRESULT hr;

    *pPidl = NULL;
    *pulFetched = 0;

    if(m_bEnumItems) {
        if(m_dwType > PIDL_TYPE_ITEM)
            return S_FALSE;
    } else {
        if(m_dwType > PIDL_TYPE_SUBTYPE)
            return S_FALSE; // nothing left to enumerate
    }


    if(m_dwType == PIDL_TYPE_PROVIDER && m_pIEnumProviders)
    {
        PPST_PROVIDERINFO   pProvInfo;

        if( m_pIEnumProviders->Next(1, &pProvInfo, &m_ulCurrent) != S_OK ) {

            //
            // enumeration of providers complete
            //

            return S_FALSE;
        }

        hr = CreateIDList(m_dwType, m_KeyType, &(pProvInfo->ID), pProvInfo->szProviderName, pPidl);

        CoTaskMemFree(pProvInfo);

        if(hr != S_OK)
            return S_FALSE;

        *pulFetched = 1;
        return NOERROR;
    }

    //
    // must have a valid provider interface at this point
    //

    if(m_pIPStoreProvider == NULL)
        return S_FALSE;

    if(m_dwType == PIDL_TYPE_TYPE) {

        IEnumPStoreTypes    *pIEnumTypes;
        PST_KEY             KeyType = PST_KEY_CURRENT_USER;
        GUID                guidType;

type_enum:

        if(KeyType == PST_KEY_LOCAL_MACHINE)
            pIEnumTypes = m_pIEnumTypesGlobal;
        else
            pIEnumTypes = m_pIEnumTypes;

        if(pIEnumTypes == NULL)
            return S_FALSE;

        if(pIEnumTypes->Next(1, &guidType, &m_ulCurrent) != S_OK) {

            //
            // if enumeration at PST_KEY_CURRENT_USER level complete,
            // continue at the PST_KEY_LOCAL_MACHINE level.
            //

            if(KeyType == PST_KEY_CURRENT_USER) {
                KeyType = PST_KEY_LOCAL_MACHINE;
                goto type_enum;
            }

            //
            // enumeration of types complete
            //

            return S_FALSE;
        }

        PST_TYPEINFO        *pTypeInfo = NULL;

        if(S_OK != m_pIPStoreProvider->GetTypeInfo(
                    KeyType,
                    &guidType,
                    &pTypeInfo,
                    0
                    )) return S_FALSE;

        hr = CreateIDList(m_dwType, KeyType, &guidType, pTypeInfo->szDisplayName, pPidl);

        //
        // free pTypeInfo data
        //

        CoTaskMemFree(pTypeInfo);

        if(hr != S_OK)
            return S_FALSE;

        *pulFetched = 1;
        return NOERROR;
    }

    if (m_dwType == PIDL_TYPE_SUBTYPE && m_pIEnumSubtypes) {

        GUID                guidSubtype;
        GUID                *pguidType = &m_guidType;

        if(m_pIEnumSubtypes->Next(1, &guidSubtype, &m_ulCurrent) != S_OK) {

            //
            // enumeration of types complete
            //

            return S_FALSE;
        }

        PST_TYPEINFO        *pTypeInfo = NULL;

        if(m_pIPStoreProvider->GetSubtypeInfo(m_KeyType, pguidType, &guidSubtype, &pTypeInfo, 0) != S_OK)
            return S_FALSE;

        hr = CreateIDList(m_dwType, m_KeyType, &guidSubtype, pTypeInfo->szDisplayName, pPidl);

        //
        // free pTypeInfo data
        //

        CoTaskMemFree(pTypeInfo);

        if(hr != S_OK)
            return S_FALSE;

        *pulFetched = 1;
        return NOERROR;
    }

    if(m_dwType == PIDL_TYPE_ITEM && m_pIEnumItems) {

        LPWSTR pszItem;

        //
        // enumerate and add items associated with specified subtype
        //

        if(m_pIEnumItems->Next(1, &pszItem, &m_ulCurrent) != S_OK) {

            //
            // enumeration of items complete
            //

            return S_FALSE;
        }

        hr = CreateIDList(
                    m_dwType,
                    m_KeyType,
                    NULL, // no item guid
                    pszItem,
                    pPidl
                    );

        //
        // free pszItem data
        //

        CoTaskMemFree(pszItem);

        if(hr != S_OK)
            return S_FALSE;

        *pulFetched = 1;
        return NOERROR;
    }

    return S_FALSE;
}


STDMETHODIMP
CEnumIDList::CreateIDList(
    DWORD dwType,
    PST_KEY KeyType,
    GUID *guid,
    LPCWSTR szString,
    LPITEMIDLIST *pPidlOut
    )
{
    DWORD cbString = 0;
    DWORD cbPidlContent;

    if(szString) {
        cbString = lstrlenW(szString);
    }

    cbString = (cbString + 1) * sizeof(WCHAR);

    cbPidlContent = sizeof(PIDL_CONTENT) + cbString;

    //
    // allocate the memory
    //

    *pPidlOut = (LPITEMIDLIST)m_pMalloc->Alloc(
            sizeof(ITEMIDLIST) +    // this item's ITEMIDLIST
            cbPidlContent +         // this item's data
            sizeof(ITEMIDLIST)      // terminal ITEMIDLIST
            );

    if(*pPidlOut == NULL)
        return E_OUTOFMEMORY;


    LPITEMIDLIST pidlTemp = *pPidlOut;
    LPPIDL_CONTENT pidlContent = (LPPIDL_CONTENT)&(pidlTemp->mkid.abID);


    //
    // set the size of this item
    //

    pidlTemp->mkid.cb = (unsigned short)(sizeof(ITEMIDLIST) + cbPidlContent);

    //
    // set the data for this item
    //

    pidlContent->dwType = dwType;
    pidlContent->KeyType = KeyType;

    if( guid ) {
        CopyMemory(&(pidlContent->guid), guid, sizeof(GUID));
    } else {
        ZeroMemory(&(pidlContent->guid), sizeof(GUID));
    }

    if(szString) {
        CopyMemory((LPBYTE)(pidlContent+1), szString, cbString);
    } else {
        ((LPWSTR)(pidlContent+1))[0] = L'\0';
    }

    //
    // advance and terminate item ID list
    //

    pidlTemp = (LPITEMIDLIST)GetPidlNextItem(*pPidlOut);
    pidlTemp->mkid.cb = 0;
    pidlTemp->mkid.abID[0] = 0;

    return NOERROR;
}



STDMETHODIMP
CEnumIDList::Skip(
    ULONG ulSkip
    )
{
    m_ulCurrent += ulSkip;

    return NOERROR;
}


STDMETHODIMP
CEnumIDList::Reset(
    void
    )
{
    m_ulCurrent = 0;

    return NOERROR;
}


STDMETHODIMP
CEnumIDList::Clone(
    LPENUMIDLIST *ppEnum
    )
{
    return E_NOTIMPL;
}
