#include "precomp.h"
#pragma hdrstop
extern HWND hwndFrame;
extern HANDLE hinstShell;
extern BOOL FYield(VOID);

BOOL WaitingOnChild = FALSE;

/*
    Code to support RunExternalProgram, InvokeLibraryProcedure install commands

    LoadLibrary          <diskname>,<libraryname>,<INFvar-for-handle>
    FreeLibrary          <libhandle>
    LibraryProcedure     <infvar>,<libhandle>,<entrypoint>[,<arg>]*
    RunProgram           <var>,<diskname>,<libhandle>,<programfile>[,<arg>]*
    StartDetachedProcess <var>,<diskname>,<libhandle>,<programfile>[,<arg>]*
    InvokeApplet         <libraryname>
*/

#define NO_RETURN_VALUE  (-1)
#define HANDLER_ENTRY    "RegHandler"

typedef enum {
    PRESENT,
    NOT_PRESENT,
    NOT_PRESENT_IGNORE
} PRESENCE;

typedef BOOL (*PFNICMD)(DWORD, RGSZ, LPSTR*);

/*
 *  MakeSureDiskIsAvailable
 *
 *  Given a fully qualified pathname, prompt the user to put the named
 *  disk into the drive specified in the pathname.  If the disk is not
 *  removable, instead flash an error to let the user retry.
 *
 *  Arguments:
 *
 *  Diskname    -   name of disk to prompt for (literal string)
 *  File        -   fully qualified filename of file to make present
 *  fVital      -   whether non-presence of file is critical
 *
 *  returns:  PRESENT            if File is now accessible
 *            NOT_PRESENT        if not (ie, user CANCELed at error popup)
 *            NOT_PRESENT_IGNORE if user IGNOREd error
 *
 */

PRESENCE MakeSureFileIsAvailable(SZ DiskName,SZ File,BOOL fVital)
{
    UINT  DriveType;
    EERC eerc;
    char disk[4];

    disk[0] = *File;
    disk[1] = ':';
    disk[2] = '\\';
    disk[3] = 0;

    DriveType = GetDriveType(disk);
    disk[2] = 0;

    while(!FFileFound(File)) {

        if((DriveType == DRIVE_REMOVABLE) || (DriveType == DRIVE_CDROM)) {
            if(!FPromptForDisk(hinstShell,DiskName,disk)) {
                return(NOT_PRESENT);                   // user canceled
            }
        } else if((eerc = EercErrorHandler(hwndFrame,grcOpenFileErr,fVital,File))
                       != eercRetry)
        {
            return((eerc == eercIgnore)
                  ? NOT_PRESENT_IGNORE
                  : NOT_PRESENT);
        }
    }
    return(PRESENT);
}


/*
 *  FLoadLibrary
 *
 *  Load a fully-qualified library and place the handle in an INF var.
 *
 *  Arguments:
 *
 *  DiskName    -   name of disk to prompt for
 *  File        -   fully qualified filename of dll to load
 *  LibHandle   -   INF var that gets library's handle
 *
 *  returns:  TRUE/FALSE.  If FALSE, user ABORTED from an error dialog.
 *                         If TRUE, library is loaded.
 */

BOOL FLoadLibrary(SZ DiskName,SZ File,SZ INFVar)
{
    HANDLE LibraryHandle;
    char   LibraryHandleSTR[25];

    if(!DiskName || !File) {
        return(FALSE);
    }

    if(MakeSureFileIsAvailable(DiskName,File,TRUE) != PRESENT) {
        return(FALSE);          // not possible to ignore
    }
    while((LibraryHandle = LoadLibrary(File)) == NULL)
    {
        switch(EercErrorHandler(hwndFrame,grcLibraryLoadErr,fTrue,File)) {
        case eercRetry:
            break;
        case eercAbort:
            return(FALSE);
#if DBG
        case eercIgnore:        // illegal because error is critical
        default:                // bogus return value
            Assert(0);
#endif
        }
    }
    LibraryHandleSTR[0] = '|';

    if (!LibraryHandle) {
       return(FALSE);
    }

#if defined(_WIN64)
    _ui64toa((DWORD_PTR)LibraryHandle,LibraryHandleSTR+1,20);
#else
    _ultoa((DWORD)LibraryHandle,LibraryHandleSTR+1,10);
#endif

    if(INFVar) {
        while(!FAddSymbolValueToSymTab(INFVar,LibraryHandleSTR)) {
            if(!FHandleOOM(hwndFrame)) {
                FreeLibrary(LibraryHandle);
                return(FALSE);
            }
        }
    }
    return(TRUE);
}


/*
 *  FFreeLibrary
 *
 *  Free a library given its handle.
 *
 *  Arguments:
 *
 *  Hnadle - dle
 *
 *  returns: TRUE
 *
 */

BOOL FFreeLibrary(SZ Handle)
{
    char buff1[100],buff2[100],buff3[500];
    HANDLE hMod;

    Assert(Handle);

    //
    // Prevent an INF from errantly unloading the interpreter!
    //
    hMod = LongToHandle(atol(Handle+1));
    if(hMod == MyDllModuleHandle) {
        return(TRUE);
    }

    if(!FreeLibrary(hMod)) {

        LoadString(hinstShell,IDS_SETUP_WARNING,buff1,sizeof(buff1)-1);
        LoadString(hinstShell,IDS_BAD_LIB_HANDLE,buff2,sizeof(buff2)-1);
        wsprintf(buff3,buff2,Handle+1);
        MessBoxSzSz(buff1,buff3);
    }
    return(TRUE);
}


/*
 * FLibraryProcedure
 *
 * Arguments:   INFVar - variable to get string result of callout
 *
 *              HandleVar - library's handle
 *
 *              EntryPoint - name of routine in library to be called
 *
 *              Args - argv to be passed to the routine.  The vector must
 *                     be terminated with a NULL entry.
 *
 */

BOOL APIENTRY FLibraryProcedure(SZ INFVar,SZ Handle,SZ EntryPoint,RGSZ Args)
{
    DWORD  cArgs;
    HANDLE LibraryHandle;
    PFNICMD Proc;
    LPSTR  TextOut;
    BOOL   rc = FALSE;
    EERC   eerc;
    SZ     szErrCtl ;

    LibraryHandle = LongToHandle(atol(Handle+1));

    while((Proc = (PFNICMD)GetProcAddress(LibraryHandle,EntryPoint)) == NULL) {
        if((eerc = EercErrorHandler(hwndFrame,grcBadLibEntry,FALSE,EntryPoint))
           == eercAbort)
        {
            return(FALSE);
        } else if(eerc == eercIgnore) {
            goto FLP;
        }
        Assert(eerc == eercRetry);
    }

    for(cArgs = 0; Args[cArgs]; cArgs++);       // count arguments

    while(!(rc = Proc(cArgs,Args,&TextOut))) {

        //  If the symbol "FLibraryErrCtl" is found and its value is non-zero,
        //  the INF file will handle all error conditions.

        if ( (szErrCtl = SzFindSymbolValueInSymTab("FLibraryErrCtl"))
              && atoi(szErrCtl) > 0  )
        {
           rc = 1 ;
           break ;
        }

        if((eerc = EercErrorHandler(hwndFrame,grcExternal,FALSE,EntryPoint,TextOut)) == eercAbort) {
            return(FALSE);
        } else if(eerc == eercIgnore) {
            break;
        }
        Assert(eerc == eercRetry);
    }

    FLP:
    if((INFVar != NULL) && (*INFVar != '\0')) {
        FAddSymbolValueToSymTab(INFVar,rc ? TextOut : "ERROR");
    }

    return(TRUE);
}


/*
 * FRunProgram
 *
 * Arguments:   INFVar - an INF variable which will get the rc of the
 *                       exec'ed program.
 *
 *              DiskName - string used in prompting the user to insert
 *                         a disk
 *
 *              ProgramFile - qualified name of program to be run
 *
 *              Args - argv to be passed directly to spawn (so must
 *                     include argv[0] filled in).
 *
 */

BOOL APIENTRY FRunProgram(SZ   INFVar,
                          SZ   DiskName,
                          SZ   LibHand,
                          SZ   ProgramFile,
                          RGSZ Args)
{
    char    Number[15];
    DWORD   rc;
    HANDLE  ProcID = NULL;
    EERC    eerc;
    MSG     msg;
    int     iWaitState = P_NOWAIT;
    SZ      szWaitState;

    switch(MakeSureFileIsAvailable(DiskName,ProgramFile,FALSE)) {
    case PRESENT:
        break;
    case NOT_PRESENT:
        return(fFalse);
    case NOT_PRESENT_IGNORE:
        goto FRP;
#if DBG
    default:
        Assert(0);      // illegal PRESENCE value
#endif
    }

    if((LibHand != NULL) && (*LibHand != '\0')) {
        SetSupportLibHandle(LongToHandle(atol(LibHand+1)));     // skip the leading |
    }

    WaitingOnChild = TRUE;

    if ( (szWaitState = SzFindSymbolValueInSymTab("FWaitForProcess"))
              && atoi(szWaitState) > 0  )
    {
        iWaitState = P_WAIT;
        rc=(DWORD)_spawnv(iWaitState,ProgramFile,Args);
    } else
    {
        while((ProcID=(HANDLE)_spawnv(iWaitState,ProgramFile,Args)) == (HANDLE)(-1)) {
            if((eerc = EercErrorHandler(hwndFrame,
                                    grcSpawn,
                                    FALSE,
                                    ProgramFile)
                ) == eercAbort
            )
            {
                WaitingOnChild = FALSE;
                return(FALSE);
            } else if(eerc == eercIgnore) {
                goto FRP;
            }
            Assert(eerc == eercRetry);
        }

        while(WaitForSingleObject(ProcID,350)) {
            FYield();
        }
    }

    //
    // Process any pending messages so the user can do stuff like move the
    // gauge around the screen if he wants to.
    //

    while(PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
        DispatchMessage(&msg);
    }

    FRP:
    WaitingOnChild = FALSE;
    if((INFVar != NULL) && (*INFVar != '\0')) {
        FAddSymbolValueToSymTab(INFVar,
                                ((szWaitState != NULL ) && (atoi(szWaitState)>0)) ? _itoa(rc,Number,10):
                                (GetExitCodeProcess(ProcID,&rc) ? _itoa(rc,
                                                                      Number,
                                                                      10
                                                                     )
                                                               : "ERROR")
                               );
    }

    CloseHandle(ProcID);

    SetForegroundWindow(hwndFrame);     // reactivate ourselves

    return(fTrue);
}



/*
 * FStartDetachedProcess
 *
 * Arguments:   INFVar - an INF variable which will get the rc of the
 *                       exec'ed program.
 *
 *              DiskName - string used in prompting the user to insert
 *                         a disk
 *
 *              ProgramFile - qualified name of program to be run
 *
 *              Args - argv to be passed directly to spawn (so must
 *                     include argv[0] filled in).
 *
 */

BOOL APIENTRY
FStartDetachedProcess(
    SZ   INFVar,
    SZ   DiskName,
    SZ   LibHand,
    SZ   ProgramFile,
    RGSZ Args
    )
{
    CHAR    Number[15];
    CHAR    App[MAX_PATH];
    DWORD   rc;
    HANDLE  ProcID = NULL;
    EERC    eerc;
    BOOL    Status = FALSE;
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;
    INT     i;

    //
    // Make sure the file is available, prompt the user if necessary
    //

    switch(MakeSureFileIsAvailable(DiskName,ProgramFile,FALSE)) {
    case PRESENT:
        break;
    case NOT_PRESENT:
        return(fFalse);
    case NOT_PRESENT_IGNORE:
        goto FRP;
#if DBG
    default:
        Assert(0);      // illegal PRESENCE value
#endif
    }

    if((LibHand != NULL) && (*LibHand != '\0')) {
        SetSupportLibHandle(LongToHandle(atol(LibHand+1)));     // skip the leading |
    }

    //
    // Initialise Startup info
    //

    si.cb           = sizeof(STARTUPINFO);
    si.lpReserved   = NULL;
    si.lpDesktop    = NULL;
    si.lpDesktop    = NULL;
    si.lpTitle      = NULL;
    si.dwX = si.dwY = si.dwXSize = si.dwYSize = si.dwFlags = 0L;
    si.wShowWindow  = 0;
    si.lpReserved2  = NULL;
    si.cbReserved2  = 0;

    //
    // Create the app command line
    //

    *App = '\0';
    for(i = 0; Args[i]; i++){
        lstrcat( App, Args[i] );
        lstrcat( App, " " );
    }


    //
    // Use Create Process to create a detached process
    //

    while (!CreateProcess(
                (LPSTR)NULL,                  // lpApplicationName
                App,                          // lpCommandLine
                (LPSECURITY_ATTRIBUTES)NULL,  // lpProcessAttributes
                (LPSECURITY_ATTRIBUTES)NULL,  // lpThreadAttributes
                FALSE,                        // bInheritHandles
                DETACHED_PROCESS,             // dwCreationFlags
                (LPVOID)NULL,                 // lpEnvironment
                (LPSTR)NULL,                  // lpCurrentDirectory
                (LPSTARTUPINFO)&si,           // lpStartupInfo
                (LPPROCESS_INFORMATION)&pi    // lpProcessInformation
                )) {


        if((eerc = EercErrorHandler(hwndFrame,grcSpawn,FALSE,ProgramFile)
           ) == eercAbort){
            return(FALSE);
        } else if(eerc == eercIgnore) {
            goto FRP;
        }

        Assert(eerc == eercRetry);
    }

    Status = TRUE;

    //
    // Since we are execing a detached process we don't care about when it
    // exits.  To do proper book keeping, we should close the handles to
    // the process handle and thread handle
    //

    CloseHandle( pi.hThread );
    CloseHandle( pi.hProcess );

FRP:
    if((INFVar != NULL) && (*INFVar != '\0')) {
        FAddSymbolValueToSymTab(
            INFVar,
            Status ? _itoa(0, Number, 10) : "ERROR"
            );
    }

    CloseHandle(ProcID);
    return(fTrue);
}


PRESENCE SendMessageToApplet(PROC Proc,HWND hwnd,DWORD msg,LONG p1,LONG p2,LONG rcDesired,SZ Libname)
{
#if 0
    LONG    rcActual;

    rcActual = Proc(hwnd,msg,p1,p2);

    if((rcDesired == NO_RETURN_VALUE) || (rcActual == rcDesired)) {
        return(PRESENT);
    }

    while(Proc(hwnd,msg,p1,p2) != rcDesired) {
        switch(EercErrorHandler(hwndFrame, grcApplet,fFalse,Libname,NULL,NULL)) {
        case eercRetry:
            break;
        case eercIgnore:
            return(NOT_PRESENT_IGNORE);
        case eercAbort:
            return(NOT_PRESENT);
#if DBG
        default:
            Assert(0);          // illegal case
#endif
        }
    }
    return(PRESENT);
#else
    Unused(Proc);
    Unused(hwnd);
    Unused(msg);
    Unused(p1);
    Unused(p2);
    Unused(rcDesired);
    Unused(Libname);
    return(NOT_PRESENT);
#endif
}


/*
 * FInvokeApplet
 *
 * Arguments:   LibraryFile - qualified name of library to load
 *
 *              Args - argv to be passed to the routine. The vector must
 *                     be terminated with a NULL entry.
 *
 */

// BUGBUG -- also need some way to specify the sub-handler

BOOL APIENTRY FInvokeApplet(SZ LibraryFile)
{
#if 0
    HANDLE   LibraryHandle;
    PROC     Proc;
    CFGINFO  cfginfo;
    PRESENCE p;

    switch(FLoadLibrary(LibraryFile,HANDLER_ENTRY,&LibraryHandle,&Proc)) {
    case PRESENT:
        break;
    case NOT_PRESENT:
        return(fFalse);         // user wants to exit setup
    case NOT_PRESENT_IGNORE:
        return(fTrue);
#if DBG
    default:
        Assert(0);      // illegal PRESENCE value
#endif
    }

    if((p = SendMessageToApplet(Proc,hwndFrame,CFG_INIT,0,0,TRUE)) == PRESENT) {

        if((p = SendMessageToApplet(Proc,
                                    hwndFrame,
                                    CFG_INQUIRE,
                                    subhandler#,
                                    &cfginfo,
                                    TRUE,
                                    LibraryFile)) == PRESENT)
        {
            SendMessageToApplet(Proc,
                                hwndFrame,
                                CFG_DBLCLK,
                                registry handle,
                                cfginfo.lData,
                                -1,
                                LibraryFile);

            // it's activated -- now what?

            SendMessageToApplet(Proc,
                                hwndFrame,
                                CFG_STOP,
                                subhandler#,
                                cfginfo.lData,
                                -1,
                                LibraryFile);

            SendMessageToApplet(Proc,hwndFrame,CFG_EXIT,0,0,-1,LibraryFile);
        }
    }
    FreeLibrary(LibraryHandle);
    return(p != NOT_PRESENT);
#else
    Unused(LibraryFile);

    MessBoxSzSz("Stub","InvokeApplet called");
    return(fTrue);
#endif
}