#include "pch.h"
#pragma hdrstop


/*-----------------------------------------------------------------------------
/ Helper functions
/----------------------------------------------------------------------------*/

HRESULT _GetQueryString(LPWSTR pQuery, UINT* pLen, LPWSTR pPrefixQuery, HWND hDlg, LPPAGECTRL aCtrl, INT iCtrls);
HRESULT _GetFilterQueryString(LPWSTR pFilter, UINT* pLen, HWND hwndFilter, HDSA hdsaColumns);


/*-----------------------------------------------------------------------------
/ Query paremeter helpers
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ ClassListAlloc
/ --------------
/   Construct a class list allocation based on the list of classes
/   we are given.
/
/ In:
/   ppClassList -> receives a class list 
/   cClassList / cClassList = array of classes to allocat from
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI ClassListAlloc(LPDSQUERYCLASSLIST* ppDsQueryClassList, LPWSTR* aClassNames, INT cClassNames)
{
    HRESULT hres;
    DWORD cbStruct, offset;
    LPDSQUERYCLASSLIST pDsQueryClassList = NULL;
    INT i;
    USES_CONVERSION;

    TraceEnter(TRACE_FORMS, "ClassListAlloc");

    if ( !ppDsQueryClassList || !aClassNames || !cClassNames )
        ExitGracefully(hres, E_FAIL, "Bad parameters (no class list etc)");

    // Walk the list of classes working out the size of the structure
    // we are going to generate, this consists of the array of 
    // classes.

    cbStruct = SIZEOF(DSQUERYCLASSLIST)+(cClassNames*SIZEOF(DWORD));
    offset = cbStruct;

    for ( i = 0 ; i < cClassNames ; i++ )
    {
        TraceAssert(aClassNames[i]);
        cbStruct += StringByteSizeW(aClassNames[i]);
    }

    // Allocate the structure using the task allocator, then fill
    // it in copying all the strings into the data blob.

    Trace(TEXT("Allocating class structure %d"), cbStruct);

    pDsQueryClassList = (LPDSQUERYCLASSLIST)CoTaskMemAlloc(cbStruct);
    TraceAssert(pDsQueryClassList);

    if ( !pDsQueryClassList )
        ExitGracefully(hres, E_OUTOFMEMORY, "Failed to allocate class list structure");

    pDsQueryClassList->cbStruct = cbStruct;
    pDsQueryClassList->cClasses = cClassNames;

    for ( i = 0 ; i < cClassNames ; i++ )
    {
        Trace(TEXT("Adding class: %s"), W2T(aClassNames[i]));
        pDsQueryClassList->offsetClass[i] = offset;
        StringByteCopyW(pDsQueryClassList, offset, aClassNames[i]);
        offset += StringByteSizeW(aClassNames[i]);
    }

    hres = S_OK;

exit_gracefully:

    TraceAssert(pDsQueryClassList);

    if (ppDsQueryClassList)
        *ppDsQueryClassList = pDsQueryClassList;

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ QueryParamsAlloc
/ ----------------
/   Construct a block we can pass to the DS query handler which contains
/   all the parameters for the query.
/
/ In:
/   ppDsQueryParams -> receives the parameter block
/   pQuery -> LDAP query string to be used
/   hInstance = hInstance to write into parameter block
/   iColumns = number of columns
/   pColumnInfo -> column info structure to use
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI QueryParamsAlloc(LPDSQUERYPARAMS* ppDsQueryParams, LPWSTR pQuery, HINSTANCE hInstance, LONG iColumns, LPCOLUMNINFO aColumnInfo)
{
    HRESULT hres;
    LPDSQUERYPARAMS pDsQueryParams = NULL;
    LONG cbStruct;
    LONG i;

    TraceEnter(TRACE_FORMS, "QueryParamsAlloc");

    if ( !pQuery || !iColumns || !ppDsQueryParams )
        ExitGracefully(hres, E_INVALIDARG, "Failed to build query parameter block");

    // Compute the size of the structure we need to be using

    cbStruct  = SIZEOF(DSQUERYPARAMS) + (SIZEOF(DSCOLUMN)*iColumns);
    cbStruct += StringByteSizeW(pQuery);

    for ( i = 0 ; i < iColumns ; i++ )
    {
        if ( aColumnInfo[i].pPropertyName ) 
            cbStruct += StringByteSizeW(aColumnInfo[i].pPropertyName);
    }

    pDsQueryParams = (LPDSQUERYPARAMS)CoTaskMemAlloc(cbStruct);

    if ( !pDsQueryParams )
        ExitGracefully(hres, E_OUTOFMEMORY, "Failed to allocate parameter block");

    // Structure allocated so lets fill it with data

    pDsQueryParams->cbStruct = cbStruct;
    pDsQueryParams->dwFlags = 0;
    pDsQueryParams->hInstance = hInstance;
    pDsQueryParams->iColumns = iColumns;
    pDsQueryParams->dwReserved = 0;

    cbStruct  = SIZEOF(DSQUERYPARAMS) + (SIZEOF(DSCOLUMN)*iColumns);

    pDsQueryParams->offsetQuery = cbStruct;
    StringByteCopyW(pDsQueryParams, cbStruct, pQuery);
    cbStruct += StringByteSizeW(pQuery);

    for ( i = 0 ; i < iColumns ; i++ )
    {
        pDsQueryParams->aColumns[i].dwFlags = 0;
        pDsQueryParams->aColumns[i].fmt = aColumnInfo[i].fmt;
        pDsQueryParams->aColumns[i].cx = aColumnInfo[i].cx;
        pDsQueryParams->aColumns[i].idsName = aColumnInfo[i].idsName;
        pDsQueryParams->aColumns[i].dwReserved = 0;

        if ( aColumnInfo[i].pPropertyName ) 
        {
            pDsQueryParams->aColumns[i].offsetProperty = cbStruct;
            StringByteCopyW(pDsQueryParams, cbStruct, aColumnInfo[i].pPropertyName);
            cbStruct += StringByteSizeW(aColumnInfo[i].pPropertyName);
        }
        else
        {
            pDsQueryParams->aColumns[i].offsetProperty = aColumnInfo[i].iPropertyIndex;
        }
    }

    hres = S_OK;              // success

exit_gracefully:

    if ( FAILED(hres) && pDsQueryParams )
    {
        CoTaskMemFree(pDsQueryParams); 
        pDsQueryParams = NULL;
    }

    if (ppDsQueryParams)
        *ppDsQueryParams = pDsQueryParams;

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ QueryParamsAddQueryString
/ -------------------------
/   Given an existing DS query block appened the given LDAP query string into
/   it. We assume that the query block has been allocated by IMalloc (or CoTaskMemAlloc).
/
/ In:
/   ppDsQueryParams -> receives the parameter block
/   pQuery -> LDAP query string to be appended
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI QueryParamsAddQueryString(LPDSQUERYPARAMS* ppDsQueryParams, LPWSTR pQuery)
{
    HRESULT hres;
    LPWSTR pOriginalQuery = NULL;
    LPDSQUERYPARAMS pDsQuery = *ppDsQueryParams;
    INT cbQuery, i;

    TraceEnter(TRACE_FORMS, "QueryParamsAddQueryString");

    if ( pQuery )
    {
        if ( !pDsQuery )
            ExitGracefully(hres, E_INVALIDARG, "No query to append to");

        // Work out the size of the bits we are adding, take a copy of the
        // query string and finally re-alloc the query block (which may cause it
        // to move).
       
        cbQuery = StringByteSizeW(pQuery) + StringByteSizeW(L"(&)");
        Trace(TEXT("DSQUERYPARAMS being resized by %d bytes"), cbQuery);

        hres = LocalAllocStringW(&pOriginalQuery, (LPWSTR)ByteOffset(pDsQuery, pDsQuery->offsetQuery));
        FailGracefully(hres, "Failed to take copy of original query string");

        pDsQuery = (LPDSQUERYPARAMS)CoTaskMemRealloc(pDsQuery, pDsQuery->cbStruct+cbQuery);
        if ( !pDsQuery )
            ExitGracefully(hres, E_OUTOFMEMORY, "Failed to re-alloc control block");
        
        *ppDsQueryParams = pDsQuery;

        // Now move everything above the query string up, and fix all the
        // offsets that reference those items (probably the property table),
        // finally adjust the size to reflect the change

        MoveMemory(ByteOffset(pDsQuery, pDsQuery->offsetQuery+cbQuery), 
                   ByteOffset(pDsQuery, pDsQuery->offsetQuery), 
                   (pDsQuery->cbStruct - pDsQuery->offsetQuery));
                
        for ( i = 0 ; i < pDsQuery->iColumns ; i++ )
        {
            if ( pDsQuery->aColumns[i].offsetProperty > pDsQuery->offsetQuery )
            {
                Trace(TEXT("Fixing offset of property at index %d"), i);
                pDsQuery->aColumns[i].offsetProperty += cbQuery;
            }
        }

        StrCpyW((LPWSTR)ByteOffset(pDsQuery, pDsQuery->offsetQuery), L"(&");
        StrCatW((LPWSTR)ByteOffset(pDsQuery, pDsQuery->offsetQuery), pOriginalQuery);
        StrCatW((LPWSTR)ByteOffset(pDsQuery, pDsQuery->offsetQuery), pQuery);        
        StrCatW((LPWSTR)ByteOffset(pDsQuery, pDsQuery->offsetQuery), L")");

        pDsQuery->cbStruct += cbQuery;
    }

    hres = S_OK;

exit_gracefully:

    LocalFreeStringW(&pOriginalQuery);

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ Form to query string helper functions
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ GetQueryString
/ --------------
/   Build the form parmaeters into a LDAP query string using the given table.
/
/ In:
/   ppQuery -> receives the string pointer
/   pPrefixQuery -> string placed at head of query / = NULL if none
/   hDlg = handle for the dialog to get the data from
/   aCtrls / iCtrls = control information for the window
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI GetQueryString(LPWSTR* ppQuery, LPWSTR pPrefixQuery, HWND hDlg, LPPAGECTRL aCtrls, INT iCtrls)
{
    HRESULT hres;
    UINT cLen = 0;

    TraceEnter(TRACE_FORMS, "GetQueryString");

    hres = _GetQueryString(NULL, &cLen, pPrefixQuery, hDlg, aCtrls, iCtrls);
    FailGracefully(hres, "Failed 1st pass (compute string length)");

    if ( cLen )
    {
        hres = LocalAllocStringLenW(ppQuery, cLen);
        FailGracefully(hres, "Failed to alloc buffer for query string");

        hres = _GetQueryString(*ppQuery, &cLen, pPrefixQuery, hDlg, aCtrls, iCtrls);
        FailGracefully(hres, "Failed 2nd pass (fill buffer)");
    }

    hres = cLen ? S_OK:S_FALSE;

exit_gracefully:

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ _GetQueryString
/ ---------------
/   Build the string from the controls or just return the buffer size required.
/
/ In:
/   pQuery -> filled with query string / = NULL
/   pLen = updated to reflect the required string length
/   pPrefixQuery -> string placed at head of query / = NULL if none
/   hDlg = handle for the dialog to get the data from
/   aCtrls / iCtrls = control information for the window
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT _GetQueryString(LPWSTR pQuery, UINT* pLen, LPWSTR pPrefixQuery, HWND hDlg, LPPAGECTRL aCtrl, INT iCtrls)
{
    HRESULT hres;
    INT i;
    TCHAR szBuffer[MAX_PATH];
    USES_CONVERSION;

    TraceEnter(TRACE_FORMS, "_GetQueryString");

    if ( !hDlg || (!aCtrl && iCtrls) )
        ExitGracefully(hres, E_INVALIDARG, "No dialog or controls list");

    Trace(TEXT("Checking %d controls"), iCtrls);

    PutStringElementW(pQuery, pLen, pPrefixQuery);

    for ( i = 0 ; i < iCtrls; i++ )
    {
        if ( GetDlgItemText(hDlg, aCtrl[i].nIDDlgItem, szBuffer, ARRAYSIZE(szBuffer)) )
        {
            Trace(TEXT("Property %s, value %s"), W2T(aCtrl[i].pPropertyName), szBuffer);
            GetFilterString(pQuery, pLen, aCtrl[i].iFilter, aCtrl[i].pPropertyName, T2W(szBuffer));
        }
    }

    Trace(TEXT("Resulting query is -%s- (%d)"), pQuery ? W2T(pQuery):TEXT("<no buffer>"), pLen ? *pLen:0);

    hres = S_OK;

exit_gracefully:

    TraceLeaveResult(hres);    
}


/*-----------------------------------------------------------------------------
/ GetFilterString
/ ---------------
/   Given a property, a property and its filter generate a suitable filter
/   string that map returning it into the given buffer via PutStringElement.
/
/ In:
/   pFilter, pLen = buffer information that we are returning int
/   iFilter = condition to be applied
/   pProperty -> property name
/   pValue -> value
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

struct
{
    BOOL fNoValue;
    BOOL fFixWildcard;
    LPWSTR pPrefix;
    LPWSTR pOperator;
    LPWSTR pPostfix;
}
filter_info[] =
{

//
// The server today does not support contains searches, therefore
// for consistency map that to a STARTSWITH, NOTSTARTSWITH
//

#if 0
    0, 1, L"(",  L"=*",   L"*)",     // CONTAIN
    0, 1, L"(!", L"=*",   L"*)",     // NOTCONTAINS
#else 
    0, 1, L"(",  L"=",    L"*)",     // CONTAINS
    0, 1, L"(!", L"=",    L"*)",     // NOTCONTAINS
#endif

    0, 1, L"(",  L"=",    L"*)",     // STARTSWITH
    0, 1, L"(",  L"=*",   L")",      // ENDSWITH
    0, 0, L"(",  L"=",    L")",      // IS
    0, 0, L"(!", L"=",    L")",      // ISNOT
    0, 0, L"(",  L">=",   L")",      // GREATEREQUAL
    0, 0, L"(",  L"<=",   L")",      // LESSEQUAL
    1, 0, L"(",  L"=*)",  NULL,      // DEFINED
    1, 0, L"(!", L"=*)",  NULL,      // UNDEFINED

    1, 0, L"(",  L"=TRUE)",  NULL,   // TRUE
    1, 0, L"(!", L"=TRUE)",  NULL,   // FALSE
};

STDAPI GetFilterString(LPWSTR pFilter, UINT* pLen, INT iFilter, LPWSTR pProperty, LPWSTR pValue)
{
    HRESULT hres;
    USES_CONVERSION;

    TraceEnter(TRACE_VIEW, "GetFilterString");

    // Check to see if the value we have contains a wildcard, if it does then just 
    // make it is exact assuming the user knows what they are doing - ho ho ho!

    if ( pValue && filter_info[iFilter-FILTER_FIRST].fFixWildcard )
    {
        if ( wcschr(pValue, L'*') )
        {
            TraceMsg("START/ENDS contains wildcards, making is exactly"); 
            iFilter = FILTER_IS;
        }
    }

    // Fix the condition to index into the our array then 
    // put the string elements down

    iFilter -= FILTER_FIRST;                     // compensate for non-zero index

    if ( iFilter >= ARRAYSIZE(filter_info) )
        ExitGracefully(hres, E_FAIL, "Bad filter value");

    PutStringElementW(pFilter, pLen, filter_info[iFilter].pPrefix);
    PutStringElementW(pFilter, pLen, pProperty);
    PutStringElementW(pFilter, pLen, filter_info[iFilter].pOperator);

    if ( !filter_info[iFilter].fNoValue )
    {
        LPWSTR pszOutput = pFilter ? (pFilter + lstrlenW(pFilter)) : NULL;
        for (; *pValue; pValue++)
        {
            int cchLen = 1;
            switch (*pValue)
            {
//                case L'*':   // do no RFC encode *, if we do then the user cannot do foo* for a wildcarded string
                case L'(':
                case L')':
                case L'\\':
                {
                    if (pszOutput)
                    {
                        LPCWSTR pszToHex = L"0123456789abcdef";
                        *pszOutput++ = L'\\';
                        *pszOutput++ = pszToHex[(*pValue & 0xf0) >> 4];
                        *pszOutput++ = pszToHex[(*pValue & 0x0f)];
                    }           
                    cchLen = 3;
                    break;
                }
            
                default:
                    if (pszOutput)
                    {
                        *pszOutput++ = *pValue;
                    }
                    break;
            }

            if (pLen)
                *pLen = (*pLen + cchLen);
        }
        
        if (pszOutput)
            *pszOutput = L'\0';
    }

    PutStringElementW(pFilter, pLen, filter_info[iFilter].pPostfix);

    Trace(TEXT("Filter is: %s"), pFilter ? W2T(pFilter):TEXT("<none>"));

    hres = S_OK;

exit_gracefully:

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ GetPatternString
/ ----------------
/   Given a string wrap in suitable wildcards to do the filtering of
/   results.
/
/ In:
/   pPattern, pLen = buffer information that we are returning int
/   iFilter = condition to be applied
/   pValue -> value
/
/ Out:
/   VOID
/----------------------------------------------------------------------------*/

struct
{
    LPTSTR pPrefix;
    LPTSTR pPostfix;
}
pattern_info[] =
{
    TEXT("*"), TEXT("*"),     // CONTAIN
    TEXT("*"), TEXT("*"),     // NOTCONTAINS
    TEXT(""),  TEXT("*"),     // STARTSWITH
    TEXT("*"), TEXT(""),      // ENDSWITH
    TEXT(""),  TEXT(""),      // IS
    TEXT(""),  TEXT(""),      // ISNOT
};

STDAPI GetPatternString(LPTSTR pFilter, UINT* pLen, INT iFilter, LPTSTR pValue)
{
    HRESULT hres;

    TraceEnter(TRACE_VIEW, "GetFilterString");

    iFilter -= FILTER_FIRST;                     // compensate for non-zero index

    if ( iFilter >= ARRAYSIZE(pattern_info) )
        ExitGracefully(hres, E_FAIL, "Bad filter value");

    PutStringElement(pFilter, pLen, pattern_info[iFilter].pPrefix);
    PutStringElement(pFilter, pLen, pValue);
    PutStringElement(pFilter, pLen, pattern_info[iFilter].pPostfix);

    hres = S_OK;

exit_gracefully:

    TraceLeaveResult(hres);
}


/*-----------------------------------------------------------------------------
/ Dialog helper functions
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ EnablePageControls
/ ------------------
/   Enable/Disable the controls on a query form.
/
/ In:
/   hDlg = handle for the dialog to get the data from
/   aCtrls / iCtrls = control information for the window
/   fEnable = TRUE/FALSE to enable disable window controls
/
/ Out:
/   VOID
/----------------------------------------------------------------------------*/
STDAPI_(VOID) EnablePageControls(HWND hDlg, LPPAGECTRL aCtrl, INT iCtrls, BOOL fEnable)
{
    HRESULT hres;
    INT i;
    HWND hwndCtrl;

    TraceEnter(TRACE_FORMS, "EnablePageControls");

    if ( !hDlg || (!aCtrl && iCtrls) )
        ExitGracefully(hres, E_INVALIDARG, "No dialog or controls list");

    Trace(TEXT("%s %d controls"), fEnable ? TEXT("Enabling"):TEXT("Disabling"),iCtrls);

    for ( i = 0 ; i < iCtrls; i++ )
    {
        hwndCtrl = GetDlgItem(hDlg, aCtrl[i].nIDDlgItem);

        if  ( hwndCtrl )
            EnableWindow(hwndCtrl, fEnable);
    }

exit_gracefully:

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ ResetPageControls
/ ------------------
/   Reset all the form controls back to their default state.
/
/ In:
/   hDlg = handle for the dialog to get the data from
/   aCtrls / iCtrls = control information for the window
/
/ Out:
/   VOID
/----------------------------------------------------------------------------*/
STDAPI_(VOID) ResetPageControls(HWND hDlg, LPPAGECTRL aCtrl, INT iCtrls)
{
    HRESULT hres;
    INT i;

    TraceEnter(TRACE_FORMS, "ResetPageControls");

    if ( !hDlg || (!aCtrl && iCtrls) )
        ExitGracefully(hres, E_INVALIDARG, "No dialog or controls list");

    for ( i = 0 ; i < iCtrls; i++ )
        SetDlgItemText(hDlg, aCtrl[i].nIDDlgItem, TEXT(""));

exit_gracefully:

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ SetDlgItemFromProperty
/ ----------------------
/   Given an IPropertyBag interface set the control with the text for 
/   that property.  We assume the property is a string.
/
/ In:
/   ppb -> IPropertyBag
/   pszProperty -> property to read
/   hwnd, id = control information
/   pszDefault = default text / = NULL if not important
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI SetDlgItemFromProperty(IPropertyBag* ppb, LPCWSTR pszProperty, HWND hwnd, INT id, LPCWSTR pszDefault)
{
    HRESULT hres;
    VARIANT variant;
    USES_CONVERSION;

    TraceEnter(TRACE_FORMS, "SetDlgItemFromProperty");

    VariantInit(&variant);

    if ( ppb && SUCCEEDED(ppb->Read(pszProperty, &variant, NULL)) )
    {
        if ( V_VT(&variant) == VT_BSTR )
        {
            pszDefault = V_BSTR(&variant);
            Trace(TEXT("property contained: %s"), W2CT(pszDefault));                
        }
    }

    if ( pszDefault )
        SetDlgItemText(hwnd, id, W2CT(pszDefault));

    VariantClear(&variant);

    TraceLeaveResult(S_OK);
}


/*-----------------------------------------------------------------------------
/ Query Persistance
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ PersistQuery
/ ------------
/   Persist a query into a IPersistQuery object
/
/ In:
/   pPersistQuery = query to persist into
/   fRead = read?
/   pSectionName = section name to use when persisting
/   hDlg = DLG to persist from
/   aCtrls / iCtrls = ctrls to be persisted
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
STDAPI PersistQuery(IPersistQuery* pPersistQuery, BOOL fRead, LPCTSTR pSection, HWND hDlg, LPPAGECTRL aCtrl, INT iCtrls)
{
    HRESULT hres = S_OK;
    TCHAR szBuffer[MAX_PATH];
    INT i;
    USES_CONVERSION;

    TraceEnter(TRACE_IO, "PersistQuery");

    if ( !pPersistQuery || !hDlg || (!aCtrl && iCtrls) )
        ExitGracefully(hres, E_INVALIDARG, "No data to persist");

    for ( i = 0 ; i < iCtrls ; i++ )
    {
        if ( fRead )
        {
            if ( SUCCEEDED(pPersistQuery->ReadString(pSection, W2T(aCtrl[i].pPropertyName), szBuffer, ARRAYSIZE(szBuffer))) )
            {
                Trace(TEXT("Reading property: %s,%s as %s"), pSection, W2T(aCtrl[i].pPropertyName), szBuffer);
                SetDlgItemText(hDlg, aCtrl[i].nIDDlgItem, szBuffer);
            }
        }
        else
        {
            if ( GetDlgItemText(hDlg, aCtrl[i].nIDDlgItem, szBuffer, ARRAYSIZE(szBuffer)) )
            {
                Trace(TEXT("Writing property: %s,%s as %s"), pSection, W2T(aCtrl[i].pPropertyName), szBuffer);
                hres = pPersistQuery->WriteString(pSection, W2T(aCtrl[i].pPropertyName), szBuffer);
                FailGracefully(hres, "Failed to write control data");
            }
        }
    }

    hres = S_OK;

exit_gracefully:

    TraceLeaveResult(hres);
}
