//***   uemapp.cpp -- application side of event monitor
// DESCRIPTION
//  event generators, actions, helpers, etc.

#include "priv.h"
#include <trayp.h>
#include "sccls.h"
#include "uemapp.h"
#include "uacount.h"
#include "regdb.h"
#include "uareg.h"
#include "resource.h"

#define MAX(a, b)   (((a) > (b)) ? (a) : (b))

#define BIT_ASSIGN(dwBits, dwMasks, dwVals) \
    (((dwBits) & ~(dwMasks)) | (dwVals))

#define FEATURE_EMDLL   0       // turn this *off* until msoem.dll in setup!

#if FEATURE_EMDLL
#include "mso.h"
#include "msoem.h"
#include "emrule.h"
#include "libem.h"   // configure genem.c and other client-side stuff

#include "emdef.h"              // rulc-generated
#endif

#if FEATURE_EMDLL
/* UEMEvalIE... */
extern "C" int FInitEm(void);
#else
#define UEMEvalIE(irul, val, ecmd)  /*NOTHING*/
#define FInitEm()                   /*NOTHING*/
#endif

#define DM_UEMTRACE     0
#define DM_UEMTRACE2    0           // verbose
#define DM_IDLEDETECT   0           // TF_CUSTOM2
#define DM_EVTMON       TF_UEM

int SHSearchInt(int *psrc, int cnt, int val);
int UEMIIDToInd(const GUID *pguidGrp);

void UEMEnableTimer(UINT uTimeout);

//***   event firers {

//CASSERT(UEMIND_SHELL == 0 && UEMIND_BROWSER == 1);
HRESULT GetUEMLogger(int iCmd, CEMDBLog **p);

CEMDBLog *g_uempDbLog[UEMIND_NSTANDARD + UEMIND_NINSTR];

DWORD g_uemdwFlags /*=0*/;      // UAF_* and UAAF_*

// Turning this so that it's a Flat 12hours. You have to explicitly set the SessionTime=0 
// in the registry to debug.
#ifdef DEBUG_UEM_TIMEOUTS
#define UAS_SESSTIME    UAT_MINUTE1
#else
#define UAS_SESSTIME    UAT_HOUR12
#endif

#define UAS_SESSMIN     0
#define UAS_SESSMAX     ... none for now ...

DWORD g_dSessTime = UAS_SESSTIME;           // session time threshhold

#define UAS_IDLETIME    UAT_HOUR12
#define UAS_IDLEMIN     0
#define UAS_IDLEMAX     ... none for now ...

DWORD g_dIdleTime = UAS_IDLETIME;           // idle time threshhold

#define UAS_CLEANSESS   16
DWORD g_dCleanSess = UAS_CLEANSESS;         // cleanup session count threshhold

#if FEATURE_EMDLL // {

#if YY_DELAYED
extern "C" MSOACTTBL  *_pacttbl;
extern "C" void DoPendingActions(void);
#endif

//***   UEMEvalIE -- sched and eval event (and generic)
//
void UEMEvalIE(IRUL irul, long val, int eCmd)
{

#ifdef DEBUG
    static long iDepth = 0;
#endif

    ASSERT(iDepth == 0);
    ASSERT(InterlockedIncrement(&iDepth) > 0);

    TraceMsg(DM_EVTMON, "uemeie: sched/eval irul=%d val=0x%x eCmd=%d", irul, val, eCmd);
    MsoScheduleIrul(irul, val);  // e.g. irulIE_UIMENU
    MsoScheduleIrul(irulIE_GENERIC, eCmd);  // e.g. UEME_UIMENU
    MsoEvaluateEvents(rulevtEmIE);

#if YY_DELAYED
    if (_pacttbl->pactPending)
        DoPendingActions();
#endif

    ASSERT(InterlockedDecrement(&iDepth) == 0);

}

#endif // }

void UEMSpecial(int iTab, int iGrp, int eCmd, WPARAM wParam, LPARAM lParam)
{
    CEMDBLog *pDbLog;

    pDbLog = g_uempDbLog[iGrp];

    if (!pDbLog) 
    {
        ASSERT(0);
        TraceMsg(TF_ERROR, "uemt: pDbLog not initialized iTab=%d iGrp=%d eCmd=%d wParam=0x%x lParam=0x%x", iTab, iGrp, eCmd, wParam, lParam);
        return;
    }

    switch (eCmd) {
    case UEME_DBTRACEA:
        TraceMsg(DM_UEMTRACE, "uemt: e=runtrace s=%hs(0x%x)", (int)lParam, (int)lParam);
        break;
    case UEME_DBTRACEW:
        TraceMsg(DM_UEMTRACE, "uemt: e=runtrace s=%ls(0x%x)", (int)lParam, (int)lParam);
        break;
#ifdef DEBUG
    case UEME_DBSLEEP:
        Sleep((DWORD)lParam);
        break;
#endif

    // UEME_DONE*
    case UEME_DONECANCEL:
        TraceMsg(DM_UEMTRACE, "uemt: e=donecancel lP=%x", (int)lParam);
        break;

    // UEME_ERROR*
    case UEME_ERRORA:
        TraceMsg(DM_UEMTRACE, "uemt: e=errora id=%hs(0x%x)", (LPSTR)lParam, (int)lParam);
        break;
    case UEME_ERRORW:
        TraceMsg(DM_UEMTRACE, "uemt: e=errorw id=%ls(0x%x)", (LPWSTR)lParam, (int)lParam);
        break;

    case UEME_CTLSESSION:
        ASSERT(lParam == -1);   // eventually, UAQ_*
        pDbLog->SetSession(UAQ_SESSION, (BOOL)wParam);
#ifdef UAAF_INSTR
        // might be safer to copy UA.sess rather than inc UA2.sess in parallel?
        if (g_uemdwFlags & UAAF_INSTR) 
        {
            if (EVAL(g_uempDbLog[iGrp + UEMIND_NINSTR]))
                g_uempDbLog[iGrp + UEMIND_NINSTR]->SetSession(UAQ_SESSION, (BOOL)wParam);
        }
#endif
        break;

    default:
        TraceMsg(DM_UEMTRACE, "uemt: e=0x%x(%d) lP=0x%x(%d)", eCmd, eCmd, (int)lParam, (int)lParam);
        break;
    }
    return;
}

#ifdef DEBUG // {
int DBShellMenuValTab[] = 
{
    0x8,    // UEMC_FILERUN
    401,    // IDM_FILERUN
};

TCHAR * DBShellMenuStrTab[] = 
{
    TEXT("run"),
    TEXT("run"),
};

int DBBrowserMenuValTab[] = {
    0x106,
};
TCHAR * DBBrowserMenuStrTab[] = {
    TEXT("properties"),
};

int DBBrowserTbarValTab[] = {
    0x124, 0x122,
};
TCHAR * DBBrowserTbarStrTab[] = {
    TEXT("stop"),
    TEXT("home"),
};

// Function used only in this file, and only in debug,
// so no point in adding to shlwapi
LPTSTR SHSearchMapIntStr(const int *src, const LPTSTR *dst, int cnt, int val)
{
    for (; cnt > 0; cnt--, src++, dst++) {
        if (*src == val)
            return *dst;
    }
    return (LPTSTR)-1;
}
#endif // }


#define TABDAT(ueme, dope, u1, u2, u3, u4)  ueme,
int UemeValTab[] = {
    #include "uemedat.h"
};
#undef  TABDAT

#define TABDAT(ueme, dope, u1, u2, u3, u4)  TEXT(# ueme),
TCHAR *UemeStrTab[] = {
    #include "uemedat.h"
};
#undef  TABDAT

#define TABDAT(ueme, dope, u1, u2, u3, u4)  dope,
char *UemeDopeTab[] = {
    #include "uemedat.h"
};
#undef  TABDAT

BOOL UEMEncodePidl(IShellFolder *psf, LPITEMIDLIST pidlItem,
    LPTSTR pszBuf, DWORD cchBuf, int* piIndexStart, int* pcsidl);

#define MAX_EVENT_NAME      32

//***
// NOTES
//  todo: could put more encoding instrs in dope vector (e.g. %pidl, %tstr)
//  for now there are only a couple so we hard-code them
void UEMEncode(int iTab, TCHAR *pszEvent, TCHAR *pszEncoded, DWORD cchEncoded, int iGrp, int eCmd, WPARAM wParam, LPARAM lParam)
{
#ifdef DEBUG
    TCHAR *pdb2;
#endif
    int i, csIdl;
    TCHAR szBufTmp[MAX_URL_STRING];
    TCHAR szEvent[MAX_PATH];


    ASSERT(pszEvent[0] == 0);
    ASSERT(pszEncoded == 0 || pszEncoded[0] == 0);

    if (iTab == -1) {
        lstrcpy(pszEvent, TEXT("UEM?_?"));
        //pszEncoded[0] = 0;
    }
    else {
        lstrcpy(pszEvent, UemeStrTab[iTab]);
        ASSERT(lstrlen(pszEvent) < MAX_EVENT_NAME);
        
        if (pszEncoded) {
            switch (eCmd) {
            case UEME_RUNPIDL:
                if (UEMEncodePidl((IShellFolder *)wParam, (LPITEMIDLIST)lParam, szBufTmp, SIZECHARS(szBufTmp), &i, &csIdl)) {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%%csidl%d%%%s"), pszEvent, csIdl, szBufTmp + i);
                }
                else {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%s"), pszEvent, szBufTmp);
                }
                break;

            case UEME_RUNPATHA:
                ASSERT(lstrcmp(pszEvent, TEXT("UEME_RUNPATHA")) == 0);
                ASSERT(pszEvent[12] == TEXT('A'));
                pszEvent[12] = 0;    // nuke the 'A'/'W'

                SHAnsiToTChar((LPSTR)lParam, szEvent, MAX_PATH);

                if (wParam != -1) {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%%csidl%d%%%s"), pszEvent, wParam, szEvent);
                }
                else {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%s"), pszEvent, szEvent);
                }
                break;
            case UEME_RUNPATHW:
                ASSERT(lstrcmp(pszEvent, TEXT("UEME_RUNPATHW")) == 0);
                ASSERT(pszEvent[12] == TEXT('W'));
                pszEvent[12] = 0;    // nuke the 'A'/'W'

                if (wParam != -1) {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%%csidl%d%%%ls"), pszEvent, wParam, (WCHAR*)lParam);
                }
                else {
                    wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%ls"), pszEvent, (WCHAR *)lParam);
                }
                break;

            case UEME_RUNCPLA:
                ASSERT(lstrcmp(pszEvent, TEXT("UEME_RUNCPLA")) == 0);
                ASSERT(pszEvent[11] == TEXT('A'));
                pszEvent[11] = 0;    // nuke the 'A'/'W'

                SHAnsiToTChar((LPSTR)lParam, szEvent, MAX_PATH);

                wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%s"), pszEvent, szEvent);
                break;

            case UEME_RUNCPLW:
                ASSERT(lstrcmp(pszEvent, TEXT("UEME_RUNCPLW")) == 0);
                ASSERT(pszEvent[11] == TEXT('W'));
                pszEvent[11] = 0;    // nuke the 'A'/'W'

                wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%ls"), pszEvent, (WCHAR *)lParam);
                break;

            default:
                wnsprintf(pszEncoded, cchEncoded, TEXT("%s:0x%x,%x"), pszEvent, (DWORD)wParam, (DWORD)lParam);
                break;
            }
        }
    }

#ifdef DEBUG
    pdb2 = (TCHAR *)-1;

    switch (eCmd) {
    case UEME_UIMENU:
        switch (iGrp) {
        case UEMIND_SHELL:
            pdb2 = SHSearchMapIntStr(DBShellMenuValTab, DBShellMenuStrTab, ARRAYSIZE(DBShellMenuValTab), (int)lParam);
            break;
        case UEMIND_BROWSER:
            pdb2 = SHSearchMapIntStr(DBBrowserMenuValTab, DBBrowserMenuStrTab, ARRAYSIZE(DBBrowserMenuValTab), (int)lParam);
            break;
        default:
            break;
        }
        break;

    case UEME_UITOOLBAR:
        ASSERT(iGrp == UEMIND_BROWSER);
        pdb2 = SHSearchMapIntStr(DBBrowserTbarValTab, DBBrowserTbarStrTab, ARRAYSIZE(DBBrowserTbarValTab), (int)lParam);
        break;

    default:
        break;
    }

    if (pdb2 != (TCHAR *)-1) {
        if (pszEncoded)
            wnsprintf(pszEncoded, cchEncoded, TEXT("%s:%s"), pszEvent, pdb2);
    }
#endif

    return;
}

STDAPI _UEMGetDisplayName(IShellFolder *psf, LPCITEMIDLIST pidl, UINT shgdnf, LPTSTR pszOut, DWORD cchOut)
{
    HRESULT hr;
    
    if (psf)
    {
        ASSERT(pidl == ILFindLastID(pidl));
        STRRET str;
        
        hr = psf->GetDisplayNameOf(pidl, shgdnf, &str);
        if (SUCCEEDED(hr))
            hr = StrRetToBuf(&str, pidl, pszOut, cchOut);
    }
    else
        hr = SHGetNameAndFlags(pidl, shgdnf, pszOut, cchOut, NULL);

    return hr;
}

//***   FoldCSIDL -- folder special CSIDLs to keep start menu happy
//
#define FoldCSIDL(csidl) \
    ((csidl) == CSIDL_COMMON_PROGRAMS ? CSIDL_PROGRAMS : (csidl))

//***   UemEncodePidl -- encode pidl into csidl and relative path
//
BOOL UEMEncodePidl(IShellFolder *psf, LPITEMIDLIST pidlItem,
    LPTSTR pszBuf, DWORD cchBuf, int* piIndexStart, int* pcsidl)
{
    static UINT csidlTab[] = { CSIDL_PROGRAMS, CSIDL_COMMON_PROGRAMS, CSIDL_FAVORITES, -1 };
    UINT *pcsidlCur;
    int i;
    TCHAR szFolderPath[MAX_PATH];

    _UEMGetDisplayName(psf, pidlItem, SHGDN_FORPARSING, pszBuf, cchBuf);

    for (pcsidlCur = csidlTab; *pcsidlCur != (UINT)-1; pcsidlCur++) 
    {
        // perf: assume shell32 caches this (it does)
        if (SHGetSpecialFolderPath(NULL, szFolderPath, *pcsidlCur, FALSE))
        {
            i = PathCommonPrefix(szFolderPath, pszBuf, NULL);

            if (i != 0 && i == lstrlen(szFolderPath))  
            {
                *pcsidl = FoldCSIDL(*pcsidlCur);
                *piIndexStart = i;
                return TRUE;
            }
        }
    }

    return FALSE;
}

//***   UEMEvalMsg -- fire event
// ENTRY/EXIT
//  pguidGrp    'owner' of event.  e.g. shell, browser, joe-app, etc.
//  eCmd        command.  one of UEME_* (standard) or UEME_USER+xxx (custom).
//  wP, lP      args.
// NOTES
//  - pri=1 gotta filter events for privacy issues (esp. Ger).  not sure if
//  we should add a param saying 'usage' of event or just infer it from the
//  event.
//  - pri=? gotta encrypt the data we log
//  - pri=? change to UemEvalMsg(eCmd, wParam, lParam)
//
void UEMEvalMsg(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;

    hr = UEMFireEvent(pguidGrp, eCmd, UEMF_XEVENT, wParam, lParam);
    return;
}

STDAPI_(BOOL) UEMGetInfo(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, LPUEMINFO pui)
{
    HRESULT hr;

    hr = UEMQueryEvent(pguidGrp, eCmd, wParam, lParam, pui);
    return SUCCEEDED(hr);
}

class CUserAssist : public IUserAssist
{
public:
    //*** IUnknown
    virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppv);
    virtual STDMETHODIMP_(ULONG) AddRef(void);
    virtual STDMETHODIMP_(ULONG) Release(void);

    //*** IUserAssist
    virtual STDMETHODIMP FireEvent(const GUID *pguidGrp, int eCmd, DWORD dwFlags, WPARAM wParam, LPARAM lParam);
    virtual STDMETHODIMP QueryEvent(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, LPUEMINFO pui);
    virtual STDMETHODIMP SetEvent(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, LPUEMINFO pui);

protected:
    CUserAssist();
    HRESULT Initialize();
    virtual ~CUserAssist();
    friend HRESULT CUserAssist_CI2(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi);
    friend void CUserAssist_CleanUp(DWORD dwReason, void *lpvReserved);

    friend HRESULT UEMRegisterNotify(UEMCallback pfnUEMCB, void *param);

    HRESULT _InitLock();
    HRESULT _Lock();
    HRESULT _Unlock();

    void FireNotify(const GUID *pguidGrp, int eCmd)
    {
        // Assume that we have the lock
        if (_pfnNotifyCB)
            _pfnNotifyCB(_param, pguidGrp, eCmd);
    }

    HRESULT RegisterNotify(UEMCallback pfnUEMCB, void *param)
    {
        HRESULT hr;
        int cTries = 0;
        do
        {
            cTries++;
            hr = _Lock();
            if (SUCCEEDED(hr))
            {
                _pfnNotifyCB = pfnUEMCB;
                _param = param;
                _Unlock();
            }
            else
            {
                ::Sleep(100); // wait some for the lock to get freed up
            }
        }
        while (FAILED(hr) && cTries < 20);
        return hr;
    }

private:
    LONG    _cRef;

    HANDLE  _hLock;

    UEMCallback _pfnNotifyCB;
    void        *_param;

};

#define SZ_UALOCK   TEXT("_SHuassist.mtx")


void DoLog(CEMDBLog *pDbLog, TCHAR *pszBuf1, TCHAR *pszBuf2)
{
    if (pDbLog && *pszBuf1) {
        pDbLog->IncCount(pszBuf1);
        if (*pszBuf2) {
            //ASSERT(iGrp == UEMIND_BROWSER);   // not req'd but currently true
            pDbLog->IncCount(pszBuf2);
        }
    }

    return;
}

HRESULT CUserAssist::FireEvent(const GUID *pguidGrp, int eCmd, DWORD dwFlags, WPARAM wParam, LPARAM lParam)
{
    TCHAR szBuf1[32];               // "UEME_xxx"
    TCHAR szBuf2[MAX_URL_STRING];   // "UEME_xxx:0x%x,%x"
    int iGrp;
    CEMDBLog *pDbLog;
    int i, iTab;
    char ch;
    char *pszDope;

    ASSERT(this != 0);

    // If called for instrumentation (NOT event monitor) and instrumentation not enabled
    // we should exit!
    if ((UEMF_INSTRUMENT == (dwFlags & UEMF_MASK)) && (!(g_uemdwFlags & UAAF_INSTR)))
        return E_FAIL;
    
    if (g_uemdwFlags & UAAF_NOLOG)
        return E_FAIL;

    if (eCmd & UEME_FBROWSER) {
        ASSERT(0);
        ASSERT(IsEqualIID(*pguidGrp, UEMIID_NIL));
        pguidGrp = &UEMIID_BROWSER;
        eCmd &= ~UEME_FBROWSER;
    }

    iGrp = UEMIIDToInd(pguidGrp);

    pDbLog = g_uempDbLog[iGrp];

    TraceMsg(DM_UEMTRACE2, "uemt: eCmd=0x%x wP=0x%x lP=0x%x(%d)", eCmd, wParam, (int)lParam, (int)lParam);

    szBuf1[0] = szBuf2[0] = 0;

    iTab = SHSearchInt(UemeValTab, ARRAYSIZE(UemeValTab), eCmd);
    if (iTab == -1) {
        ASSERT(0);
        return E_FAIL;
    }

    pszDope = UemeDopeTab[iTab];

    while (ch = *pszDope++) {
        switch (ch) {
        case 'e':
            i = *pszDope++ - '0';
            UEMEncode(iTab, szBuf1, i >= 2 ? szBuf2 : NULL, SIZECHARS(szBuf2), iGrp, eCmd, wParam, lParam);
            TraceMsg(DM_UEMTRACE, "uemt: %s %s (0x%x %x %x)", szBuf1, szBuf2, eCmd, wParam, lParam);
            break;

        case 'f':
            // make sure we don't cause ourselves trouble in future
            // EM only gives us a couple of DWORDs, so we need s.t. like:
            //  bits(UEMIND_*)+bits(wParam)+bits(lParam) <= bits(DWORD)
            // for now we allow 0/-1 in hiword, if/when we use EM we'll
            // need to clean that up.
#if 0
            // fails for pidls, sigh...
            ASSERT((int)(unsigned short)lParam == lParam ||
                ((int)(short)lParam == lParam));
            UEMEvalIE(irulXxx, fTrue, eCmd);
#endif
            break;

        case 'l':
            if (SUCCEEDED(_Lock())) {
                if (dwFlags & UEMF_EVENTMON)
                    DoLog(pDbLog, szBuf1, szBuf2);
#ifdef UAAF_INSTR
                if ((g_uemdwFlags & UAAF_INSTR) && (dwFlags & UEMF_INSTRUMENT))
                    DoLog(g_uempDbLog[iGrp + UEMIND_NINSTR], szBuf1, szBuf2);
#endif
                FireNotify(pguidGrp, eCmd);
                _Unlock();
            }
            break;

        case 'x':
            TraceMsg(DM_UEMTRACE, "uemt: NYI");
            goto Lnodope;

#ifdef DEBUG
        case '!':
            ASSERT(0);
            break;
#endif

        case '@':
            if (SUCCEEDED(_Lock())) {
                UEMSpecial(iTab, iGrp, eCmd, wParam, lParam);
                FireNotify(pguidGrp, eCmd);
                _Unlock();
            }
            break;
        }
    }
Lnodope:

    return S_OK;
}

HRESULT CUserAssist::QueryEvent(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, LPUEMINFO pui)
{
    int iGrp;
    CEMDBLog *pDbLog;
    TCHAR szBuf1[32];               // "UEME_xxx"
    TCHAR szBuf2[MAX_URL_STRING];   // "UEME_xxx:0x%x,%x"

    ASSERT(this != 0);

    if (g_uemdwFlags & UAAF_NOLOG)
        return E_FAIL;

    ASSERT(eCmd == UEME_RUNPIDL
        || eCmd == UEME_RUNPATH || eCmd == UEME_RUNWMCMD);  // others NYI

    ASSERT(pui->cbSize == SIZEOF(*pui));
    // pui->dwVersion?

    iGrp = UEMIIDToInd(pguidGrp);

    pDbLog = g_uempDbLog[iGrp];

    TraceMsg(DM_UEMTRACE2, "uemgi: eCmd=0x%x wP=0x%x lP=0x%x(%d)", eCmd, wParam, (int)lParam, (int)lParam);

    szBuf1[0] = szBuf2[0] = 0;

    int iTab = SHSearchInt(UemeValTab, ARRAYSIZE(UemeValTab), eCmd);
    UEMEncode(iTab, szBuf1, szBuf2, SIZECHARS(szBuf2), iGrp, eCmd, wParam, lParam);

    int cHit;
    //if (SUCCEEDED(_Lock()))
    cHit = pDbLog->GetCount(szBuf2);
    //_Unlock();

    TraceMsg(DM_UEMTRACE, "uemgi: cHit=%d psz=%s", cHit, szBuf2);

    if (pui->dwMask & UEIM_HIT) 
    {
        pui->cHit = cHit;
    }

    if (pui->dwMask & UEIM_FILETIME) 
    {
        pui->ftExecute = pDbLog->GetFileTime(szBuf2);
    }

    return S_OK;
}

HRESULT CUserAssist::SetEvent(const GUID *pguidGrp, int eCmd, WPARAM wParam, LPARAM lParam, LPUEMINFO pui)
{
    int iGrp;
    CEMDBLog *pDbLog;
    TCHAR szBuf1[32];               // "UEME_xxx"
    TCHAR szBuf2[MAX_URL_STRING];   // "UEME_xxx:0x%x,%x"

    ASSERT(this != 0);

    if (g_uemdwFlags & UAAF_NOLOG)
        return E_FAIL;

    ASSERT(pui->cbSize == SIZEOF(*pui));
    // pui->dwVersion?

    iGrp = UEMIIDToInd(pguidGrp);

    pDbLog = g_uempDbLog[iGrp];

    TraceMsg(DM_UEMTRACE2, "uemgi: eCmd=0x%x wP=0x%x lP=0x%x(%d)", eCmd, wParam, (int)lParam, (int)lParam);

    szBuf1[0] = szBuf2[0] = 0;

    int iTab = SHSearchInt(UemeValTab, ARRAYSIZE(UemeValTab), eCmd);
    UEMEncode(iTab, szBuf1, szBuf2, SIZECHARS(szBuf2), iGrp, eCmd, wParam, lParam);

    pui->dwMask &= UEIM_HIT | UEIM_FILETIME;    // what we support

    if (pui->dwMask && SUCCEEDED(_Lock())) {
        if (pui->dwMask & UEIM_HIT) {
            pDbLog->SetCount(szBuf2, pui->cHit);
        }
        if (pui->dwMask & UEIM_FILETIME) {
            pDbLog->SetFileTime(szBuf2, &pui->ftExecute);
        }
        _Unlock();
    }

    return S_OK;
}

//***   CUserAssist::CCI,ctor/dtor/init {

IUnknown *g_uempUaSingleton;

//***   CUserAssist_CreateInstance -- manage *singleton* instance
//
HRESULT CUserAssist_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    HRESULT hr = E_FAIL;

    if (g_uempUaSingleton == 0) {
        IUnknown *pua;

        hr = CUserAssist_CI2(pUnkOuter, &pua, poi);
        if (pua)
        {
            ENTERCRITICAL;
            if (g_uempUaSingleton == 0)
            {
                // Now the global owns the ref.
                g_uempUaSingleton = pua;    // xfer refcnt
                pua = NULL;
            }
            LEAVECRITICAL;
            if (pua)
            {
                // somebody beat us.
                // free up the 2nd one we just created, and use new one
                TraceMsg(DM_UEMTRACE, "sl.cua_ci: undo race");
                pua->Release();
            }

            // Now, the caller gets it's own ref.
            g_uempUaSingleton->AddRef();
            TraceMsg(DM_UEMTRACE, "sl.cua_ci: create pua=0x%x g_uempUaSingleton=%x", pua, g_uempUaSingleton);
        }
    }
    else {
        g_uempUaSingleton->AddRef();
    }

    TraceMsg(DM_UEMTRACE, "sl.cua_ci: ret g_uempUaSingleton=0x%x", g_uempUaSingleton);
    *ppunk = g_uempUaSingleton;
    return *ppunk ? S_OK : hr;
}

//***   CUserAssist_CI2 -- *always* create instance
//
HRESULT CUserAssist_CI2(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    CUserAssist * p = new CUserAssist();

    if (p && FAILED(p->Initialize())) {
        delete p;
        p = NULL;
    }

    if (p) {
        *ppunk = SAFECAST(p, IUserAssist*);
        return S_OK;
    }

    *ppunk = NULL;
    return E_OUTOFMEMORY;
}

extern "C"
HRESULT UEMRegisterNotify(UEMCallback pfnUEMCB, void *param)
{
    HRESULT hr = E_UNEXPECTED;
    if (g_uempUaSingleton)
    {
        CUserAssist *pua = reinterpret_cast<CUserAssist *>(g_uempUaSingleton);
        hr = pua->RegisterNotify(pfnUEMCB, param);
    }
    return hr;
}

extern void GetUEMSettings();
DWORD g_dwSessionStart; // When did this session start?

#if defined(_M_IX86) && (_MSC_VER < 1200)
#pragma optimize("", off)
#define BUG_OPTIMIZE        // restore, see below
#endif

//***
HRESULT CUserAssist::Initialize()
{
    HRESULT hr = S_OK;

    ASSERT(UEMIND_SHELL == 0 && UEMIND_BROWSER == 1);

    hr = _InitLock();

    // get standard loggers
    if (SUCCEEDED(hr))
        hr = GetUEMLogger(UEMIND_SHELL, &g_uempDbLog[UEMIND_SHELL]);
    if (SUCCEEDED(hr))
        hr = GetUEMLogger(UEMIND_BROWSER, &g_uempDbLog[UEMIND_BROWSER]);

    if (SUCCEEDED(hr)) {
        FInitEm();
    }

    GetUEMSettings();

#define UAXF_XSETTINGS  (UAXF_NOPURGE|UAXF_BACKUP|UAXF_NOENCRYPT)
    if (g_uempDbLog[UEMIND_SHELL]) 
    {
        g_uempDbLog[UEMIND_SHELL]->_SetFlags(UAXF_XSETTINGS, g_uemdwFlags & UAXF_XSETTINGS);
        // n.b. just for shell (browser no need, instr no decay)
        g_uempDbLog[UEMIND_SHELL]->GarbageCollect(FALSE);
    }

    if (g_uempDbLog[UEMIND_BROWSER])
    {
        g_uempDbLog[UEMIND_BROWSER]->_SetFlags(UAXF_XSETTINGS, g_uemdwFlags & UAXF_XSETTINGS);
        g_uempDbLog[UEMIND_BROWSER]->GarbageCollect(FALSE);
    }
    
#ifdef UAAF_INSTR
    if (g_uemdwFlags & UAAF_INSTR) {
        if (SUCCEEDED(hr))
            hr = GetUEMLogger(UEMIND_SHELL2, &g_uempDbLog[UEMIND_SHELL2]);
        if (SUCCEEDED(hr))
            hr = GetUEMLogger(UEMIND_BROWSER2, &g_uempDbLog[UEMIND_BROWSER2]);
        if (g_uempDbLog[UEMIND_SHELL2]) {
            g_uempDbLog[UEMIND_SHELL2]->_SetFlags(UAXF_XSETTINGS, g_uemdwFlags & UAXF_XSETTINGS);
            g_uempDbLog[UEMIND_SHELL2]->_SetFlags(UAXF_NODECAY, UAXF_NODECAY);
        }
        if (g_uempDbLog[UEMIND_BROWSER2]) {
            g_uempDbLog[UEMIND_BROWSER2]->_SetFlags(UAXF_XSETTINGS, g_uemdwFlags & UAXF_XSETTINGS);
            g_uempDbLog[UEMIND_BROWSER2]->_SetFlags(UAXF_NODECAY, UAXF_NODECAY);
        }
    }
#endif

    g_dwSessionStart = GetTickCount();
    UEMEnableTimer(UATTOMSEC(g_dIdleTime));

    return hr;
}

#ifdef BUG_OPTIMIZE
#pragma optimize("", on)
#undef BUG_OPTIMIZE
#endif

void CEMDBLog_CleanUp();

//***   CUserAssist_CleanUp -- free up the world (on DLL_PROCESS_DETACH)
// NOTES
//  a bit hoaky right now since our UEMLog object isn't really refcnt'ed
void CUserAssist_CleanUp(DWORD dwReason, void *lpvReserved)
{
    int i;
    IUnknown *pUa;

    ASSERT(dwReason == DLL_PROCESS_DETACH);
    if (lpvReserved != 0) {
        // on process termination, *don't* nuke us since:
        //  - safety: other DLLs in our process may still be using us, and
        // they'll blow up when they reference us if we're freed
        //  - leaks: process termination will free us up when all is done,
        // so there's no worry about a leak
        TraceMsg(DM_UEMTRACE, "bui.cua_cu: skip cleanup (end process/non-FreeLibrary)");
        return;
    }
    // otherwise, on FreeLibrary, *do* nuke us since:
    //  - safety: our refcnt is 0, so nobody is using us any more
    //  - leaks: multiple Load/FreeLibrary calls will cause a leak if we
    // don't free ourselves here

    //ENTERCRITICAL;

    TraceMsg(DM_UEMTRACE, "bui.cua_cu: cleaning up");

    UEMEnableTimer(0);

    // free cache (and make sure we'll GPF if we party on it further)
    for (i = 0; i < UEMIND_NSTANDARD + UEMIND_NINSTR; i++) {
        // UEMIND_SHELL, UEMIND_BROWSER, UEMIND_SHELL2, UEMIND_BROWSER2
        InterlockedExchangePointer((void**) &g_uempDbLog[i], (LPVOID) -1);
    }

    // free 'real' guy
    CEMDBLog_CleanUp();

    // free THIS
    if (pUa = (IUnknown *)InterlockedExchangePointer((void**) &g_uempUaSingleton, (LPVOID) -1)) {
        delete SAFECAST(pUa, CUserAssist *);
    }

    //LEAVECRITICAL;
}

DWORD Reg_GetFlags(DWORD dwInit, HKEY hk, LPCTSTR pszSubkey, LPCTSTR const pszNameTab[], DWORD *dwMaskTab, int cTab)
{
    int i;
    DWORD dwMasks, dwVals;

    dwMasks = dwVals = 0;
    for (i = 0; i < cTab; i++) {
        DWORD dwData, cbSize = SIZEOF(dwData);
        if (SHGetValue(hk, pszSubkey, pszNameTab[i], NULL, &dwData, &cbSize) == ERROR_SUCCESS) {
            TraceMsg(DM_UEMTRACE, "ua: regkey %s\\%s=0x%x", pszSubkey, pszNameTab[i], dwData);
            dwMasks |= dwMaskTab[i];
            if (dwData)
                dwVals |= dwMaskTab[i];
        }
    }
    dwInit = BIT_ASSIGN(dwInit, dwMasks, dwVals);
    TraceMsg(DM_UEMTRACE, "ua.grs: ret 0x%x", dwInit);
    return dwInit;
}

void Reg_GetVals(HKEY hk, LPCTSTR pszSubkey, LPCTSTR const pszNameTab[], DWORD **dwValTab, int cTab)
{
    for (int i = 0; i < cTab; i++) {
        DWORD dwData, cbSize = SIZEOF(dwData);
        if (SHGetValue(hk, pszSubkey, pszNameTab[i], NULL, &dwData, &cbSize) == ERROR_SUCCESS) {
            TraceMsg(DM_UEMTRACE, "ua: regkey %s/%s=0x%x", pszSubkey, pszNameTab[i], dwData);
            *dwValTab[i] = dwData;
        }
    }
}

void GetUEMSettings()
{
    HKEY hk = SHGetShellKey(SHELLKEY_HKCU_EXPLORER, NULL, FALSE);
    if (hk)
    {
        static const LPCTSTR pszName1Tab[] = {
            SZ_NOPURGE  , SZ_BACKUP  , SZ_NOLOG  , SZ_INSTRUMENT, SZ_NOENCRYPT,
        };
        static DWORD dwMask1Tab[] = {
            UAXF_NOPURGE, UAXF_BACKUP, UAAF_NOLOG, UAAF_INSTR   , UAXF_NOENCRYPT,
        };
        static const LPCTSTR pszName2Tab[] = { SZ_SESSTIME,  SZ_IDLETIME , SZ_CLEANTIME, };
        static DWORD *dwVal2Tab[]   = { &g_dSessTime, &g_dIdleTime, &g_dCleanSess,};

        g_uemdwFlags = Reg_GetFlags(g_uemdwFlags, hk, SZ_UASSIST TEXT("\\") SZ_SETTINGS, pszName1Tab, dwMask1Tab, ARRAYSIZE(pszName1Tab));

        TraceMsg(DM_UEMTRACE, "ua: g_uemdwFlags=0x%x", g_uemdwFlags);

        Reg_GetVals(hk, SZ_UASSIST TEXT("\\") SZ_SETTINGS, pszName2Tab, dwVal2Tab, ARRAYSIZE(pszName2Tab));
        if (!((int)UAS_SESSMIN <= (int)g_dSessTime /*&& g_dSessTime<=UAS_SESSMAX*/))
            g_dSessTime = UAS_SESSTIME;
        if (!((int)UAS_IDLEMIN <= (int)g_dIdleTime /*&& g_dIdleTime<=UAS_IDLEMAX*/))
            g_dIdleTime = UAS_IDLETIME;

        RegCloseKey(hk);
    }

    if (SHRestricted2(REST_NoUserAssist, NULL, 0)) {
        TraceMsg(DM_WARNING, "ua: restrict off!");
        g_uemdwFlags |= UAAF_NOLOG;
        g_uemdwFlags &= ~UAAF_INSTR;    // paranoia (UAAF_NOLOG should be enuf)
    }

#ifdef DEBUG
    if (g_uemdwFlags & UAAF_NOLOG)
        TraceMsg(DM_WARNING, "ua: logging off!");
#endif

    return;
}

CUserAssist::CUserAssist() : _cRef(1)
{
    return;
}

//***
// NOTES
//  n.b. we're only called on DLL_PROCESS_DETACH (refcnt never really
// goes to 0).
CUserAssist::~CUserAssist()
{
    if (_hLock)
        CloseHandle(_hLock);
#if 1 // 981022 breadcrumbs for stress failure (see if we're double freed)
    //memcpy((BYTE *)_hLock, "CUAd", 4);
    _hLock = (void *)0x77777777;
#endif

    return;
}

// }

//***   CUserAssist::IUnknown::* {

ULONG CUserAssist::AddRef()
{
    TraceMsg(DM_UEMTRACE2, "cua.ar: _cRef=%d++", _cRef);
    return InterlockedIncrement(&_cRef);
}

ULONG CUserAssist::Release()
{
    ASSERT(_cRef > 0);

    TraceMsg(DM_UEMTRACE2, "cua.r: _cRef=%d--", _cRef);
    // n.b. returns <0,=0,>0 (not actual dec result)
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

HRESULT CUserAssist::QueryInterface(REFIID riid, void **ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CUserAssist, IUserAssist),         // IID_IUserAssist
        { 0 },
    };

    return QISearch(this, qit, riid, ppvObj);
}

// }

//***   locking stuff {

HRESULT CUserAssist::_InitLock()
{
    HRESULT hr = S_OK;

    if ((_hLock = CreateMutex(NULL, FALSE, SZ_UALOCK)) == NULL) {
        TraceMsg(TF_ERROR, "cua.i: no mutex");
        hr = E_FAIL;
    }

    return hr;
}

#define LOCK_TIMEOUT    0   // immediate timeout, should be rare

HRESULT CUserAssist::_Lock()
{
    DWORD dwRes;

    dwRes = WaitForSingleObject(_hLock, LOCK_TIMEOUT);
    switch (dwRes) {
    case WAIT_ABANDONED:
        return S_FALSE;

    case WAIT_OBJECT_0:
        return S_OK;

    case WAIT_TIMEOUT:
        TraceMsg(DM_UEMTRACE, "cua.l: locked (timeout)");
        return E_FAIL;
    }
    /*NOTREACHED*/
    return E_FAIL;
}

HRESULT CUserAssist::_Unlock()
{
    ReleaseMutex(_hLock);
    return S_OK;
}


// }

//***   timer stuff {

DWORD_PTR g_idTimer;
BOOL g_fIdle /*=FALSE*/;

#if !(_WIN32_WINNT >= 0x0500) // {

#define GetLastInputInfo    UEMGetLastInputInfo

typedef struct {
    UINT cbSize;
    DWORD dwTime;
} LASTINPUTINFO;

DWORD g_dwTime;         // prev GetTickCount
int g_csKeys;           // prev GetKeyboardState
int g_csCursor;         // prev GetCursorPos

BOOL (*g_pfnGLII)(LASTINPUTINFO *plii);     // 'real' version

//***   memsum -- checksum bytes
//
int memsum(void *pv, int n)
{
    unsigned char *pb = (unsigned char *)pv;
    int sum = 0;

    while (n-- > 0)
        sum += *pb++;

    return sum;
}

//***   UEMGetLastInputInfo -- simulate (sort of...) GetLastInputInfo
// DESCRIPTION
//  we fake it big time.  our detection of 'currently non-idle' is pretty
// good, but the the actual *time* we were idle is pretty iffy.  each time
// we're called defines a checkpoint.  any time the new checkpoint differs
// from the old one, we update our (approx) idle start point.
BOOL UEMGetLastInputInfo(LASTINPUTINFO *plii)
{
    int csCursor, csKeys;
    POINT ptCursor;
    BYTE ksKeys[256];       // per GetKeyboardState spec

    if (g_dwTime == 0) {
        // 1st time here...
        g_dwTime = GetTickCount();
        g_csCursor = g_csKeys = -1;
        // GetProcAddress only accepts ANSI.
        *(FARPROC *)&g_pfnGLII = GetProcAddress(GetModuleHandle(TEXT("user32.dll")),
            "GetLastInputInfo");
        TraceMsg(DM_UEMTRACE, "bui.glii: init g_dwTime=%d pfn=0x%x", g_dwTime, g_pfnGLII);
    }

#if 1 // 980313 adp: off until we can test it!
    // 1st try the easy (and exact) way...
    if (g_pfnGLII)
        return (*g_pfnGLII)(plii);
#endif

    // now the hard (and approximate) way...
    csCursor = csKeys = -1;
    if (GetCursorPos(&ptCursor))
        csCursor = memsum(&ptCursor, SIZEOF(ptCursor));
    if (GetKeyboardState(ksKeys))
        csKeys = memsum(ksKeys, SIZEOF(ksKeys));
    
    if (csCursor != g_csCursor || csKeys != g_csKeys
      || (csCursor == -1 && csKeys == -1)) {
        TraceMsg(DM_UEMTRACE, "bui.glli: !idle cur=0x%x cur'=%x keys=%x keys'=%x gtc(old)=%x",
            g_csCursor, csCursor, g_csKeys, csKeys, g_dwTime);
        g_dwTime = GetTickCount();
        g_csCursor = csCursor;
        g_csKeys = csKeys;
    }

    plii->dwTime = g_dwTime;

    TraceMsg(DM_UEMTRACE, "bui.uastp: !nt5, simulate GLII()=%d", plii->dwTime);

    return TRUE;
}

#endif // }

LRESULT UEMSendTrayMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //
    //  We may be sending a function pointer, so make sure the target window
    //  is in our address space.
    //
    //  We need to revalidate that g_hwndTray really is the
    //  tray window, because Explorer may have crashed, and then the
    //  window handle got recycled into our process, and so we send
    //  a random message to a window that isn't what we think it is.
    // (raymondc)
    //
    HWND hwndTray;
    DWORD dwPid;
    LRESULT lres;

    hwndTray = GetTrayWindow();
    if (IsWindow(hwndTray) &&
        GetWindowThreadProcessId(hwndTray, &dwPid) &&
        dwPid == GetCurrentProcessId()) {
        lres = SendMessage(hwndTray, uMsg, wParam, lParam);
    } else {
        lres = 0;
    }
    return lres;
}

//***
//
//  UEMTimerProc
//
//  Periodically checks if the user has gone idle.
//
//  Rules for session increment:
//
//  No session lasts longer than g_dIdleTime units.
//
//  If the user remains idle for a long time, keep bumping the
//  "start of session" timer so time spent idle does not count towards
//  the new session.
//
void CALLBACK UEMTimerProc(HWND hwnd, UINT uMsg, UINT idEvt, DWORD dwNow)
{
#ifdef DEBUG
    static long iDepth;     // make sure we don't get 2 ticks
#endif
    UINT dwIdleTime;        // mSec
    LASTINPUTINFO lii;

    ASSERT(iDepth == 0);
    ASSERT(InterlockedIncrement(&iDepth) > 0);

    UEMEnableTimer(0);

    dwIdleTime = UATTOMSEC(g_dIdleTime);    // convert to mSec's (again...)

    lii.cbSize = SIZEOF(lii);
    if (GetLastInputInfo(&lii)) {
        DWORD dwNow = GetTickCount();

        TraceMsg(DM_IDLEDETECT, "UEM.tp: now-start=%d, now-last=%d",
                 dwNow - g_dwSessionStart, dwNow - lii.dwTime);

        if (!g_fIdle && dwNow - g_dwSessionStart >= dwIdleTime)
        {
            g_fIdle = TRUE;
            g_dwSessionStart = dwNow;
            TraceMsg(DM_IDLEDETECT, "UEM.tp: IncrementSession");
            UEMFireEvent(&UEMIID_SHELL, UEME_CTLSESSION, UEMF_XEVENT, TRUE, -1);
            UEMFireEvent(&UEMIID_BROWSER, UEME_CTLSESSION, UEMF_XEVENT, TRUE, -1);
            UEMSendTrayMessage(TM_REFRESH, 0, 0); // RefreshStartMenu
        }

        //
        //  Get out of idle mode if the user has done anything since
        //  the session started.  And mark the session as having started
        //  at the point the user did something.
        //
        if (dwNow - lii.dwTime < dwNow - g_dwSessionStart) {
            TraceMsg(DM_IDLEDETECT, "UEM.tp: not idle; starting new session");
            g_dwSessionStart = lii.dwTime;
            g_fIdle = FALSE;
        }

        //
        //  Now decide how much longer before the next interesting event.
        //
        DWORD dwWait = g_fIdle ? dwIdleTime : dwIdleTime - (dwNow - g_dwSessionStart);

        TraceMsg(DM_UEMTRACE, "UEM.tp: sleep=%d", dwWait);
        UEMEnableTimer(dwWait);
    }
    //else timer left disabled

    ASSERT(InterlockedDecrement(&iDepth) == 0);
    return;
}

//***   UEMEnableTimer -- turn timer on/off
// ENTRY
//  uTimeout    delay in mSec; 0 means disable
void UEMEnableTimer(UINT uTimeout)
{
#if !(_WIN32_WINNT >= 0x0500)
    static BOOL fVirg = TRUE;   // 1st time thru?

    if (fVirg) {
        LASTINPUTINFO lii;

        fVirg = FALSE;

        lii.cbSize = SIZEOF(lii);
        GetLastInputInfo(&lii);     // prime it in case it's simulated
    }
#endif

    if (uTimeout) {
        // ASSERT(!g_idTimer); // race window can hit this assert spuriously
        g_idTimer = UEMSendTrayMessage(TM_SETTIMER, uTimeout, (LPARAM)UEMTimerProc);
    }
    else if (g_idTimer) {
        UEMSendTrayMessage(TM_KILLTIMER, 0, g_idTimer);
        g_idTimer = 0;
    }

    return;
}

// }

// }

//***   utils {

//***   FAST_IsEqualIID -- fast compare
// (cast to 'int' so don't get overloaded ==)
#define FAST_IsEqualIID(piid1, piid2)   ((int) (piid1) == (int) (piid2))

//***
// ENTRY/EXIT
//  iGuid   (return) index of GUID in table, o.w. -1 if not found
// NOTES
//  move to shlwapi if this is needed some place other than here.
int SHSearchIID(IID **pguidTab, int cnt, IID *pguidVal)
{
    IID **pguid;
    BOOL fInt;

    pguid = pguidTab;
    fInt = (pguidVal == 0 || pguidVal == (IID *)-1);
    for (; cnt > 0; cnt--, pguid++) {
        if (fInt) {
            if (*pguid == pguidVal)
                goto Lfound;
        }
        else if (IsEqualIID(**pguid, *pguidVal)) {
Lfound:
            return (int)(pguid - pguidTab);
        }
    }
    return -1;
}

int SHSearchInt(int *psrc, int cnt, int val)
{
    int *pcur;

    pcur = psrc;
    for (; cnt > 0; cnt--, pcur++) {
        if (*pcur == val)
            return (int)(pcur - psrc);
    }
    return -1;
}

int UEMIIDToInd(const GUID *pguidGrp)
{
    int iGrp;

    if (IsEqualIID(*pguidGrp, UEMIID_BROWSER))
        iGrp = UEMIND_BROWSER;
    else if (IsEqualIID(*pguidGrp, UEMIID_SHELL))
        iGrp = UEMIND_SHELL;
    else 
    {
        ASSERT(IsEqualIID(*pguidGrp, UEMIID_NIL));
        iGrp = UEMIND_SHELL;
    }

    return iGrp;
}
// }


// {
#if 0 // currently unused

// glue for standalone test {
//#define XXX_TEST
//#define XXX_DEBUG

#ifdef XXX_TEST // {
#include "stdlib.h"
#include "string.h"
#include "stdio.h"

#define lstrlen(s1)         strlen(s1)
#define lstrcmp(s1, s2)     strcmp(s1, s2)
#define lstrncmp(s1, s2, n) strncmp(s1, s2, n)

#define TRUE    1
#define FALSE   0
#endif // }
// }

//***   rule: smart rename
// DESCRIPTION
//  we recognize renames that remove prefixes (e.g. "Shortcut to ..."),
//  and apply automatically to future renames.  adding back the prefix
//  resets to the original state.
// NOTES
//  assumes only care about prefix, which is bogus for non-US

struct renpre {
    char    *pszPrefix;     // prefix we care about
    int     fChange;        // recent change: 0=nil +1=add -1=del
};

struct renpre SmartPrefixTab[] = {
    { "Shortcut to ", 0 },
    0
};

//***   IsSuffix -- return index of (possible) suffix in string
// ENTRY/EXIT
//  i   (return) index of suffix in string, o.w. -1
int IsSuffix(char *pszStr, char *pszSuf)
{
    int cchStr, cchSuf;
    int i;

    cchStr = lstrlen(pszStr);
    cchSuf = lstrlen(pszSuf);
    if (cchSuf > cchStr)
        return -1;
    i = cchStr - cchSuf;
    if (lstrcmp(pszStr + i, pszSuf) == 0)
        return i;
    return -1;
}

int UpdateSmartPrefix(char *pszOld, char *pszNew, int fChange);

//***   UEMOnRename -- track renames for interesting patterns
// ENTRY/EXIT
//  (SE)    updates smart prefix table if rename effects it
void UEMOnRename(char *pszOld, char *pszNew)
{
    if (UpdateSmartPrefix(pszOld, pszNew, -1)) {
#ifdef XXX_DEBUG
        //  "Shortcut to foo" to "foo"
        printf("or: -1\n");
#endif
    }
    else if (UpdateSmartPrefix(pszNew, pszOld, +1)) {
#ifdef XXX_DEBUG
        //  "foo" to "Shortcut to foo"
        printf("or: +1\n");
#endif
    }
    return;
}

//***   UpdateSmartPrefix -- if op effects prefix, mark change
//
int UpdateSmartPrefix(char *pszOld, char *pszNew, int fChange)
{
    int i;
    struct renpre *prp;

    // (note that if we rename to the same thing, i==0, so we don't
    // do anything.  this is as it should be).

    if ((i = IsSuffix(pszOld, pszNew)) > 0) {
        prp = &SmartPrefixTab[0];   // TODO: for each ...

        // iSuf==cchPre, so pszOld[0..iSuf-1] is prefix
        if (i == lstrlen(prp->pszPrefix) && lstrncmp(pszOld, prp->pszPrefix, i) == 0) {
#ifdef XXX_DEBUG
            printf("usp: o=%s n=%s p=%s f=%d\n", pszOld, pszNew, SmartPrefixTab[0].pszPrefix, fChange);
#endif
            prp->fChange = fChange;
            return 1;
        }
    }

    return 0;
}

//***   GetSmartRename --
// ENTRY/EXIT
//  pszDef  proposed default name (in 'original' form, e.g. 'Shortcut to')
//  i       (ret) index of 'smart' default name
int GetSmartRename(char *pszDef)
{
    char *pszPre;
    int cchPre;

    // for each prefix in smartPrefixList ...
    pszPre = SmartPrefixTab[0].pszPrefix;

    cchPre = lstrlen(pszPre);
    if (strncmp(pszDef, pszPre, cchPre) == 0) {
        if (SmartPrefixTab[0].fChange == -1)
            return cchPre;
    }
    return 0;
}
#endif
// }

#ifdef XXX_TEST // {
char c_szScToFoo[] = "Shortcut to foo";
char c_szScToFred[] = "Shortcut to fred";

char *TestTab[] = {
    c_szScToFoo,
    c_szScToFred,
    "bar",
    0
};

pr(char *p)
{
    int i;

    i = GetSmartRename(p);
    if (i >= 0)
        printf("\t<%s>", p + i);
    else
        printf("\t<%s>", p);
    return;
}

prtab()
{
    pr("foo");
    pr(c_szScToFoo);
    pr(c_szScToFred);
    printf("\n");
}

TstRename()
{
    int i;

    // original
    prtab();

    // delete
    printf("del\n");
    UEMOnRename(c_szScToFoo, "foo");
    prtab();

    // ... again
    printf("del\n");
    UEMOnRename(c_szScToFoo, "foo");
    prtab();

    // add (restore)
    printf("add\n");
    UEMOnRename("foo", c_szScToFoo);
    prtab();

    // ... again
    printf("add\n");
    UEMOnRename("foo", c_szScToFoo);
    prtab();

    // delete partial (shouldn't look like prefix)
    printf("del partial\n");
    UEMOnRename(c_szScToFoo, "to foo");
    prtab();

    // rename to same (shouldn't look like prefix)
    printf("ren same\n");
    UEMOnRename(c_szScToFoo, "c_szScToFoo");
    prtab();
}

main()
{
    TstRename();
}

#endif // }