/* Copyright 1996 Microsoft */

#include <priv.h>
#include "sccls.h"
#include "aclmulti.h"

//
// CACLMulti -- An AutoComplete List COM object that
//                  contains other AutoComplete Lists and
//                  has them do all the work.
//

struct _tagListItem
{
    IUnknown        *punk;
    IEnumString     *pes;
    IEnumACString   *peacs;
    IACList         *pacl;
};
typedef struct _tagListItem LISTITEM;

#define MULTILIST_GROWTH_CONST 8

/* IUnknown methods */

HRESULT CACLMulti::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IEnumString))
    {
        *ppvObj = SAFECAST(this, IEnumString*);
    }
    else if (IsEqualIID(riid, IID_IEnumACString))
    {
        *ppvObj = SAFECAST(this, IEnumACString*);
    }
    else if (IsEqualIID(riid, IID_IObjMgr))
    {
        *ppvObj = SAFECAST(this, IObjMgr*);
    }
    else if (IsEqualIID(riid, IID_IACList))
    {
        *ppvObj = SAFECAST(this, IACList*);
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

ULONG CACLMulti::AddRef(void)
{
    _cRef++;
    return _cRef;
}

ULONG CACLMulti::Release(void)
{
    ASSERT(_cRef > 0);

    _cRef--;

    if (_cRef > 0)
    {
        return _cRef;
    }

    delete this;
    return 0;
}

/* IEnumString methods */

HRESULT CACLMulti::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
    HRESULT hr = S_FALSE;    // nothing found... stop

    *pceltFetched = 0;

    if (celt == 0)
    {
        return S_OK;
    }

    if (!rgelt)
    {
        hr = E_FAIL;
    }

    if (SUCCEEDED(hr) && _hdsa)
    {
        //
        // Keep calling Next() starting with the current list
        // until somebody returns something.
        //
        for( ; _iSubList < DSA_GetItemCount(_hdsa); _iSubList++)
        {
            LISTITEM li;

            if ((DSA_GetItem(_hdsa, _iSubList, &li) != -1) && (li.pes != NULL))
            {
                hr = li.pes->Next(1, rgelt, pceltFetched);
                if (hr == S_OK)
                    break;

                if (FAILED(hr))  // Why is the caller failing? 
                    hr = S_FALSE;   // Probably because it failed to conntect to the source (ftp)
            }
        }
    }
    ASSERT(SUCCEEDED(hr));

    return hr;
}

HRESULT CACLMulti::Skip(ULONG)
{
    return E_NOTIMPL;
}

HRESULT CACLMulti::Reset(void)
{
    HRESULT hr = S_OK;
    TraceMsg(TF_BAND|TF_GENERAL, "ACLMulti::Reset() Beginning");

    if (_hdsa)
    {
        // Call Reset() on each sublist.
        for (_iSubList=0; _iSubList < DSA_GetItemCount(_hdsa); _iSubList++)
        {
            LISTITEM li;
            
            if ((DSA_GetItem(_hdsa, _iSubList, &li) != -1) && (li.pes != NULL))
            {
                hr = li.pes->Reset();
                if (FAILED(hr))
                    break;
            }
        }
    }

    // Reset ourselves to point to the first list.
    _iSubList = 0;

    return hr;
}

HRESULT CACLMulti::Clone(IEnumString **ppenum)
{
    return CACLMulti_Create(ppenum, this);
}

// IEnumAutocomplete methods
HRESULT CACLMulti::NextItem(LPOLESTR pszUrl, ULONG cchMax, ULONG* pulSortIndex)
{
    HRESULT hr = S_FALSE;    // nothing found... stop

    if (!pszUrl)
    {
        hr = E_FAIL;
    }

    if (SUCCEEDED(hr) && _hdsa)
    {
        //
        // Keep calling Next() starting with the current list
        // until somebody returns something.
        //
        for( ; _iSubList < DSA_GetItemCount(_hdsa); _iSubList++)
        {
            LISTITEM li;

            if ((DSA_GetItem(_hdsa, _iSubList, &li) != -1) && (li.pes != NULL))
            {
                // Use the IEnumACString interface if we have it
                if (NULL != li.peacs)
                {
                    hr = li.peacs->NextItem(pszUrl, cchMax, pulSortIndex);
                }

                // Fall back to the old IEnumString interface
                else
                {
                    LPWSTR pszNext;
                    ULONG ulFetched;

                    hr = li.pes->Next(1, &pszNext, &ulFetched);
                    if (S_OK == hr)
                    {
                        StrCpyN(pszUrl, pszNext, cchMax);
                        if (pulSortIndex)
                        {
                            *pulSortIndex = 0;
                        }
                        CoTaskMemFree(pszNext);
                    }
                }
                if (hr == S_OK)
                    break;

                if (FAILED(hr))  // Why is the caller failing? 
                    hr = S_FALSE;   // Probably because it failed to conntect to the source (ftp)
            }
        }
    }
    ASSERT(SUCCEEDED(hr));

    return hr;
}

/* IObjMgr methods */

HRESULT CACLMulti::Append(IUnknown *punk)
{
    HRESULT hr = E_FAIL;

    if (punk)
    {
        if (!_hdsa)
        {
            _hdsa = DSA_Create(SIZEOF(LISTITEM), MULTILIST_GROWTH_CONST);
        }

        if (_hdsa)
        {
            LISTITEM li = { 0 };

            //
            // Call QI to get the necessary interfaces,
            // and append the interfaces to the internal list.
            //
            li.punk = punk;
            li.punk->AddRef();

            li.punk->QueryInterface(IID_IEnumString, (LPVOID *)&li.pes);
            li.punk->QueryInterface(IID_IEnumACString, (LPVOID *)&li.peacs);
            li.punk->QueryInterface(IID_IACList, (LPVOID *)&li.pacl);

            if (DSA_AppendItem(_hdsa, &li) != -1)
            {
                hr = S_OK;
            }
            else
            {
                _FreeListItem(&li, 0);
                hr = E_FAIL;
            }
        }
    }

    return hr;
}

HRESULT CACLMulti::Remove(IUnknown *punk)
{
    HRESULT hr = E_FAIL;
    int i;

    if (punk && _hdsa)
    {
        for(i=DPA_GetPtrCount(_hdsa); i>=0; i--)
        {
            LISTITEM li;

            if (DSA_GetItem(_hdsa, i, &li) != -1)
            {
                if (punk == li.punk)
                {
                    _FreeListItem(&li, 0);
                    if (DSA_DeleteItem(_hdsa, i))
                    {
                        hr = S_OK;
                    }
                    break;
                }
            }
        }
    }

    return hr;
}


/* IACList methods */

HRESULT CACLMulti::Expand(LPCOLESTR pszExpand)
{
    HRESULT hr = S_OK;
    int i;

    if (_hdsa)
    {
        // Call Expand() on each sublist.
        for (i=0; i < DSA_GetItemCount(_hdsa); i++)
        {
            LISTITEM li;
            
            if ((DSA_GetItem(_hdsa, i, &li) != -1) && (li.pacl != NULL))
            {
                hr = li.pacl->Expand(pszExpand);
                if (hr == S_OK)
                    break;
            }
        }
    }
    
    if (E_NOTIMPL == hr)
        hr = S_OK;

    return hr;
}

/* Constructor / Destructor / CreateInstance */

CACLMulti::CACLMulti()
{
    DllAddRef();
    ASSERT(!_hdsa);
    ASSERT(!_iSubList);
    _cRef = 1;
}

CACLMulti::~CACLMulti()
{
    if (_hdsa)
    {
        DSA_DestroyCallback(_hdsa, _FreeListItem, 0);
        _hdsa = NULL;
    }

    DllRelease();
}

HRESULT CACLMulti_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
    // aggregation checking is handled in class factory


    *ppunk = NULL;
    CACLMulti * p = new CACLMulti();
    if (p) 
    {
        *ppunk = SAFECAST(p, IEnumString *);
        return NOERROR;
    }

    return E_OUTOFMEMORY;
}

HRESULT CACLMulti_Create(IEnumString **ppenum, CACLMulti * paclMultiToCopy)
{
    HRESULT hr = E_OUTOFMEMORY;
    *ppenum = NULL;
    CACLMulti * p = new CACLMulti();
    if (p) 
    {
        if (paclMultiToCopy->_hdsa)
        {
            // Clone data
            int iSize = DSA_GetItemCount(paclMultiToCopy->_hdsa);
            int iIndex;
            LISTITEM li;

            hr = S_OK;
            p->_hdsa = DSA_Create(SIZEOF(LISTITEM), MULTILIST_GROWTH_CONST);

            // We need to copy the source HDSA
            for (iIndex = 0; (iIndex < iSize) && (S_OK == hr); iIndex++)
            {
                if (DSA_GetItem(paclMultiToCopy->_hdsa, iIndex, &li) != -1)
                    hr = p->Append(li.punk);
                else
                    hr = E_FAIL;
            }
            p->_iSubList = paclMultiToCopy->_iSubList;

            if (SUCCEEDED(hr))
                *ppenum = SAFECAST(p, IEnumString *);
            else
                p->Release();
        }
        else
        {
            p->Release();
        }
    }

    return hr;
}

//
// Frees all the contents of one list item.
//
int CACLMulti::_FreeListItem(LPVOID p, LPVOID d)
{
    LISTITEM *pli = (LISTITEM *)p;

    SAFERELEASE(pli->pacl);
    SAFERELEASE(pli->pes);
    SAFERELEASE(pli->peacs);
    SAFERELEASE(pli->punk);

    return 1;
}
