/* file intrinsics for S-Lang */
/* 

   Copyright (C) 1993 John E. Davis (davis@amy.tch.harvard.edu)
   All rights reserved.

   This file is part of S-Lang.

   S-Lang is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY.  No author or distributor accepts responsibility to
   anyone for the consequences of using it or for whether it serves any
   particular purpose or works at all, unless he says so in writing.
   Refer to the S-Lang General Public License for full details.

   Everyone is granted permission to copy, modify and redistribute
   S-Lang, but only under the conditions described in the S-Lang General
   Public License.  A copy of this license is supposed to have been given
   to you along with S-Lang so you can know your rights and
   responsibilities.  It should be in a file named COPYING.  Among other
   things, the copyright notice and this notice must be preserved on all
   copies.

*/

#include <stdio.h>
#include <string.h>
#include "slang.h"
#include "_slang.h"

#define SL_READ		0x01
#define SL_WRITE	0x02
#define SL_BINARY	0x04

typedef struct 
{
   int fd;			       /* handle */
   FILE *fp;			       /* kind of obvious */
   unsigned int flags;		       /* modes, etc... */
} SL_File_Table_Type;

#define SL_MAX_FILES 30

SL_File_Table_Type SL_File_Table[SL_MAX_FILES];

SL_File_Table_Type *get_file_table_entry(void)
{
   SL_File_Table_Type *t = SL_File_Table, *tmax;
   
   tmax = t + SL_MAX_FILES;
   while (t < tmax)
     {
	if (t->fd == -1) return t;
	t++;
     }
   
   return NULL;
}

static char *remake_string(char *s, int len)
{
   len++;			       /* for null terminator */
   if (s == NULL)
     {
	s = (char *) MALLOC(len);
     }
   else s = (char *) REALLOC(s, len);
   
   if (s == NULL) SLang_Error = SL_MALLOC_ERROR;
   return (s);
}


unsigned int file_process_flags(char *mode)
{
   char ch;
   unsigned int flags = 0;
   
   while ((ch = *mode++) != 0)
     {
	if (ch == 'r') flags |= SL_READ;
	else if (ch == 'w') flags |= SL_WRITE;
	else if (ch == '+') flags |= SL_WRITE | SL_READ;
	else if (ch == 'b') flags |= SL_BINARY;
	else return(0);
     }
   return (flags);
}

/* returns -1 upon failure or returns a handle to file */
int SLfopen(char *file, char *mode)
{
   FILE *fp;
   SL_File_Table_Type *t;
   unsigned int flags;
   
   if ((t = get_file_table_entry()) == NULL) return (-1);
   if (0 == (flags = file_process_flags(mode))) return (-1);
   
   if ((fp = fopen(file, mode)) == NULL) return (-1);
   t->fp = fp;
   t->fd = fileno(fp);
   t->flags = flags;
   return ((int) (t - SL_File_Table));
}

/* returns pointer to file entry if it is open and consistent with 
   flags.  Returns NULL otherwise */
static SL_File_Table_Type *check_handle(int *h, unsigned int flags)
{
   int n = *h;
   SL_File_Table_Type *t;
   
   if ((n < 0) || (n >= SL_MAX_FILES)) return(NULL);
   t = SL_File_Table + n;
   if (t->fd == -1) return (NULL);
   if (flags & t->flags) return (t);
   return NULL;
}

   

/* returns 0 upon failure or 1 if successful */
int SLfclose(int *handle)
{
   SL_File_Table_Type *t = check_handle(handle, 0xFFFF);
   
   if ((t != NULL) && (t->fp != NULL) && (EOF != fclose(t->fp))) 
     {
	t->fp = NULL;  t->fd = -1;
	return (1);
     }
   
   return (0);
}

/* returns number of characters read and pushes the string to the stack.  
   If it fails, it returns -1 */
int SLfgets(int *handle)
{
   char buf[256];
   char *s = NULL, *s1;
   register char *b = buf, *bmax = b + 256;
   register int ch;
   int len = 0, dlen;
   SL_File_Table_Type *t;
   register FILE *fp;
   

   if (NULL == (t = check_handle(handle, SL_READ))) return (-1);
   fp = t->fp;
   while (EOF != (ch = getc(fp)))
     {
	if (b == bmax)
	  {
	     if (NULL == (s1 = remake_string(s, len + 256)))
	       {
		  if (s != NULL) FREE(s);
		  return(-1);
	       }
	     s = s1;
	     b = buf;
	     strncpy(s + len, b, 256);
	     len += 256;
	  }
	*b++ = (char) ch;
	if (ch == '\n') break;
     }
   
   dlen = (int) (b - buf);
   if ((dlen == 0) && (s == NULL)) return(0);
   
   
   if (NULL == (s1 = remake_string(s, len + dlen)))
     {
	if (s != NULL) FREE(s);
	return(-1);
     }
   
   strncpy(s1 + len, buf, dlen);
   len += dlen;
   *(s1 + len) = 0;   /* null terminate since strncpy may not have */
   SLang_push_malloced_string(s1);
   return(len);
}

int SLfputs(char *s, int *handle)
{
   SL_File_Table_Type *t;
   

   if (NULL == (t = check_handle(handle, SL_WRITE))) return (-1);
   if (EOF == fputs(s, t->fp)) return (0);
   return (1);
}


int SLfflush(int *handle)
{
   SL_File_Table_Type *t;
   

   if (NULL == (t = check_handle(handle, SL_WRITE))) return (-1);
   if (EOF == fflush(t->fp)) return (0);
   return (1);
}

static int slfile_stdin = 0;
static int slfile_stdout = 1;
static int slfile_stderr = 2;

static SLang_Name_Type SLFiles_Name_Table[] = 
{
   MAKE_INTRINSIC(".fopen", SLfopen, INT_TYPE, 2),
   /* Usage: int fopen(String file, String mode);
      Open 'file' in 'mode' returning handle to file.
      Here mode is one of:  "r", "w", "a", "r+", "w+", "a+", "rb", ...
          where 'r' means read, 'w' means write, '+' means read and write,
	  'b' means binary, etc...
      Returns -1 if file could not be opened.  Any other value is a handle
      to the file.  This handle must be used in other file operations. */
   MAKE_INTRINSIC(".fclose", SLfclose, INT_TYPE, 1),
   /* Usage: int fclose(int handle);
      Closes file associated with 'handle'.  
      returns: 0 if file could not be closed or buffers could not be flushed.
               1 upon success.
	       -1 if handle is not associated with an open stream. */
   MAKE_INTRINSIC(".fgets", SLfgets, INT_TYPE, 1),
   /*  Usage:  [String] int fgets(int handle);
       Reads a line from the open file specified by handle.  
       Returns: -1 if handle not associated with an open file.
                 0 if at end of file.
		 Otherwise, returns the number of characters read.  The 
		   second value on the stack is the string of characters.
		   
	  Example:
	    variable buf, fp, n;
	    fp = fopen("myfile", "r");
	    if (fp < 0) error("File not open!");
	    n = fgets(fp);
	    if (n == 0) print("End of File!"); 
	    else if (n == -1) print("fgets failed!");
	    else buf = ();  % characters left on the stack.
	    */
	     
   MAKE_INTRINSIC(".fflush", SLfflush, INT_TYPE, 1),
   /* Usage: int fflush(int handle);
      flushes an output stream.
      returns: -1 if handle is not associated with an open stream
                0 if fflush fails (lack of disk space, etc...)
		1 success */
    
   MAKE_INTRINSIC(".fputs", SLfputs, INT_TYPE, 2),
   /* Usage: int fputs(string buf, int handle);
      Writes a null terminated string (buf) to the open output stream (handle).
      returns: -1 if handle is not associated with an open output stream.
                1 indicates success. */
   MAKE_VARIABLE(".stdin", &slfile_stdin, INT_TYPE, 1),
   /* Handle to stdin */
   MAKE_VARIABLE(".stdout", &slfile_stdout, INT_TYPE, 1),
   /* Handle to stdout */
   MAKE_VARIABLE(".stderr", &slfile_stderr, INT_TYPE, 1),
   /* Handle to stderr */
   SLANG_END_TABLE
};


int init_SLfiles()
{
   int i;
   SL_File_Table_Type *s = SL_File_Table;
   
   for (i = 3; i < SL_MAX_FILES; i++) s[i].fd = -1;
   
   s->fd = fileno(stdin);  s->flags = SL_READ; s->fp = stdin; s++;
   s->fd = fileno(stdout); s->flags = SL_WRITE; s->fp = stdout; s++;
   s->fd = fileno(stderr); s->flags = SL_READ | SL_WRITE; s->fp = stderr;
   
   return SLang_add_table(SLFiles_Name_Table, "_Files");
}

   
   
