/* sysipaq.c                      Copyright (C) 1989-2007 Codemist Ltd */

/*
 * System specific code. My objective is that this will subsume and replace
 * all the other files that I have or had called sysxxx.c, sysyyy.c etc.
 *
 * System-specific code for use with the "fwin" window interface code.
 * This is expected to be buildable on Windows via mingw32, on
 * both 32 and 64-bit variants of Linux, and (I hope) on Macintosh
 * system X (at least if an X server is made available to it). I thus
 * at least hope that I can use it as a generic uniform body of code.
 * The system will also build as a terminal-mode program as well as
 * a windowed one.
 *
 * I will use "autoconf" and all that stuff with a view to making
 * this code build on a wide range of systems via the usual
 *    ./configure ; make
 * sequence. This (obviously) involves a Unix-like build environment
 * but mingw/msys provides that for Windows.
 */

/*
 * 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: 018c0c8a 18-Jan-2007 */

#include "headers.h"

/*
 * WIN32               all Windows platforms that I support
 * <else>              Unix-like
 *
 *    popen(cmd, dir)  vs   _popen(cms, dir)
 *    pclose(stream)   vs   _pclose(stream)
 *    fileno(file)     vs   _fileno(file)
 *    struct stat      vs   struct _stat
 *    stat             vs   _stat
 *    ftruncate(file)  vs   chsize(file)
 *    S_IFMT __S_IFMT       to go with stat
 *    S_IFDIR __S_IFDIR
 *    DO_NOT_USE_GETUID     is getuid available
 *    UNIX_TIMES            how can I read the clock
 *    UTIME_TIME_T          struct utimbuf
 */



#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif

#ifdef WIN32
#include <windows.h>
#endif

/*
 * Jollies re GC statistics...
 */
 
static char time_string[32], space_string[32];

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

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

void flush_screen()
{
    fwin_ensure_screen();
}

void pause_for_user()
{
}

int wimpget(char *buf)
{
    int c, n=0;
    ensure_screen();
    while (n < 255)
    {   c = ce_readch();
        if (c == (0x1f & 'C') ||           /* ^C - quiet : quit      */
            c == (0x1f & 'G')) return 0;   /* ^G - noisy : interrupt */
        if (c == EOF) c = 0x1f & 'D';
        buf[n++] = (char)c;
        if (c == '\n' || c == (0x1f & 'D')) break;
    };
    return n;
}

FILE *my_popen(char *command, char *direction)
{
    return NULL;
}

int my_pipe_putc(int c, FILE *f)
{
    return putc(c, f);
}

int my_pipe_flush(FILE *f)
{
    return fflush(f);
}

void my_pclose(FILE *stream)
{
}


/*
 * Map file-names to expand references to shell variables etc.
 * and to provide portability of names across operating systems.
 */


char *look_in_lisp_variable(char *o, int prefix)
{
    Lisp_Object nil, var;
/*
 * I will start by tagging a '$' (or whatever) on in front of the
 * parameter name.
 */
    o[0] = (char)prefix;
    var = make_undefined_symbol(o);
    nil = C_nil;
/*
 * make_undefined_symbol() could fail either if we had utterly run out
 * of memory or if somebody generated an interrupt (eg ^C) around now. Ugh.
 */
    if (exception_pending())
    {   flip_exception();
        return NULL;
    }
/*
 * If the variable $name was undefined then I use an empty replacement
 * text for it. Otherwise I need to look harder at its value.
 */
    if (qvalue(var) == unset_var) return o;
    else
    {   Header h;
        intptr_t len;
        var = qvalue(var);
/*
 * Mostly I expect that the value will be a string or symbol.
 */
#ifdef COMMON
        if (complex_stringp(var))
        {   var = simplify_string(var);
            nil = C_nil;
            if (exception_pending())
            {   flip_exception();
                return NULL;
            }
        }
#endif /* COMMON */
        if (symbolp(var))
        {   var = get_pname(var);
            nil = C_nil;
            if (exception_pending())
            {   flip_exception();
                return NULL;
            }
            h = vechdr(var);
        }
        else if (!is_vector(var) ||
                 type_of_header(h = vechdr(var)) != TYPE_STRING)
            return NULL;
        len = length_of_header(h) - CELL;
/*
 * Copy the characters from the string or from the name of the variable
 * into the file-name buffer. There could at present be a crash here
 * if the expansion was very very long and overflowed my buffer. Tough
 * luck for now - people doing that (maybe) get what they (maybe) deserve.
 */
        memcpy(o, (char *)var + (CELL - TAG_VECTOR), (size_t)len);
        o = o + len;
        return o;
    }
}


#ifdef UNIX_TIMES
/*
 * This is a BSD-style clock facility, possibly giving a resolution of
 * only 1/100 second.  I believe that Portable Standard Lisp typically
 * reports user time, which is why I do this.  A further nasty here
 * is that I am probably compiling this file in ANSI mode, and on
 * at least some computers this makes #include <sys/times.h> fairly
 * ineffective (ugh), so I declare all the structures and functions I
 * want directly (ugh ugh) and hope they are as needed.  Consider this
 * when you port to a new machine.
 */

clock_t read_clock(void)
{
    struct my_tms {
        clock_t tms_utime;
        clock_t tms_stime;
        clock_t tms_cutime;
        clock_t tms_cstime;
        } tmsbuf;
    clock_t w1, w2, w3;
    extern void times(/*struct my_tms * */);
    times(&tmsbuf);
    w1 = tmsbuf.tms_utime;   /* User time in UNIX_TIMES ticks */
    w2 = CLOCKS_PER_SEC;
    w3 = UNIX_TIMES;
    return (clock_t)((double)w1 * ((double)w2/(double)w3));
}

#else

clock_t read_clock()
{
    return clock();
}

#endif

int batchp()
{
    return !isatty(fileno(stdin));
}

/*
 * 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[])
{
    int n = strlen(programName) + strlen(programDir) + 6;
    char *w = (char *)(*malloc_hook)(n);
    strcpy(w, programDir);
    n = strlen(programDir);
    w[n] = '/';                 /* Should be '\\' for Windows? */
    strcpy(&w[n+1], programName);
    n += strlen(programName) + 1;
    strcpy(&w[n], ".img");
    return w;
}

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

int32_t ok_to_grab_memory(int32_t current)
{
#ifdef COMMON
    return current;
#else
    return 3*current + 2;
#endif
}

time_t MS_CDECL time(time_t *x)
{
    time_t r = 0;
    if (x != NULL) *x = r;
    return r;
}

clock_t MS_CDECL clock()
{
    return 0;
}

const char *fwin_full_program_name = (const char *)"./csl.exe";
const char *programName            = (const char *)"csl.exe";
const char *programDir             = (const char *)".";

void fwin_restore()
{
}

void fwin_putchar(int c)
{
/*
 * Despite using termed during keyboard input I will just use the
 * ordinary C stream functions for normal output. Provided I do an
 * fflush(stdout) before requesting input I should be OK.
 */
    char s[2];
    s[0] = c;
    s[1] = 0;
    ce_print(s);
}

void fwin_puts(const char *s)
{
    ce_print(s);
}


void MS_CDECL fwin_printf(const char *fmt, ...)
{
    va_list a;
    char s[200];
    va_start(a, fmt);
    vsprintf(s, fmt, a);
    va_end(a);
    ce_print(s);
}

void fwin_vfprintf(const char *fmt, va_list a)
{
    char s[200];
    vsprintf(s, fmt, a);
    ce_print(s);
}

void fwin_ensure_screen()
{
}

void fwin_report_left(const char *s)
{
}

void fwin_report_mid(const char *s)
{
}

void fwin_report_right(const char *s)
{
}

void fwin_set_prompt(const char *s)
{
}

void fwin_menus(char **modules, char **switches)
{
}

void fwin_refresh_switches(char **switches, char **packages)
{
}

void fwin_set_help_file(const char *key, const char *path)
{
}

void fwin_acknowledge_tick()
{
}

int fwin_windowmode()
{
    return 0;
}

/*
 * The following four strings may be updated (but PLEASE keep within the
 * length limit) to make the display in the "ABOUT" box reflect your
 * particular application.
 */
char about_box_title[32] = "ABout Reduce";
char about_box_description[32] = "Version 3.8";
char about_box_rights_1[32] = "Copyright A C Hearn";
char about_box_rights_2[32] = "and Codemist Ltd";
char about_box_rights_3[32] = "Ipaq version March 2005";

int get_current_directory(char *s, int n)
{
    strcpy(s, ".");
    return 1;
}

int get_home_directory(char *s, int n)
{
    strcpy(s, ".");
    return 1;
}

int get_users_home_directory(char *s, int n)
{
    strcpy(s, ".");
    return 1;
}

char *ctime(const time_t *t)
{
    return "<today's date>";
}

static void process_file_name(char *filename, char *old, size_t n)
/*
 * This procedure maps filenames by expanding some environment
 * variables.  It is very thoroughly system specific, which is why it
 * is in this file.  See also LONGEST_LEGAL_FILENAME in "tags.h" for a
 * limit on the permitted size of an expanded filename.
 * The input (old) is not necessarily properly terminated as a C string,
 * so n says how many characters to inspect.  Build a converted name
 * in filename.
 * At present the expansions I allow are:
 *
 *    $xxx   (terminated by '.', '/' or '\' with at least one char x)
 *    ${xxx} (self-terminating)
 *           First check for a Lisp variable $xxx. If this is set (and is
 *           a string or a symbol) then its value is used. If not then
 *           next inspect the environment variable xxx and dump its
 *           value into the output.  If the variable is unset then a check
 *           is made for the value of a global lisp variable called @xxx,
 *           and if that exists and is a string or symbol it is used.
 *           If @xxx is undefined a null string is inserted.
 *           If one of the variables is defined but has an improper value
 *           then the whole file-translation fails.
 *           The use of two Lisp variables makes it possible to control
 *           precedence between these and shell variables.
 *
 *    ~      ) followed by '.', '/' or '\'
 *    ~xxx   )
 *           On Unix these try to find home directories using
 *           getpwuid(getuid()) for '~' and getpwnam() for ~xxx.
 *           If that fails ~ expands into nothing at all.
 *           This syntax is only recognised at the very start of a file-name.
 *           For systems other than Unix this syntax will not be useful and
 *           should be avoided, however as an experimental place-holder I
 *           may do things with environment variables called HOME etc.
 * 
 * 
 * I convert file-names of the form aaa/bbb/ccc.ddd into something
 * acceptable to the system being used, even though this may result in
 * some native file titles that include '/' characters becoming unavailable.
 * The reasoning here is that scripts and programs can then use Unix-like
 * names and non-Unix hosts will treat them forgivingly.
 *
 *
 */
{
    int i;
    int c;
    char *o;
    if (n == 0)
    {   *filename = 0;
        return;    /* deem zero-length name to be illegal */
    }
    o = filename;
    c = *old;
/*
 * First I deal with a leading "~"
 */
    if (c == '~')
    {   old++;
        n--;
        while (n != 0)
        {   c = *old;
            if (c == '.' || c == '/' || c == '\\') break;
            old++;
            n--;
            *o++ = (char)c;
        }
        *o = 0;
/*
 * actually deciding what the home directory is is passed down to a
 * system-specific call, but it is not to be relied upon especially
 * on personal computers.
 */
        if (o == filename)  /* '~' on its own */
        {   get_home_directory(filename, LONGEST_LEGAL_FILENAME);
            o = filename + strlen(filename);
        }
        else
        {   get_users_home_directory(filename, LONGEST_LEGAL_FILENAME);
            o = filename + strlen(filename);
        }
    }
/*
 * Having copies a user-name across (if there was one) I now copy the
 * rest of the file-name, expanding $xxx and ${xxx} as necessary.
 */
    while (n != 0)
    {   c = *old++;
        n--;
/*
 * If I find a "$" that is either at the end of the file-name or that is
 * immediately followed by ".", "/" or "\" then I will not use it for
 * parameter expansion. This at least gives me some help with the RISCOS
 * file-name $.abc.def where the "$" is used to indicate the root of the
 * current disc.
 */
        if (c == '$' && n != 0 &&
            (c = *old) != '.' && c != '/' && c != '\\')
        {   char *p = o;
            const char *w;
/*
 * I collect the name of the parameter at the end of my file-name buffer,
 * but will over-write it later on when I actually do the expansion.
 */
            if (c == '{')
            {   old++;
                n--;
                while (n != 0)
                {   c = *old++;
                    n--;
                    if (c == '}') break;
                    *p++ = (char)c;
                }
            }
            else
            {   while (n != 0)
                {   c = *old;
                    if (c == '.' || c == '/' || c == '\\') break;
                    old++;
                    n--;
                    *p++ = (char)c;
                }
            }
            *p = 0;
            i = strlen(o) + 2;
            while (i-- != 0) o[i] = o[i-1];
#ifdef CSL
            if ((p = look_in_lisp_variable(o, '$')) != NULL &&
                p != o) o = p;
            else 
#endif
            if ((w = my_getenv(o+1)) != NULL)    /* Shell variable? */
            {   strcpy(o, w);
                o = o + strlen(o);
            }
#ifdef CSL
            else if ((p = look_in_lisp_variable(o, '@')) != NULL)
                o = p;
#endif
            else
            {   *filename = 0;  /* return reporting failure */
                return;
            }
        }
        else *o++ = (char)c;
    }
    *o = 0;


#ifdef WIN32
/*
 * Now the filename has had $ and ~ prefix things expanded - I "just"
 * need to deal with sub-directory representation issues. Specifically I need
 * to map "/" separators into "\" so that if a user presents a file
 * name such as aaa/bbb/ccc.d it gets passed to the operating system
 * as aaa\bbb\ccc.d
 * Note that I enable this code under the heading MS_DOS but really it
 * means any file-system (eg Windows too) that uses "\" as its main
 * directory separator character.
 * As of September 2004 I will also map an intial sequence
 *         /cygdrive/x/
 * onto    x:\
 */

    if (strncmp(filename, "/cygdrive/", 10) == 0 &&
                filename[11] == '/')
    {   char *p = filename+2, *tail = filename+11;
        filename[0] = filename[10];
        filename[1] = ':';
        while (*tail != 0) *p++ = *tail++;
        *p = 0;
    }
/*
 * I map "/" characters in MSDOS filenames into "\" so that users
 * can give file names with Unix-like slashes as separators if they want.
 * People who WANT to use filenames with '/' in them will be hurt.
 */
    {   int j;
        char *tail = filename;
        while ((j = *tail) != 0)
        {   if (j == '/') *tail = '\\';
            tail++;
        }
/*
 * stat and friends do not like directories referred to as "\foo\", so check
 * for a trailing slash, being careful to respect directories with names
 * like "\" and "a:\".
 */
       j = strlen(filename);
       if (j > 0 && j != 1 && !(j == 3 && *(filename+1) == ':'))
       {
           if ( (*(tail - 1) == '\\')) *(tail - 1) = 0;
       }
    }
#endif /* WIN32 */
}

FILE *open_file(char *filename, char *old, size_t n,
                char *mode, FILE *old_file)
{
/*
 * mode is something like "r" or "w" or "rb", as needed by fopen(),
 * and old_file is NULL normally, but can be a (FILE *) to indicate
 * the use of freopen rather than fopen.
 */
    FILE *ff;
    process_file_name(filename, old, n);
ce_print("Filename given as: "); ce_print(old);
ce_print("\nConverted to: "); ce_print(filename);
ce_print("\n");
    if (*filename == 0) return NULL;
#ifdef NO_BINARY_FOPEN
/*
 * On some Unix implementations (I mean DECs version on its MIPS workstations
 * and on the microvax I tried) the library does not support "rb" and "wb"
 * modes, so I work around that here.  Explicit selection of binary file
 * access will be needed on some non-Unix operating systems, but should
 * never be relevant under Unix, hence my choice of a flag for the conditional
 * compilation here.
 */
    if (mode[0] == 'w')
    {   if (mode[1] == '+') mode = "w+";
        else mode = "w";
    }
    else if (mode[1] == '+') mode = "r+";
    else mode = "r";    /* That ought to patch it up */
#endif

    if (old_file == NULL) ff = fopen(filename, mode);
    else 
    {   fclose(old_file);
        ff = fopen(filename, mode);
    }
/*
 * In suitable cases when the first attempt to open the file fails I
 * will try creating any necessary directories and then try again.
 */
    if (ff==NULL && *mode=='w')
    {   char *p = filename;
        while (*p != 0)
        {   int ch = *p;
            if (ch == '/' || ch == '\\')
            {   *p = 0;
                // Cmkdir(filename);  CAN NOT DO THIS YET
                *p = ch;
            }
            p++;
        }
        if (old_file == NULL) ff = fopen(filename, mode);
        else
        {   fclose(old_file);
            ff = fopen(filename, mode);
        }
    }
    return ff;
}

int fwin_linelength = 67;

int fwin_pause_at_end = 0;

void abort()
{
    exit(1);
}

int isatty(int n)
{
    return 0;
}

long file_length(char *filename, char *old, size_t n)
{
    return 0;
}

int file_readable(char *filename, char *old, size_t n)
{
    FILE *ff;
    process_file_name(filename, old, n);
    ff = fopen(filename, "r");
    if (ff == NULL) return 0;
    fclose(ff);
    return 1;
}

int file_writeable(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    return 1;
}

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

char *get_truename(char *filename, char *old, size_t n)
{
    process_file_name(filename, old, n);
    return filename;
}

extern CSLbool file_exists(char *filename, char *old, size_t n, char *tt)
{
    FILE *ff;
    process_file_name(filename, old, n);
    ff = fopen(filename, "r");
    if (ff == NULL) return 0;
    fclose(ff);
    strcpy(tt, "Today");
    return 1;
}

extern char *change_directory(char *filename, char *old, size_t n)
{
    return "change_directory not implemented";
}

/*
 * The interfaces to create_directory and delete_file are similar
 * to that for open_file. They do what their names suggest! They return
 * zero on success, and non-zero on failure. Each does file-name
 * conversion so that Unix-style names can be used even with Windows.
 */

extern int create_directory(char *filename, char *old, size_t n)
{
   return 0;
}

extern int delete_file(char *filename, char *old, size_t n)
{
    return 0;
}

extern int rename_file(char *from_name, char *from_old, size_t from_size,
                       char *to_name,   char *to_old,   size_t to_size)
{
    return 0;
}

int truncate_file(FILE *f, long int where)
{
    return 0;
}

char *tmpnam()
{
    return "tempfile.tmp";
}

char *my_getenv(char *s)
{
    return NULL;
}

int my_system(char *s)
{
    return 0;
}

/*
 * list_directory_members calls the given callback function handing it
 * the name of each file in given directory.
 */

typedef void directory_callback(char *, int, long int);

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

int scan_leafstart = 0;

/* end of sysipaq.c */
