#include "pch.h"
#include "stddef.h"
#pragma hdrstop


/*-----------------------------------------------------------------------------
/ Helper functions (exported)
/----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------
/ MergeMenu
/ ---------
/   Merge two menus together, taking the first popup menu and merging it into
/   the target.  We use the caption from the pop-up menu as the caption
/   for the target.
/
/ In:
/   hMenu = handle of menu to merge into
/   hMenuToInsert = handle of menu to get the popup from
/   iIndex = index to insert at
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
VOID MergeMenu(HMENU hMenu, HMENU hMenuToInsert, INT iIndex)
{
    TCHAR szBuffer[MAX_PATH];
    HMENU hPopupMenu = NULL;

    TraceEnter(TRACE_HANDLER|TRACE_VIEW, "MergeMenu");
    
    hPopupMenu = CreatePopupMenu();
    
    if ( hPopupMenu )
    {
        GetMenuString(hMenuToInsert, 0, szBuffer, ARRAYSIZE(szBuffer), MF_BYPOSITION);
        InsertMenu(hMenu, iIndex, MF_BYPOSITION|MF_POPUP, (UINT_PTR)hPopupMenu, szBuffer);

        Shell_MergeMenus(hPopupMenu, GetSubMenu(hMenuToInsert, 0), 0x0, 0x0, 0x7fff, 0);
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ BindToPath
/ ----------
/   Given a namespace path bind to it returning the shell object
/
/ In:
/   pszPath -> path to bind to
/   riid = interface to request
/   ppvObject -> receives the object pointer
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT BindToPath(LPWSTR pszPath, REFIID riid, LPVOID* ppObject)
{
    HRESULT hres;
    IShellFolder* psfDesktop = NULL;
    LPITEMIDLIST pidl = NULL;

    TraceEnter(TRACE_VIEW, "BindToPath");

    hres = CoCreateInstance(CLSID_ShellDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, (LPVOID*)&psfDesktop);
    FailGracefully(hres, "Failed to get IShellFolder for the desktop object");

    hres = psfDesktop->ParseDisplayName(NULL, NULL, pszPath, NULL, &pidl, NULL);
    FailGracefully(hres, "Failed when getting root path of DS");
    
    if ( ILIsEmpty(pidl) )
    {
        TraceMsg("PIDL is desktop, therefore just QIing for interface");
        hres = psfDesktop->QueryInterface(riid, ppObject);
    }
    else
    {
        TraceMsg("Binding to IDLIST via BindToObject");
        hres = psfDesktop->BindToObject(pidl, NULL, riid, ppObject);
    }

exit_gracefully:

    if ( FAILED(hres) )
        *ppObject = NULL;

    DoRelease(psfDesktop);           
    DoILFree(pidl);

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ GetColumnHandlerFromProperty
/ ----------------------------
/   Given a COLUMN structure allocate the property name appending the
/   CLSID of the handler if we have one.
/
/ In:
/   pColumn -> column value to decode
/   pProperty -> property value parse
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetColumnHandlerFromProperty(LPCOLUMN pColumn, LPWSTR pProperty)
{
    HRESULT hres;
    LPWSTR pPropertyTemp;
    LPWSTR pColumnHandlerCLSID;
    USES_CONVERSION;

    TraceEnter(TRACE_VIEW, "GetColumnHandlerFromProperty");
    Trace(TEXT("pProperty is: %s"), W2T(pProperty));

    if ( !pProperty )
        pProperty = pColumn->pProperty;
    
    // if we find a ',' then we must parse the GUID as it may be a CLSID for a column handler.

    pColumnHandlerCLSID = wcschr(pProperty, L',');

    if ( pColumnHandlerCLSID )
    {
        // attempt to extract the CLSID form the property name

        *pColumnHandlerCLSID++ = L'\0';           // terminate the property name

        if ( GetGUIDFromStringW(pColumnHandlerCLSID, &pColumn->clsidColumnHandler) )
        {
            TraceGUID("CLSID for handler is:", pColumn->clsidColumnHandler);
            pColumn->fHasColumnHandler = TRUE;
        }
        else
        {
            TraceMsg("**** Failed to parse CLSID from property name ****");
        }

        // we truncated the string, so lets re-alloc the buffer with the
        // new string value.

        if ( SUCCEEDED(LocalAllocStringW(&pPropertyTemp, pProperty)) )
        {
            LocalFreeStringW(&pColumn->pProperty);
            pColumn->pProperty = pPropertyTemp;
        }

        Trace(TEXT("Property name is now: %s"), W2T(pColumn->pProperty));
    }
    else
    {
        // now CLSID, so just allocate the property string if we need to.

        if ( pColumn->pProperty != pProperty )
        {
            if ( SUCCEEDED(LocalAllocStringW(&pPropertyTemp, pProperty)) )
            {
                LocalFreeStringW(&pColumn->pProperty);
                pColumn->pProperty = pPropertyTemp;
            }
        }
    }

    TraceLeaveResult(S_OK);
}


/*-----------------------------------------------------------------------------
/ GetPropertyFromColumn
/ ---------------------
/   Given a COLUMN structure allocate the property name appending the
/   CLSID of the handler if we have one.
/
/ In:
/   ppProperty -> receives a pointer to the property string
/   pColumn -> column value to decode
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetPropertyFromColumn(LPWSTR* ppProperty, LPCOLUMN pColumn)
{
    HRESULT hres;
    TCHAR szGUID[GUIDSTR_MAX+1];
    USES_CONVERSION;

    TraceEnter(TRACE_VIEW, "GetPropertyFromColumn");

    if ( !pColumn->fHasColumnHandler )
    {
        // just copy the property name

        hres = LocalAllocStringW(ppProperty, pColumn->pProperty);
        FailGracefully(hres, "Failed to allocate property");
    }
    else
    {
        // allocate a buffer and then place the property name and the
        // column handler CLSID.

        hres = LocalAllocStringLenW(ppProperty, lstrlenW(pColumn->pProperty)+GUIDSTR_MAX+1);  // nb: +2 for "," 
        FailGracefully(hres, "Failed to allocate buffer for property name");

        GetStringFromGUID(pColumn->clsidColumnHandler, szGUID, ARRAYSIZE(szGUID));

        StrCpyW(*ppProperty, pColumn->pProperty);
        StrCatW(*ppProperty, L",");
        StrCatW(*ppProperty, T2W(szGUID));
    }

    hres = S_OK;

exit_gracefully:

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ FreeColumn / FreeColumnValue
/ ----------------------------
/   A column consists of the header and filter information including the underlying
/   property value.  
/
/   A COLUMNVALUE is the typed information for the column which must be freed
/   based the iPropertyType value.
/
/ In:
/   pColumn -> LPCOLUMN structure to released
/   or
/   pColumnValue ->LPCOLUMNVALUE structure to be released
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

VOID FreeColumnValue(LPCOLUMNVALUE pColumnValue)
{
    TraceEnter(TRACE_VIEW, "FreeColumnValue");

    switch ( pColumnValue->iPropertyType )
    {
        case PROPERTY_ISUNDEFINED:
        case PROPERTY_ISBOOL:
        case PROPERTY_ISNUMBER:
            break;

        case PROPERTY_ISUNKNOWN:
        case PROPERTY_ISSTRING:
            LocalFreeString(&pColumnValue->pszText);
            break;

        default:
            Trace(TEXT("iPropertyValue is %d"), pColumnValue->iPropertyType);
            TraceAssert(FALSE);                       
            break;
    }

    pColumnValue->iPropertyType = PROPERTY_ISUNDEFINED;         // no value

    TraceLeave();
}

INT FreeColumnCB(LPVOID pItem, LPVOID pData)
{
    FreeColumn((LPCOLUMN)pItem);
    return 1;
}

VOID FreeColumn(LPCOLUMN pColumn)
{
    TraceEnter(TRACE_VIEW, "FreeQueryResult");

    if ( pColumn )
    {
        LocalFreeStringW(&pColumn->pProperty);
        LocalFreeString(&pColumn->pHeading);
        FreeColumnValue(&pColumn->filter);
        DoRelease(pColumn->pColumnHandler);
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ FreeQueryResult
/ ---------------
/   Given a QUERYRESULT structure free the elements within it
/
/ In:
/   pResult -> result blob to be released
/   cColumns = number of columns to be released
/
/ Out:
/   -
/----------------------------------------------------------------------------*/

INT FreeQueryResultCB(LPVOID pItem, LPVOID pData)
{
    FreeQueryResult((LPQUERYRESULT)pItem, PtrToUlong(pData));
    return 1;
}

VOID FreeQueryResult(LPQUERYRESULT pResult, INT cColumns)
{
    INT i;

    TraceEnter(TRACE_VIEW, "FreeQueryResult");

    if ( pResult )
    {
        LocalFreeStringW(&pResult->pObjectClass);
        LocalFreeStringW(&pResult->pPath);

        for ( i = 0 ; i < cColumns ; i++ )
            FreeColumnValue(&pResult->aColumn[i]);
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ PropertyIsFromAttribute
/ -----------------------
/   Get the property is value from the specified attribute.
/
/ In:
/   pszAttributeName -> attribute name
/   pdds -> IDsDisplaySpecifier
/
/ Out:
/   DWORD dwType
/----------------------------------------------------------------------------*/
DWORD PropertyIsFromAttribute(LPCWSTR pszAttributeName, IDsDisplaySpecifier *pdds)
{   
    DWORD dwResult = PROPERTY_ISUNKNOWN;
    USES_CONVERSION;

    TraceEnter(TRACE_CORE, "PropertyIsFromAttribute");
    Trace(TEXT("Fetching attribute type for: %s"), W2CT(pszAttributeName));

    switch ( pdds->GetAttributeADsType(pszAttributeName) )
    {
        case ADSTYPE_DN_STRING:
        case ADSTYPE_CASE_EXACT_STRING:
        case ADSTYPE_CASE_IGNORE_STRING:
        case ADSTYPE_PRINTABLE_STRING:
        case ADSTYPE_NUMERIC_STRING:
            TraceMsg("Property is a string");
            dwResult = PROPERTY_ISSTRING;
            break;

        case ADSTYPE_BOOLEAN:
            TraceMsg("Property is a BOOL");
            dwResult = PROPERTY_ISBOOL;
            break;

        case ADSTYPE_INTEGER:
            TraceMsg("Property is a number");
            dwResult = PROPERTY_ISNUMBER;
            break;

        default:
            TraceMsg("Property is UNKNOWN");
            break;
    }

    TraceLeaveValue(dwResult);
}


/*-----------------------------------------------------------------------------
/ MatchPattern
/ ------------
/   Given two strings, one a string the other a pattern match the two
/   using standard wildcarding "*" == any number of characters, "?" means
/   single character skip
/
/ In:
/   pString = string to compare
/   pPattern = pattern to compare against
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
BOOL MatchPattern(LPTSTR pString, LPTSTR pPattern)
{                                                                              
    TCHAR c, p, l;

    for ( ;; ) 
    {
        switch (p = *pPattern++ ) 
        { 
            case 0:                                 // end of pattern
                return *pString ? FALSE : TRUE;     // if end of pString TRUE

            case TEXT('*'):
            {
                while ( *pString ) 
                {                                   // match zero or more char
                    if ( MatchPattern(pString++, pPattern) )
                        return TRUE;
                }

                return MatchPattern(pString, pPattern);
            }
                                                                               
            case TEXT('?'):
            {
                if (*pString++ == 0)                // match any one char
                    return FALSE;                   // not end of pString
 
                break;
            }

            default:
            {
                if ( *pString++ != p ) 
                    return FALSE;                   // not a match

                break;
            }
        }
    }
}



/*-----------------------------------------------------------------------------
/ EnumClassAttrbutes
/ ------------------
/   This is a wrapper around the attribute enum functions exposed in
/   the IDsDisplaySpecifier interface.
/
/   We read the attributes into a DPA, then sort them add in the 
/   extra columns exposed from this UI.
/
/ In:
/   pdds -> IDsDisplaySpecifier object
/   pszObjectClass = object class to enumerate
/   pcbEnum, lParam = enumeration callback
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

typedef struct
{
    LPWSTR pszName;
    LPWSTR pszDisplayName;
    DWORD  dwFlags;
} CLASSATTRIBUTE, * LPCLASSATTRIBUTE;

INT _FreeAttribute(LPCLASSATTRIBUTE pca)
{
    LocalFreeStringW(&pca->pszName);
    LocalFreeStringW(&pca->pszDisplayName);
    LocalFree(pca);
    return 1;
}

INT _FreeAttributeCB(LPVOID pv1, LPVOID pv2)
{
    return _FreeAttribute((LPCLASSATTRIBUTE)pv1);
}

HRESULT _AddAttribute(HDPA hdpa, LPCWSTR pszName, LPCWSTR pszDisplayName, DWORD dwFlags)
{
    HRESULT hres;
    LPCLASSATTRIBUTE pca = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_CORE, "_AddAttribute");
    Trace(TEXT("Adding %s (%s)"), W2CT(pszDisplayName), W2CT(pszName));

    pca = (LPCLASSATTRIBUTE)LocalAlloc(LPTR, SIZEOF(CLASSATTRIBUTE));
    if ( !pca )
        ExitGracefully(hres, E_OUTOFMEMORY, "Failed to allocate CLASSATTRIBUTE");

    // pca->pszName = NULL;
    // pca->pszDisplayName = NULL;
    pca->dwFlags = dwFlags;

    hres = LocalAllocStringW(&pca->pszName, pszName);
    FailGracefully(hres, "Failed to copy the name");

    hres = LocalAllocStringW(&pca->pszDisplayName, pszDisplayName);
    FailGracefully(hres, "Failed to copy the name");

    if ( -1 == DPA_AppendPtr(hdpa, pca) )
        ExitGracefully(hres, E_OUTOFMEMORY, "Failed to append the record to the DPA");

    hres = S_OK;

exit_gracefully:

    if ( FAILED(hres) && pca )
        _FreeAttribute(pca);

    TraceLeaveResult(hres);
}

HRESULT _AddAttributeCB(LPARAM lParam, LPCWSTR pszName, LPCWSTR pszDisplayName, DWORD dwFlags)
{
    return _AddAttribute((HDPA)lParam, pszName, pszDisplayName, dwFlags);
}

INT _CompareAttributeCB(LPVOID pv1, LPVOID pv2, LPARAM lParam)
{
    LPCLASSATTRIBUTE pca1 = (LPCLASSATTRIBUTE)pv1;
    LPCLASSATTRIBUTE pca2 = (LPCLASSATTRIBUTE)pv2;
    return StrCmpIW(pca1->pszDisplayName, pca2->pszDisplayName);
} 

HRESULT EnumClassAttributes(IDsDisplaySpecifier *pdds, LPCWSTR pszObjectClass, LPDSENUMATTRIBUTES pcbEnum, LPARAM lParam)
{
    HRESULT hres;
    HDPA hdpaAttributes = NULL;
    WCHAR szBuffer[MAX_PATH];
    INT i;

    TraceEnter(TRACE_CORE, "EnumClassAttributes");

    hdpaAttributes = DPA_Create(16);
    if ( !hdpaAttributes )
        ExitGracefully(hres, E_OUTOFMEMORY, "Failed to allocate the DPA");

    //
    // add the stock properties for objectClass and ADsPath
    //

    LoadStringW(GLOBAL_HINSTANCE, IDS_OBJECTCLASS, szBuffer, ARRAYSIZE(szBuffer));
    hres = _AddAttribute(hdpaAttributes, c_szObjectClassCH, szBuffer, DSECAF_NOTLISTED);
    FailGracefully(hres, "Failed to add the ObjectClass default property");
    
    LoadStringW(GLOBAL_HINSTANCE, IDS_ADSPATH, szBuffer, ARRAYSIZE(szBuffer));
    hres = _AddAttribute(hdpaAttributes, c_szADsPathCH, szBuffer, DSECAF_NOTLISTED);
    FailGracefully(hres, "Failed to add the ObjectClass default property");

    //
    // now call the IDsDisplaySpecifier object to enumerate the properites correctly
    //

    TraceMsg("Calling IDsDisplaySpecifier::EnumClassAttributes");

    hres = pdds->EnumClassAttributes(pszObjectClass, _AddAttributeCB, (LPARAM)hdpaAttributes);
    FailGracefully(hres, "Failed to add the attributes");

    //
    // now sort and return them all to the caller via their callback funtion
    //

    Trace(TEXT("Sorting %d attributes, to return to the caller"), DPA_GetPtrCount(hdpaAttributes));
    DPA_Sort(hdpaAttributes, _CompareAttributeCB, NULL);

    for ( i = 0 ; i < DPA_GetPtrCount(hdpaAttributes) ; i++ )
    {
        LPCLASSATTRIBUTE pca = (LPCLASSATTRIBUTE)DPA_FastGetPtr(hdpaAttributes, i);
        TraceAssert(pca);
                                       
        hres = pcbEnum(lParam, pca->pszName, pca->pszDisplayName, pca->dwFlags);
        FailGracefully(hres, "Failed in cb to original caller");
    }

    hres = S_OK;

exit_gracefully:

    if ( hdpaAttributes )
        DPA_DestroyCallback(hdpaAttributes, _FreeAttributeCB, NULL);

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ GetFriendlyAttributeName
/ ------------------------
/   Trim the column handler information if needed, and call the
/   friendly attribute name functions.
/
/ In:
/   pdds -> IDsDisplaySpecifier object
/   pszObjectClass, pszAttributeName => attribute info to look up
/   pszBuffer, chh => return buffer
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT GetFriendlyAttributeName(IDsDisplaySpecifier *pdds, LPCWSTR pszObjectClass, LPCWSTR pszAttributeName, LPWSTR pszBuffer, UINT cch)
{
    HRESULT hres = S_OK;
    WCHAR szAttributeName[MAX_PATH];
    USES_CONVERSION;

    TraceEnter(TRACE_CORE, "GetFriendlyAttributeName");

    //
    // trim off the attribute suffix if we have one (eg: the GUID for the column handler)
    //

    if ( wcschr(pszAttributeName, L',') )
    {
        TraceMsg("Has column handler information");

        StrCpyW(szAttributeName, pszAttributeName);
        LPWSTR pszSeperator = wcschr(szAttributeName, L',');
        *pszSeperator = L'\0';

        pszAttributeName = szAttributeName;
    }

    //
    // pick off some special cases before passing onto the COM object to process
    //

    Trace(TEXT("Looking up name for: %s"), W2CT(pszAttributeName));

    if ( !StrCmpIW(pszAttributeName, c_szADsPath) )
    {
        LoadStringW(GLOBAL_HINSTANCE, IDS_ADSPATH, pszBuffer, cch);
    }
    else if ( !StrCmpIW(pszAttributeName, c_szObjectClass) )
    {
        LoadStringW(GLOBAL_HINSTANCE, IDS_OBJECTCLASS, pszBuffer, cch);
    }
    else
    {
        hres = pdds->GetFriendlyAttributeName(pszObjectClass, pszAttributeName, pszBuffer, cch);
    }                

    TraceLeaveResult(hres);
}