/*++

    Implements IShellFolder.

    IUnknown
    IPersist
    IPersistFolder
    IShellFolder
    IExtractIcon

Soon:

    IContextMenu
    IDataObject
    IShellPropSheetExt ?

--*/

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

#include <docobj.h> // IOleCommandTarget

#include "pstore.h"

#include "enumid.h"
#include "utility.h"

#include "shfolder.h"
#include "shview.h"
#include "icon.h"   // icon handling
#include "guid.h"
#include "resource.h"


extern HINSTANCE    g_hInst;
extern LONG         g_DllRefCount;


CShellFolder::CShellFolder(
    CShellFolder *pParent,
    LPCITEMIDLIST pidl
    )
{

    m_pSFParent = pParent;
    if(m_pSFParent) {
        m_pSFParent->AddRef();
    }

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

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

    //
    // make a copy of the pidl in it's entirety.  We do this so we are free
    // to look at the pidl later.
    //

    m_pidl = CopyPidl(m_pMalloc, pidl);

    m_ObjRefCount = 1;
    InterlockedIncrement(&g_DllRefCount);
}


CShellFolder::~CShellFolder()
{
    if(m_pSFParent)
        m_pSFParent->Release();

    if(m_pidl)
        m_pMalloc->Free(m_pidl);

    if(m_pMalloc)
        m_pMalloc->Release();

    InterlockedDecrement(&g_DllRefCount);
}


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

    if(IsEqualIID(riid, IID_IUnknown))
        *ppReturn = (IUnknown*)(IShellFolder*)this;
    else if(IsEqualIID(riid, IID_IPersistFolder))
        *ppReturn = (IPersistFolder*)(CShellFolder*)this;
    else if(IsEqualIID(riid, IID_IShellFolder))
        *ppReturn = (CShellFolder*)this;

    if(*ppReturn == NULL)
        return E_NOINTERFACE;

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


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


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

    if(lDecremented == 0)
        delete this;

    return lDecremented;
}


STDMETHODIMP
CShellFolder::GetClassID(
    LPCLSID lpClassID
    )
/*++

    IPersist::GetClassID

--*/
{
    *lpClassID = CLSID_PStoreNameSpace;

    return S_OK;
}


STDMETHODIMP
CShellFolder::Initialize(
    LPCITEMIDLIST pidl
    )
/*++

    IPersistFolder::Initialize

--*/
{
    return S_OK;
}



STDMETHODIMP
CShellFolder::BindToObject(
    LPCITEMIDLIST pidl,
    LPBC pbcReserved,
    REFIID riid,
    LPVOID *ppvOut
    )
/*++

    Creates an IShellFolder object for a subfolder.

--*/
{
    CShellFolder   *pShellFolder;

    pShellFolder = new CShellFolder(this, pidl);
    if(pShellFolder == NULL)
        return E_OUTOFMEMORY;

    HRESULT  hr = pShellFolder->QueryInterface(riid, ppvOut);

    pShellFolder->Release();

    return hr;
}

STDMETHODIMP
CShellFolder::CompareIDs(
    LPARAM lParam,
    LPCITEMIDLIST pidl1,
    LPCITEMIDLIST pidl2
    )
/*++
    Determines the relative ordering of two file objects or folders,
    given their item identifier lists.

    Returns a handle to a result code. If this method is successful,
    the CODE field of the status code (SCODE) has the following meaning:

    Less than zero      The first item should precede the second (pidl1 < pidl2).
    Greater than zero   The first item should follow the second (pidl1 > pidl2)
    Zero                The two items are the same (pidl1 = pidl2).

    Passing 0 as the lParam indicates sort by name.
    0x00000001-0x7fffffff are for folder specific sorting rules.
    0x80000000-0xfffffff are used by the system.

--*/
{
    LPCWSTR szString1;
    LPCWSTR szString2;
    DWORD cbLength1;
    DWORD cbLength2;
    DWORD dwCompare;

    //
    // compare strings first, then GUID if equality is encountered
    // this is important because we do not want to return a value indicating
    // equality based on display string only; we also want to account for the
    // GUID associated with the display name, since we can have multiple GUIDs
    // with the same display name.  If we just compared the string values,
    // the shell would not display all types/subtypes.
    //

    szString1 = GetPidlText(pidl1);
    szString2 = GetPidlText(pidl2);

    cbLength1 = lstrlenW(szString1) ;
    cbLength2 = lstrlenW(szString2) ;

    //
    // check via shortest length string
    //

    if(cbLength2 < cbLength1)
        cbLength1 = cbLength2;

    cbLength1 *= sizeof(WCHAR);

    dwCompare = memcmp(szString1, szString2, cbLength1);

    if(dwCompare == 0) {
        GUID *guid1;
        GUID *guid2;

        //
        // now compare the GUIDs.
        //

        guid1 = GetPidlGuid(pidl1);
        guid2 = GetPidlGuid(pidl2);

        dwCompare = memcmp(guid1, guid2, sizeof(GUID));
    }

    //
    // still equal? sort by PST_KEY_CURRENT_USER, then PST_KEY_LOCAL_MACHINE
    //

	if(dwCompare == 0) {
    	dwCompare = GetPidlKeyType(pidl1) - GetPidlKeyType(pidl2);
	}

    return ResultFromDWORD(dwCompare);
}


STDMETHODIMP
CShellFolder::CreateViewObject(
    HWND hwndOwner,
    REFIID riid,
    LPVOID *ppvOut
    )
/*++

    CreateViewWindow creates a view window. This can be either the right pane
    of the Explorer or the client window of a folder window.

--*/
{
    HRESULT     hr;
    CShellView  *pShellView;

    pShellView = new CShellView(this, m_pidl);
    if(pShellView == NULL)
        return E_OUTOFMEMORY;

    hr = pShellView->QueryInterface(riid, ppvOut);

    pShellView->Release();

    return hr;
}

STDMETHODIMP
CShellFolder::EnumObjects(
    HWND hwndOwner,
    DWORD dwFlags,
    LPENUMIDLIST *ppEnumIDList
    )
/*++
    Determines the contents of a folder by creating an item enumeration
    object (a set of item identifiers) that can be retrieved using the
    IEnumIDList interface.

--*/
{
    *ppEnumIDList = new CEnumIDList(m_pidl, FALSE);
    if(*ppEnumIDList == NULL)
        return E_FAIL;

    return NOERROR;
}


STDMETHODIMP
CShellFolder::GetAttributesOf(
    UINT uCount,
    LPCITEMIDLIST aPidls[],
    ULONG *pulAttribs
    )
/*++
    Retrieves the attributes of one or more file objects or subfolders.

    Builds a ULONG value that specifies the common (logically AND'ed)
    attributes of specified file objects, which is supplied to the caller
    via the pdwAttribs parameter.

--*/
{
    UINT  i;

    *pulAttribs = (ULONG)-1; // assume all in common initially

    for(i = 0; i < uCount; i++)
    {
        DWORD dwSubFolder = SFGAO_CANDELETE | SFGAO_HASPROPSHEET;

        if(HasSubFolders(aPidls[i]))
            dwSubFolder |= SFGAO_HASSUBFOLDER;

        *pulAttribs &= (SFGAO_FOLDER | dwSubFolder);
    }

    return NOERROR;
}

BOOL
CShellFolder::HasSubFolders(
    LPCITEMIDLIST pidl
    )
/*++

    This function is used to test if the specified pidl has subfolders.

    The combination of this->m_pidl and the supplied pidl can be used
    to make this determination.

--*/
{
    //
    // If we are at the subtype level, then no subfolders exist
    //

    if(GetPidlType(pidl) >= PIDL_TYPE_SUBTYPE)
        return FALSE;

    //
    // TODO: is there anyway to check if the root has subfolders?
    // m_pidl == NULL or pidl == NULL ???
    // then try to enum providers.
    //


	//
	// make a fully qualified (absolute) pidl out of m_pidl and pidl,
	// then call the enum interface to see if subfolders exist.
	//

	LPITEMIDLIST pidlNew = CopyCatPidl(m_pidl, pidl);
	if(pidlNew == NULL)
		return FALSE;

	BOOL bSubfolder = FALSE;

    LPENUMIDLIST pEnumIDList = new CEnumIDList(pidlNew, FALSE);
    if(pEnumIDList != NULL) {

	    ULONG ulFetched;
    	LPITEMIDLIST pidl = NULL;

		if( NOERROR == pEnumIDList->Next(1, &pidl, &ulFetched) && ulFetched == 1) {
			FreePidl(pidl);
			bSubfolder = TRUE;	
		}

		pEnumIDList->Release();
	}

	FreePidl(pidlNew);

	return bSubfolder;
}


STDMETHODIMP
CShellFolder::GetDisplayNameOf(
    LPCITEMIDLIST pidl,
    DWORD dwFlags,
    LPSTRRET lpName
    )
/*++
    Retrieves the display name for the specified file object or subfolder,
    returning it in a STRRET structure.

    If the ID contains the display name (in the local character set),
    it returns the offset to the name. If not, it returns a pointer to
    the display name string (UNICODE) allocated by the task allocator,
    or it fills in a buffer. The type of string returned depends on the
    type of display specified.

    Values identifying different types of display names are contained
    in the enumeration SHGNO.

--*/
{
    switch(dwFlags)
    {
        case SHGDN_NORMAL:
        case SHGDN_INFOLDER:
        case SHGDN_FORPARSING:
        {
            LPCWSTR szDisplay;
            DWORD cbDisplay;
            LPWSTR pOleStr;

            szDisplay = GetPidlText(pidl);
            cbDisplay = (lstrlenW(szDisplay) + 1) * sizeof(WCHAR);

            pOleStr = (LPWSTR)CoTaskMemAlloc(cbDisplay);
            if(pOleStr == NULL)
                return E_OUTOFMEMORY;

            CopyMemory(pOleStr, szDisplay, cbDisplay);

            lpName->uType = STRRET_WSTR;
            lpName->pOleStr = pOleStr;

            return NOERROR;
        }

        default:
            return E_INVALIDARG;
    }
}




BOOL
CShellFolder::GetPidlFullText(
    LPCITEMIDLIST pidl,
    LPTSTR lpszOut,
    DWORD dwOutSize
    )
/*++
    This function returns a string containing the full-text associated with
    the supplied pidl.  The full text will consist of a "full path" string,
    similiar to a fully qualified directory entry.

--*/
{


    return FALSE;
}



STDMETHODIMP
CShellFolder::BindToStorage(
    LPCITEMIDLIST pidl,
    LPBC pbcReserved,
    REFIID riid,
    LPVOID *ppvOut
    )
/*++
    ...Reserved for a future use. This method should return E_NOTIMPL. ...

--*/
{
    return E_NOTIMPL;
}


STDMETHODIMP
CShellFolder::GetUIObjectOf(
    HWND hwndOwner,
    UINT cidl,
    LPCITEMIDLIST *pPidl,
    REFIID riid,
    LPUINT puReserved,
    LPVOID *ppvReturn
    )
/*++
    Creates a COM object that can be used to carry out actions on the
    specified file objects or folders, typically, to create context menus
    or carry out drag-and-drop operations.

--*/
{
    *ppvReturn = NULL;

    if(IsEqualIID(riid, IID_IExtractIcon)) {
        if(cidl != 1)
            return E_INVALIDARG;
        *ppvReturn = (IExtractIcon*)( new CExtractIcon( pPidl[0] ) );
    }
    else if(IsEqualIID(riid, IID_IContextMenu)) {
        if(cidl == 0)
            return E_INVALIDARG;
    }
    else if(IsEqualIID(riid, IID_IDataObject)) {
        if(cidl == 0)
            return E_INVALIDARG;
    }

    if(*ppvReturn == NULL)
        return E_NOINTERFACE;

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




STDMETHODIMP
CShellFolder::ParseDisplayName(
    HWND hwndOwner,
    LPBC pbcReserved,
    LPOLESTR lpDisplayName,
    LPDWORD pdwEaten,
    LPITEMIDLIST *pPidlNew,
    LPDWORD pdwAttributes
    )
{
    return E_NOTIMPL;
}


STDMETHODIMP
CShellFolder::SetNameOf(
    HWND hwndOwner,
    LPCITEMIDLIST pidl,
    LPCOLESTR lpName,
    DWORD dw,
    LPITEMIDLIST *pPidlOut
    )
{
    return E_NOTIMPL;
}