/*  syscwin.c                     Copyright (C) 1989-2002 Codemist Ltd */


/*
 * Windows 95, 98, NT and 2000, XP support, using MFC via the "cwin" package.
 * Should also be OK for Windows 3.x if "win32s" is used, although I think
 * I now count Windows 3.x/win32s as "out of date, unsupported".
 */

/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */


/* Signature: 4f874860 10-Oct-2002 */

#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <math.h>

#ifdef __cplusplus
#include <afxwin.h>
#else
#include <windows.h>
#endif

#include "machine.h"
#include "tags.h"
#include "externs.h"
#include "read.h"
#include "cslerror.h"
#include "sys.h"
#include "cwin.h"
#ifdef TIMEOUT
#include "timeout.h"
#endif

#ifdef __WATCOMC__
#include <float.h>
#endif

#include <errno.h>
#include <io.h>
#include <dos.h>
#include <direct.h>
#include <sys\stat.h>

#ifdef _MSC_VER
#define strdup(x) _strdup(x)
#endif


#ifndef SOFTWARE_TICKS

void CALLBACK deal_with_tick(UINT idevent, UINT x1, DWORD x2, DWORD x3, DWORD x4)
/*
 * A timer arranges to call me back kere about 5 times per second.
 * Since this call is not synchronised with anything else all I do is
 * set flags that will be looked at by other bits of code.
 */
{
    if (tick_pending == 0)
    {
        if (already_in_gc) tick_on_gc_exit = YES;
        else
        {   Lisp_Object nil = C_nil;
            CSLbool xxx = NO;
            if (exception_pending()) flip_exception(), xxx = YES;
            tick_pending = YES;
            saveheaplimit = heaplimit;
            heaplimit = fringe;
            savevheaplimit = vheaplimit;
            vheaplimit = vfringe;
            savecodelimit = codelimit;
            codelimit = codefringe;
            savestacklimit = stacklimit;
            stacklimit = stackbase;
            if (xxx) flip_exception();
        }
    }
}
#endif


static CSLbool ticker_active = NO;

void add_ticker(void)
{
    if (ticker_active) return;
#ifdef SOFTWARE_TICKS
    countdown = SOFTWARE_TICKS;
#else
/*
 * I take an interrupt 5 times per second...
 */
//    my_timer = SetTimer(my_window, (UINT)1, (UINT)200,
//                        (TIMERPROC)deal_with_tick);
    timeBeginPeriod(200);
    my_timer = timeSetEvent(200, 200, (LPTIMECALLBACK)deal_with_tick,
                            0, TIME_PERIODIC);
    if (my_timer == 0)
    {   alert_box("Failed to start up the timer");
        ticker_active = NO; /* Actually a shambles */
    }
    else
#endif
    ticker_active = YES;
}

void MS_CDECL remove_ticker(void)
{
    if (!ticker_active) return;
#ifndef SOFTWARE_TICKS
//    KillTimer(my_window, my_timer);
    timeKillEvent(my_timer);
    timeEndPeriod(200);
#endif
    ticker_active = NO;
}

/*
 * void poll_for_attn()
 * {
 *    kbhit();   // causes ^C to be noticed!
 * }
 */

static clock_t prev_clock = 0;

#ifdef INITIAL_SOFTWARE_TICKS
int32 software_ticks = INITIAL_SOFTWARE_TICKS;
int32 software_tick_count = 0, prev_software_tick_count = 0;
#endif

void accept_tick(void)
{
/*
 * This is where I can put things that need to be done regularly. At
 * In particular I can poll the window manager here.
 */
/*
 * Around once per 2 seconds I update the clock display on the menu bar -
 * the intent of this is to give a bit of feedback to the user that
 * things are happening. NOTE WELL: I tune all this in terms of CPU time
 * (measured with clock()) rather than wall-clock time (time()). On
 * a multi-tasking system this means that a Lisp task running at low
 * priority in the background will hardly poll at all, and one that is
 * suspended (if such a possibility can arise) will not do a lot. But
 * I HOPE that CPU time is not consumed while one is suspended awaiting
 * keyboard input, and so that sort of delay should not cause messups
 * in my "learning" of the correct poll rate.
 */
    clock_t t0 = clock();
#ifdef SOFTWARE_TICKS_PER_SECOND
    software_tick_count++;
#endif
    if (prev_clock == 0 || t0 > prev_clock + 2*CLOCKS_PER_SEC)
    {   long int t, gct;
        Lisp_Object nil;
        ensure_screen(); nil = C_nil;
        if (exception_pending()) return;
#ifdef SOFTWARE_TICKS_PER_SECOND
        if (prev_clock != 0)
        {   double t1 = (double)(t0-prev_clock)/(double)CLOCKS_PER_SEC;
            double ratio = 
               (double)(software_tick_count - prev_software_tick_count)/t1;
            int32 w;
/*
 * t1 is how long since I was last here, ratio is the number of
 * ticks per second over that time-span.
 */
            ratio = ratio / (double)SOFTWARE_TICKS_PER_SECOND;
            prev_software_tick_count = software_tick_count;
/*
 * Now ratio is the extent by which I was taking ticks too fast.
 * To dampen out my correction I will scale software_ticks by the
 * square root of this.
 */
            ratio = sqrt(ratio);
            w = (int)(1000.0 * ratio);
/*
 * I clamp the correction factor so I never adjust my clock rate by
 * a factor of more than (about) 3.
 */
            if (w > 3000) w = 3000;
            else if (w < 300) w = 300;
/*
 * Furthermore I attempt to keep software_ticks within integer range.
 */
            if (software_ticks < (0x7fffffff/3000) &&
                software_ticks > 50)
                software_ticks = (w*software_ticks)/1000;
        }
#endif
        t0 = clock();
        if (clock_stack == &consolidated_time[0])
        {   consolidated_time[0] +=
                (double)(t0 - base_time)/(double)CLOCKS_PER_SEC;
            base_time = t0;
        }
        t = (long int)(100.0 * consolidated_time[0]);
        gct = (long int)(100.0 * gc_time);
        report_time(t, gct);
        prev_clock = t0;
    }
    cwin_poll_window_manager(0);
    if (cwin_interrupt_pending != 0)
    {   Lisp_Object nil = C_nil;
        interrupt_pending = YES;
        if (exception_pending()) nil = (Lisp_Object)((int32)nil ^ 1);
        miscflags |= HEADLINE_FLAG | MESSAGES_FLAG;
        cwin_interrupt_pending = 0;
    }
}

void putc_stdout(int c)
{
    if (alternative_stdout != NULL) putc(c, alternative_stdout);
    else cwin_putchar(c);
}

void pause_for_user(void)
{
}

int pipes_today = 0;
int win32s = 0;

void flush_screen(void)
{
    cwin_ensure_screen();
}

void start_up_window_manager(int use_wimp)
{
    int32 n = GetVersion();
    CSL_IGNORE(use_wimp); /* on this platform ALWAYS use the window system */
    switch (n & 0xc0000000)
    {
case 0: win32s = 0;       /* NT, 2000, XP */
        pipes_today = 1;
        break;
case 0x80000000:
default:
        win32s = 1;       /* win32s */
        pipes_today = 0;
        break;
case 0xc0000000:
        win32s = 2;       /* Windows 95 */
        pipes_today = 1;
        break;
    }
}

int wimpget(char *buf)
{
    int c, n=0;
    Lisp_Object nil;
    ensure_screen(); nil = C_nil;
    if (exception_pending()) return 0;
    while (n < 255)
    {   c = cwin_getchar();
        nil = C_nil;
        if (exception_pending() || c == EOF) return 0;
        c = c & 0xff;
        buf[n++] = (char)c;
        if (c == '\n') break;
    };
    return n;
}


/*
 * Slightly optional jollies re GC statistics...
 */
 
static char time_string[32], space_string[32];

void report_time(int32 t, int32 gct)
{
    sprintf(time_string, "%ld.%.2ld+%ld.%.2ld secs  ",
        t/100L, t%100L, gct/100L, gct%100L);
    if ((window_heading & 1) == 0) cwin_report_left(time_string);
}

void report_space(int n, double percent)
{
    sprintf(space_string, "[GC %d]:%.2f%%", n, percent);
    if ((window_heading & 4) == 0) cwin_report_right(space_string);
}

/*
 * End of the worst of the window-manager aware code
 */

#undef exit
#define exit(a) my_exit(a)

#include "fileops.c"
#include "scandir.c"

/*
 * To the extent that I believe that versions of Windows that support long
 * file names are now readily available, and that compatibility with older
 * systems is ridiculous, I disable the conversion to 8+3 format shown
 * here. The particular bit of sample code that convinced me was attempting
 * to create files with names like xxx.html where each file contained the
 * names of other files in the set. And truncation to xxx.htm meant that the
 * cross-references failed. Ugh.
 */

#ifdef EIGHT_PLUS_THREE

/*
 * Windows 95 can support long file names, but Windows 3.x and DOS do not.
 * This is in fact a potential cause of big mess. What I do here is to
 * truncate all file-names to 8+3 format. The effect will be that if files
 * have been set up for use with Windows 3.x they will still work, and files
 * created by and used ONLY by this system will be OK. But if a Windows-95
 * utility is used to create a file with a long name then I will not be able
 * to use that name to access it.  Users beware!
 */

static void eight_plus_three(char *s)
{
    int c, i=0;
    char *p=s;
    while ((c=*p++)!=0 && c!='.' && c!='\\' && c!=':') i++;
    if (c==':') return;
    if (i>8)
    {   char *q=s+8;
        p--;
        while ((*q++=*p++)!=0);
        p=s+9;
    }
    if (c!='.') return;
    i=0;
    s=p;
    while ((c=*p++)!=0 && c!='\\') i++;
    if (i>3)
    {   char *q=s+3;
        p--;
        while ((*q++=*p++)!=0);
    }
}

#endif

#include "filename.c"


int change_directory(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    if (*filename == 0) return 1;
#ifdef _MSC_VER
    if (_chdir(filename))
#else
    if (chdir(filename))
#endif
    {   char err_buf[LONGEST_LEGAL_FILENAME+100];
        switch (errno)
        {
    case ENOENT:  
            sprintf(err_buf,"The directory %s does not exist.",filename);
            break;
    default:
            sprintf(err_buf,"Cannot change directory to %s.",filename);
            break;
        }
        aerror0(err_buf);
        return 1;
    }
    else return 0;
}

int create_directory(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    if (*filename == 0) return 1;
#ifdef _MSC_VER
    return _mkdir(filename);
#else
    return mkdir(filename);
#endif
}

CSLbool file_exists(char *filename, char *old, size_t n, char *tt)
{
#ifdef _MSC_VER
    struct _stat statbuff;
#else
    struct stat statbuff;
#endif
    process_file_name(filename, old, n);
    if (*filename == 0) return NO;
#ifdef _MSC_VER
    if (_stat(filename, &statbuff) != 0) return NO;
#else
    if (stat(filename, &statbuff) != 0) return NO;
#endif
    strcpy(tt, ctime(&(statbuff.st_mtime)));
    return YES;
}

static void remove_files(char *name, int dirp, long int size)
/*
 * Remove a file, or a directory and all its contents. This function
 * can still misbehave if you do not have write access to all the files
 * involved.
 */
{
    size = size;
    switch (dirp)
    {
case SCAN_ENDDIR:
#ifdef _MSC_VER
        _rmdir(name);  return;
#else
        rmdir(name);   return;
#endif
case SCAN_FILE:
        remove(name);  return;
    }
}

int delete_file(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    if (*filename == 0) return 1;
    scan_directory(filename, remove_files);
    return 0;
}

long file_length(char *filename, char *old, size_t n)
{
#ifdef _MSC_VER
    struct _stat buf;
#else
    struct stat buf;
#endif
    process_file_name(filename, old, n); 
    if (*filename == 0) return 0;
#ifdef _MSC_VER
    if (_stat(filename,&buf) == -1) return -1;
#else
    if (stat(filename,&buf) == -1) return -1;
#endif
    return (long)(buf.st_size);
}

int directoryp(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    if (*filename == 0) return 0;
    return (GetFileAttributes(filename) == FILE_ATTRIBUTE_DIRECTORY);
}

extern char *get_truename(char *filename, char *old, size_t n)
{
#ifdef _MSC_VER
    struct _stat buf;
#else
    struct stat buf;
#endif
    char *temp, *fn, *dir, *pwd;

    process_file_name(filename, old, n);
    if (*filename == 0) aerror("truename");

/* Find out whether we have a file or a directory.  Be careful in case
 *  the first two characters define a drive.
 */

    if (0 /* strlen(filename) > 2 && *(filename+1) == ':' */)
    {
/*
 * Again beware the fact that aerror0 does not change the flow of control.
 */
#ifdef _MSC_VER
        if (_stat(filename+2,&buf) == -1) aerror0("truename: cannot stat file");
#else
        if (stat(filename+2,&buf) == -1) aerror0("truename: cannot stat file");
#endif
    }
    else
    {
#ifdef _MSC_VER
        if (_stat(filename,&buf) == -1) aerror0("truename: cannot stat file");
#else
        if (stat(filename,&buf) == -1) aerror0("truename: cannot stat file");
#endif
    }

  /* Store current directory */
#ifdef _MSC_VER
    if ((pwd = (char *)_getcwd((char *)NULL, LONGEST_LEGAL_FILENAME)) == NULL)
#else
    if ((pwd = (char *)getcwd((char *)NULL, LONGEST_LEGAL_FILENAME)) == NULL)
#endif
        aerror0("truename: cannot get current working directory");

    if ((buf.st_mode & S_IFMT) == S_IFDIR)
    {   /* We have a directory */
        char *dir = (char*)(*malloc_hook)(LONGEST_LEGAL_FILENAME);

#ifdef _MSC_VER
        if (_chdir(filename) != 0) 
            aerror0("truename: cannot change directory");
        if (_getcwd(dir ,LONGEST_LEGAL_FILENAME) == NULL)
            aerror0("truename: cannot get current working directory");
        if (_chdir(pwd) != 0)
            aerror0("truename: cannot change directory");
#else
        if (chdir(filename) != 0) 
            aerror0("truename: cannot change directory");
        if (getcwd(dir ,LONGEST_LEGAL_FILENAME) == NULL)
            aerror0("truename: cannot get current working directory");
        if (chdir(pwd) != 0)
            aerror0("truename: cannot change directory");
#endif
        (*free_hook)(pwd);
    /* 
     * This is an axiom-specific hack --- it expects that truname
     * preserves trailing directory characters.
     */
        if (old[n-1] == '\\' || old[n-1] == '/' &&
            dir[strlen(dir)-1] != '\\')
        {   n = strlen(dir);
            dir[n] = '\\';
            dir[n+1] = '\0';
        }
        return dir;
    }
    else
    {   /* Assume we have some kind of file */
        temp = strrchr(filename,'\\');
        if (temp) 
        {   /* Found a directory component */
            fn   = strdup(temp);
            *temp = '\0';
            /* fn is now "\file" and filename is the directory */

#ifdef _MSC_VER
            if (_chdir(filename) != 0)
                aerror0("truename: cannot change directory");
            if ((temp = (char *)_getcwd((char *)NULL,LONGEST_LEGAL_FILENAME)) == NULL)
                aerror0("truename: cannot get current working directory");
            if (_chdir(pwd) != 0)
                aerror0("truename: cannot change directory");
#else
            if (chdir(filename) != 0)
                aerror0("truename: cannot change directory");
            if ((temp = (char *)getcwd((char *)NULL,LONGEST_LEGAL_FILENAME)) == NULL)
                aerror0("truename: cannot get current working directory");
            if (chdir(pwd) != 0)
                aerror0("truename: cannot change directory");
#endif
            dir = (char *)(*malloc_hook)((strlen(temp) + strlen(fn) + 1)*sizeof(char));
            strcpy(dir,temp);
            (*free_hook)(temp);
            strcat(dir, fn);
            (*free_hook)(pwd);
            (*free_hook)(fn);
            return dir;
        }
        else
        {   dir = (char *)(*malloc_hook)((strlen(pwd) +
                        strlen(filename) + 2)*sizeof(char));
            strcpy(dir, pwd);
            strcat(dir, "\\");
            strcat(dir,filename);
            (*free_hook)(pwd);
            return dir;
        }
    }
}

int get_current_directory(char *s, int n)
{
    int n1 = GetCurrentDirectory(n, s);
    if (n1 == 0)
    {   aerror0("cannot get current directory name");
        return 0;
    }
    else if (n1 >= n)
    {   aerror("the pathname of the current directory is too long");
        return 0;
    }
    else return n1;
}

#ifdef NAG_VERSION

#define MAX_NUMBER_OF_FILES 2048

int list_directory_members(char *filename, char *old, char **filelist[],
                           size_t n)
{   WIN32_FIND_DATA fileData;
    HANDLE fileHandle;
    int32 number_of_entries = 1;
    char **files;

    process_file_name(filename, old, n);

    if (*filename == 0) strcpy(filename,"*.*");
    else if (filename[strlen(filename)-1] != '\\')
        filename=strcat(filename,"\\*.*");
    else filename=strcat(filename,"*.*");
    
    fileHandle = FindFirstFile(filename,&fileData);
    
    if (fileHandle == INVALID_HANDLE_VALUE) return 0;

/*
 * The fixed allocation size here seems unsatisfactory, but I will leave it
 * like that for the moment because altering things to behave better would
 * probably involve redesigning the interface to this function.
 */
    files=(char **)(*malloc_hook)(MAX_NUMBER_OF_FILES*sizeof(char *));
    files[0]=strdup(fileData.cFileName);

    while(FindNextFile(fileHandle,&fileData))
    {   files[number_of_entries] = strdup(fileData.cFileName);
        number_of_entries++;
    }

    FindClose(fileHandle);

    *filelist = files;

    return number_of_entries;
}

#else

void list_directory_members(char *filename, char *old,
                            size_t n, directory_callback *fn)
{
    process_file_name(filename, old, n);
    scan_files(filename, fn);
}


#endif

/*
 * The following few lines are an attempt to provide compatibility
 * between Watcom C 11.0 and Microsoft Visual C++ version 5... Both provide
 * a stat() function but they use different names for the bits that
 * can be used to test if files are readable or writable.
 */

#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif

int file_readable(char *filename, char *old, size_t n)
{
#ifdef _MSC_VER
    struct _stat buf;
#else
    struct stat buf;
#endif
    process_file_name(filename, old, n);
    if (*filename == 0) return 0;
#ifdef _MSC_VER
    if (_stat(filename,&buf) == -1)
#else
    if (stat(filename,&buf) == -1)
#endif
      return 0; /* File probably does not exist */
    else
        return (buf.st_mode & S_IRUSR);
}

int file_writeable(char *filename, char *old, size_t n)
{
#ifdef _MSC_VER
    struct _stat buf;
#else
    struct stat buf;
#endif
    process_file_name(filename, old, n);
    if (*filename == 0) return 0;
#ifdef _MSC_VER
    if (_stat(filename,&buf) == -1)
#else
    if (stat(filename,&buf) == -1)
#endif
      return 0; /* Should we check to see if the directory is writeable? */
    else
        return (buf.st_mode & S_IWUSR);
}

int rename_file(char *from_name, char *from_old, size_t from_size,
                char *to_name, char *to_old, size_t to_size)
{
    process_file_name(from_name, from_old, from_size);
    process_file_name(to_name, to_old, to_size);
    if (*from_name == 0 || *to_name == 0) return 0;
    return rename(from_name,to_name);
}

/*
 * It appears to me that "system" as provided by the Windows NT SDK
 * libraries is a real mess.  This is perhaps because executing a
 * command might want to generate terminal output, so when one invokes
 * "system" it first pops up a console window running the command
 * interpreter, then loads the thing you wanted executed, and then
 * closes everything down - probably before you have had any real
 * chance to observe any output that was generated.  I might therefore
 * want to invent a version of my own... as in here using CreateProcess.
 * The issues about Win32 vs Win32s incompatibilities here are HORRID.
 */
 

static HANDLE osRunRedirect(char *cmd, BOOL wait, HANDLE hin,
                            HANDLE hout, HANDLE herr);
static HANDLE osMakeReadPipe(char *cmd, HANDLE *);
static char *osMangleCommand(char *s);

int my_system(char *s)
{
    HANDLE h, hproc;
    DWORD  retCode;
    char buf[256];
    char *os;
    DWORD nread;
    BOOL done = FALSE;
        
    os = s;
    s = osMangleCommand(s);
    h = osMakeReadPipe(s, &hproc);
    if (s != os) free(s);
    if (h==NULL)
    {   term_printf("Failed: %d\n", GetLastError());
        CloseHandle(hproc);
        return 0;
    }

    while (!done)
    {   DWORD i;
        BOOL res;
        res = ReadFile(h, (void*) buf, 256, &nread, NULL);
        if (res == TRUE && nread == 0 && 
            GetExitCodeProcess(hproc, &retCode) != STILL_ACTIVE) 
        {   done = TRUE;
        }
        else if (!res)
        {   int err = GetLastError();
            if (GetExitCodeProcess(hproc, &retCode) == STILL_ACTIVE) 
            {   term_printf("Error: %d\n", err);
                CloseHandle(hproc);                                
                CloseHandle(h);
                return -1;
            }
            return retCode;
        }
        else
        {   for (i=0; i<nread; i++)
            {   if (buf[i] != '\r')
                {   stdout_printf("%c", buf[i]);
                }
            }
        }
    }
    GetExitCodeProcess(hproc, &retCode);
    return retCode;
}

static HANDLE osMakeReadPipe(char *cmd, HANDLE *proc)
{
    HANDLE hwr, hwr2, hrd, hproc;
    SECURITY_ATTRIBUTES attribs;
    BOOL ret;

    attribs.nLength = sizeof(attribs);
    attribs.lpSecurityDescriptor = NULL;
    attribs.bInheritHandle = TRUE;
    if (!CreatePipe(&hrd,       /* address of variable for read handle */
                    &hwr,       /* address of variable for write handle */
                    &attribs,   /* Security sillyness */
                    0)) return NULL;
    hproc = GetCurrentProcess();
    ret = DuplicateHandle(hproc, hwr,          /* Source */
                          hproc, &hwr2,        /* Target */
                          0,                   /* Desired access */
                          TRUE,                /* Inherit */
                          DUPLICATE_SAME_ACCESS);
    if (!ret)
    {   CloseHandle(hrd);
        CloseHandle(hwr);
        return NULL;
    }
        
    hproc = osRunRedirect(cmd, FALSE, NULL, hwr, hwr2);
    CloseHandle(hwr);
    CloseHandle(hwr2);
    if (hproc == NULL) return NULL;
    *proc = hproc;
    return hrd;
}

static HANDLE osRunRedirect(char *cmd, BOOL wait, HANDLE hin, 
                            HANDLE hout, HANDLE herr)
{
    static STARTUPINFO si;
    static PROCESS_INFORMATION pi;
    BOOL rc;

    rc = TRUE;

    if (!rc) term_printf("Ooops!");
    /* Create process. */
    memset(&si, 0, sizeof(si));
    si.cb                = sizeof(si);
    si.lpReserved        = NULL;
    si.lpDesktop         = NULL;
    si.lpTitle           = "!";
    si.cbReserved2       = 0;
    si.lpReserved2       = NULL;
    si.dwFlags           = STARTF_USESTDHANDLES;
    si.hStdInput         = hin;
    si.hStdOutput        = hout;
    si.hStdError         = herr;

    rc = CreateProcess(NULL, cmd, NULL, NULL, 
                       TRUE,
                       DETACHED_PROCESS/*|NORMAL_PRIORITY_CLASS*/,
                       NULL, NULL, 
                       &si, &pi);
    if (rc == FALSE) return NULL;

    if (wait == TRUE)
    {   rc = WaitForSingleObject(pi.hProcess, INFINITE);
        if (rc == WAIT_FAILED) return NULL;
    }

    return pi.hProcess;
}

/* Expand any environment variables inside s */
static char *osFindEnvVar(char *s, int idx, int *new_idx)
{
    char *var, c;
    int j=idx;
    while (isalnum(s[j])) j++;
    if (j==idx) return NULL;
    c = s[j];
    s[j] = '\0';

    *new_idx = j;
    var = getenv(s+idx);
    s[j] = c;
    return var;
}

static char *osMangleCommand(char *s)
{
    char *var, *res;
    int  i, j, o, len;

    len = strlen(s);

    o = i = 0; res = s; 
    while (s[i] != '\0') 
    {   int new_i;
        if (s[i] == '$' && (var = osFindEnvVar(s, i+1, &new_i)) != NULL)
        {   /* Get new space */
            int lv = strlen(var);
            char *nres = (char *) malloc(o + lv + len - i + 1);
            for (j=0; j<o; j++) nres[j] = res[j];
            if (res != s) free(res);
            res = nres;
            for (j=0; j<lv; j++) res[o++] = var[j];
            i = new_i;
        } 
        else if (res != s) res[o++] = s[i++];
        else
        {   i++;
            o++;
        }
    }
    if (res != s) res[o] = s[i];

    return res;
}

HWND gnuplot_handle = 0;

BOOL CALLBACK find_text(HWND h, LPARAM x)
{
    char buffer[24];
    CSL_IGNORE(x);
    GetClassName(h, buffer, 20);
    if (strcmp(buffer, "wgnuplot_text") != 0) return TRUE;
    gnuplot_handle = h;
    return FALSE;
}

FILE *my_popen(char *command, char *direction)
{
/*
 * Here I take a pretty shameless direction and spot the special case of
 * opening an output pipe to gnuplot... and hook in a behind-the-scenes
 * way.
 */
    int i = 0, j;
    for (;;)
    {   char *name = "gnuplot";
        j = i;
        while (*name && tolower(command[j++]) == *name) name++;
        if (*name == 0)
        {   HWND parent = 0;
#ifdef OLD_API
            if (WinExec(command, SW_SHOWMINIMIZED) <= 32) return 0;
#else
/*
 * Win32 would rather I used the following long-winded version, which provides
 * a pile of generality that is irrelevant here!
 */
            STARTUPINFO startup;
            PROCESS_INFORMATION process;
            clock_t t0, t1;
            memset(&startup, 0, sizeof(STARTUPINFO));
            startup.cb = sizeof(startup);
            startup.lpReserved = NULL;
            startup.lpDesktop = NULL;
            startup.lpTitle = NULL;
            startup.dwFlags = STARTF_USESHOWWINDOW;
            startup.wShowWindow = SW_SHOWMINIMIZED;
            startup.cbReserved2 = 0;
            startup.lpReserved2 = NULL;
            if (!CreateProcess(NULL, command, NULL, NULL, FALSE,
                               0, NULL, NULL, &startup, &process)) return 0;
#endif
            gnuplot_handle = 0;
            t0 = clock();
            for (i=0; i<25; i++)  /* Give it 5 seconds to appear */
            {   parent = FindWindow((LPSTR)"wgnuplot_parent",
                                    (LPSTR)"gnuplot");
                if (parent != 0) break;
                t0 += CLOCKS_PER_SEC/5;
                while ((t1 = clock()) < t0) cwin_poll_window_manager(0);
                t0 = t1;
            }
            if (parent != 0)
            {   for (i=0; i<10; i++)   /* 2 more seconds for the child */
                {   EnumChildWindows(parent, find_text, 0);
                    if (gnuplot_handle != 0) break;
                    t0 += CLOCKS_PER_SEC/5;
                    while ((t1 = clock()) < t0) cwin_poll_window_manager(0);
                    t0 = t1;
                }
            }
            return (FILE *)-1;  // special handle for the gnuplot pipe
        }
        i++;
        if (command[i] == 0) break;
    }
#ifdef __WATCOMC__
    CSL_IGNORE(command); CSL_IGNORE(direction);
    return 0;
#else
    return _popen(command, direction);    
#endif
}

int my_pipe_putc(int c, FILE *f)
{
    if (f == (FILE *)(-1))
    {   if (gnuplot_handle == 0) return EOF;
        if (c == '\n') c = '\r';
        SendMessage(gnuplot_handle, WM_CHAR, c, 1L);
        return c;
    }
    else return putc(c, f);
}

int my_pipe_flush(FILE *f)
{
    if (f != (FILE *)(-1)) return fflush(f);
    return 0;
}

void my_pclose(FILE *stream)
{
    if (stream == (FILE *)(-1))
    {   SendMessage(gnuplot_handle, WM_CHAR, 'q', 1L);
        SendMessage(gnuplot_handle, WM_CHAR, 'u', 1L);
        SendMessage(gnuplot_handle, WM_CHAR, 'i', 1L);
        SendMessage(gnuplot_handle, WM_CHAR, 't', 1L);
        SendMessage(gnuplot_handle, WM_CHAR, '\r', 1L);
        return;
    }
#ifdef __WATCOMC__
    CSL_IGNORE(stream);
#else
    _pclose(stream);
#endif
}


char *my_getenv(char *s)
{
/*
 * Case fold for MSDOS
 */
    char uppercase[LONGEST_LEGAL_FILENAME];
    int c;
    char *p = uppercase;
    while ((c = *s++) != 0) *p++ = (char)toupper(c);
    *p = 0;
    s = uppercase;
    return getenv(s);
}

/*
 * MSDOS does not support the idea of home directories for
 * users, so in case anybody still wants to use the notation "~" that
 * would indicate a home directory under Unix I implement something
 * in terms of environment variables.
 */

int get_home_directory(char *b, int len)
{
/*
 * Worry about "len" here...
 */
    char *w = my_getenv("home");
    if (w != NULL) strcpy(b, w);
    else strcpy(b, ".");
    return strlen(b);
}

int get_users_home_directory(char *b, int len)
{
/*
 * Worry about "len" here...
 */
    char *w, h[LONGEST_LEGAL_FILENAME];
    sprintf(h, "home$%s", b);
    w = my_getenv(h);
    if (w != NULL) strcpy(b, w);
    else strcpy(b, ".");
    return strlen(b);
}

/*
 * The next bit of mess is jolly - I just want to see if stdin has been
 * redirected to come from a file, i.e. whether I am interactive in some
 * sense.  This may be used to decide what to do about error reports etc.
 * The IDEA seems generic across most systems, but the details vary in
 * frustrating ways.
 */


int batchp(void)
{
    return 0;   /* !isatty(stdin->_file); */
}

/*
 * The next procedure is responsible for establishing information about
 * where the main checkpoint image should be recovered from, and where
 * and fasl files should come from.
 */
char *find_image_directory(int argc, char *argv[])
{
    char *w;
    int i = strlen(cwin_full_program_name), j;
    strcpy(program_name, programName);
    CSL_IGNORE(argc); CSL_IGNORE(argv);
/*
 * If the current program is called c:\aaa\xxx.exe, then I look
 * for a file c:\aaa\xxx.img
 */
    w = (char *)(*malloc_hook)(i+1);
    if (w == NULL)
    {   fprintf(stderr, "\n+++ Panic - run out of space\n");
        my_exit(EXIT_FAILURE);
    }
    strcpy(w, cwin_full_program_name);
/*
 * Windows file-names are not case-sensitive when you search, but
 * I think names look neater in lower case so I force things here.
 */
    for (j=i-4; j>=0; j--)
    {   if (w[j] == '\\' || w[j] == '/') break;
        w[j] = (char)tolower(w[j]);
    }
    w[i-3] = 'i';
    w[i-2] = 'm';
    w[i-1] = 'g';
    return w;
}

CSLbool sigint_must_longjmp = NO;
jmp_buf sigint_buf;

void MS_CDECL sigint_handler(int code)
{
    CSL_IGNORE(code);
    interrupt_pending = 1;
    if (sigint_must_longjmp)
    {   sigint_must_longjmp = 0;
        longjmp(sigint_buf, 1);
    }
    return;
}

/*
 * The following function controls memory allocation policy
 */

int32 ok_to_grab_memory(int32 current)
{
    MEMORYSTATUS w;
    memset(&w, 0, sizeof(w));
    w.dwLength = sizeof(w);
    GlobalMemoryStatus(&w);
#ifdef NOT_CONFIDENT_ENOUGH_HIDE_THIS_TRASH
    term_printf("memStats %d %d %d %d %d %d %d %d\n",
       w.dwLength, 
       w.dwMemoryLoad, 
       w.dwTotalPhys,     w.dwAvailPhys,
       w.dwTotalPageFile, w.dwAvailPageFile, 
       w.dwTotalVirtual,  w.dwAvailVirtual);
#endif
    if (w.dwTotalPhys == 0)
    {
#ifdef COMMON
        return current;
#else
        return 3*current + 2;
#endif
    }
    else
/*
 * Here we seem to be running under win32 (not win32s) and can measure how
 * much memory is currently available for this process. I will grab space
 * until win32 is using all the physical memory there is. After that I will
 * tend to grow much more cautiously.
 */
    {   int32 freemem = w.dwAvailPhys / CSL_PAGE_SIZE;
        if (freemem == 0) return current / 2 + 1;
        if (freemem > 3*current + 2) freemem = 3*current + 2;
        return freemem;
    }
}

/* end of syscwin.c */

