/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * ShapeTools/shape program - mfiles.c
 *   implements memory file. A memory file is completely
 *   read into a buffer. All operations work on that buffer.
 *
 * by Juergen.Nickelsen@cs.tu-berlin.de
 *
 * $Header: mfiles.c[8.0] Tue Jul  6 15:46:00 1993 axel@cs.tu-berlin.de frozen $
 */
#ifndef lint
static char *AtFSid = "$Header: mfiles.c[8.0] Tue Jul  6 15:46:00 1993 axel@cs.tu-berlin.de frozen $";
#endif


#include "shape.h"
#include "mfiles.h"
#include "parser.h"

/* list of all MFILE structures */
static MFILE *mf_structlist = NULL ;

/* internal functions */
static void mf_do_read A((MFILE *mf, int fd)) ;
static void mf_split_lines A((MFILE *mf)) ;
static MFILE *mf_struct_alloc A((char *fname)) ;
static int mf_open A((char *fname, FILE **fpp)) ;
static void mf_readin_special A((MFILE *mf, int handle)) ;
static MFILE *mf_readin_nosplit A((char *fname)) ;

static MFILE *mf_struct_alloc(fname)
char *fname;
{
    MFILE *mf ;

    mf = (MFILE *) check_malloc(sizeof(MFILE)) ;
    mf->mf_name = check_strdup(fname) ;
    mf->mf_buffer = NULL ;
    mf->mf_lbeg = NULL ;
    mf->mf_lines = 0 ;
    mf->mf_curline = -1 ;
    mf->mf_end = NULL ;
    mf->mf_next = mf_structlist ;
    mf_structlist = mf ;

    return mf ;
}

/* Kill an MFILE buffer pointed to by mf. Releases all memory
   associated with this buffer. */
void mf_kilbuf(mf)
MFILE *mf;
{
    MFILE *tmp ;
    
    if (mf == mf_structlist) {
	mf_structlist = mf->mf_next ;
    } else {
	for (tmp = mf_structlist; tmp->mf_next != NULL; tmp = tmp->mf_next) {
	    if (tmp->mf_next == mf) {
		tmp->mf_next = mf->mf_next ;
	    }
	}
	if (tmp->mf_next == NULL) {
#ifdef DEBUG
	    fatal("freeing spurious buffer", mf->mf_name) ;
#endif
	    return ;		/* not found in our list */
	}
    }
    free(mf->mf_name) ;
    free(mf->mf_buffer) ;
    free(mf->mf_lbeg) ;
    free(mf) ;
}

/* Kill all MFILE buffers. */
void mf_killall()
{
    MFILE *tmp, *next ;

    for (tmp = mf_structlist, next = mf_structlist->mf_next;
	 tmp != NULL;
	 tmp = tmp->mf_next, next = next->mf_next) {

	free(tmp->mf_name) ;
	free(tmp->mf_buffer) ;
	free(tmp->mf_lbeg) ;
	free(tmp) ;
    }
}
	
/* Read standard input into MFILE buffer */
MFILE *mf_readin_stdin()
{
    MFILE *mf ;

    mf = mf_struct_alloc("Standard Input") ;
    mf_readin_special(mf, 0) ;
    mf_split_lines(mf) ;
    
    return mf ;
}

static void mf_readin_special(mf, handle)
MFILE *mf ;
int handle ;
{
    unsigned long buffer_size ;	/* current size of buffer */
    unsigned long increment ;	/*  */
    unsigned long read_ret ;	/* return value of read(2) */
    unsigned long total ;	/* no of bytes read in, next read()
				   goes to this offset */

    buffer_size = INITIAL_BUFFER_SIZE ;
    increment = INITIAL_BUFFER_SIZE ;
    mf->mf_buffer = check_malloc(INITIAL_BUFFER_SIZE) ;
    
    total = 0 ;
    do {
	if ((read_ret = read(handle, mf->mf_buffer + total,
			     buffer_size - total)) == -1) {
	    fatal_perror(mf->mf_name) ;
	}
	total += read_ret ;
	if (total > 3 * buffer_size / 4) {
	    increment = increment * 4 / 3 ;
	    mf->mf_buffer = check_realloc(mf->mf_buffer,
					  buffer_size + increment) ;
	    buffer_size += increment ;
	}
    }  while (read_ret > 0);
    mf->mf_buffer = check_realloc(mf->mf_buffer, total + 1) ;

    mf->mf_end = mf->mf_buffer + total ;
    *(mf->mf_end) = '\0' ;
}

    

/* Read file fname into an MFILE buffer */
MFILE *mf_readin(fname)
char *fname;
{
    MFILE *mf ;			/* struct holding buffer information */

    mf = mf_readin_nosplit(fname) ;
    if (mf != NULL) {
	mf_split_lines(mf) ;
    }

    return mf ;
}

/* Read file fname into an MFILE buffer without splitting into lines */
static MFILE *mf_readin_nosplit(fname)
char *fname;
{
    MFILE *mf ;			/* struct holding buffer information */
    unsigned long fsize ;	/* size of the file */
    int fd ;			/* file descriptor to read from */
    struct stat statbuf ;
    FILE *in ;			/* *this* must be closed instead of fd */

    if ((fd = mf_open(fname, &in)) == -1) {
	return NULL ;
    } else {
	if (fstat(fd, &statbuf) == -1) {
	    fatal_perror(fname) ;
	}
	mf = mf_struct_alloc(fname) ;
	if (S_ISREG(statbuf.st_mode)) {
	  fsize = statbuf.st_size ;
	  mf->mf_buffer = check_malloc(fsize + 1) ;
	  mf->mf_end = mf->mf_buffer + fsize ;
	  *(mf->mf_end) = '\0' ;
	  mf_do_read(mf, fd) ;
	} else {
	  /* if it is not a regular file, we don't know the size */
	  mf_readin_special(mf, fd) ;
	}
	fclose(in) ;
    }

    return mf ;
}

/* Open file fname, return file descriptor. Pointer to FILE structure
   is written into place filepp points to. */
static int mf_open(fname, filepp)
FILE **filepp ;
char *fname ;
{
    Af_key *key ;		/* key for af_open() */

    if (!(key = atBindVersion (fname, "")))
      return -1;
    
    if ((*filepp = af_open(key, "r")) == NULL) {
	/* This must be an error because AtFS promised it could
	 * open the file by giving us a key */
	af_perror(fname) ;
	fatal("AtFS error", NULL) ;
    }

    return fileno(*filepp) ;
}

/* Read the contents of the file fd is a descriptor to into buffer mf.
   Space for the file contents is already allocated. */
static void mf_do_read(mf, fd)
MFILE *mf;
int fd ;
{
    unsigned long left ;	/* bytes left to read */
    unsigned long read_ret ;	/* return value of read(2) */
    char *next_here ;		/* next read(2) goes here */

    left = mf->mf_end - mf->mf_buffer ;
    next_here = mf->mf_buffer ;
    while (left > 0) {
	if ((read_ret = read(fd, next_here, left)) == -1) {
	    fatal_perror(mf->mf_name) ;
	}
	left -= read_ret ;
	next_here += read_ret ;
    }
}

/* Split the file buffer mf into lines. Newlines are replaced by null bytes,
   backslash/newline sequences are replaced by two blanks. Pointers to the
   original line beginnings are written into mf->mf_lbeg[].
   mf->mf_lbeg[mf->mf_lines] points to the first character past the last
   line, i.e. mf->mf_end.
   The size of this array is written into mf->mf_lines. */

static void mf_split_lines(mf)
MFILE *mf;
{
    /* for the sake of efficiency backslash-newline pairs make
     * continuation lines even inside comments, i.e. *always* */

    unsigned long enoli ;	/* estimated number of lines */
    unsigned long lines	;	/* actual number of lines */
    char *bufptr ;		/* pointer into buffer */
    char *oldbufptr ;		/* copy of bufptr */
    
#define CHARS_PER_LINE 15

    enoli = (mf->mf_end - mf->mf_buffer) / CHARS_PER_LINE + 1 ;
				/* perhaps... */
    
    mf->mf_lbeg = (char **) check_malloc(enoli * sizeof(char *)) ;

    bufptr = mf->mf_buffer ;
    lines = 0 ;

    while (bufptr < mf->mf_end) { /* bufptr gets incremented in the switch */
	mf->mf_lbeg[lines++] = bufptr ;	/* get beg. of current line */
	if (lines >= enoli) {
	    /* extend array of pointers */
	    enoli *= 2 ;
	    mf->mf_lbeg =
		(char **) check_realloc((char *) mf->mf_lbeg,
					enoli * sizeof(char *)) ;
	}
	oldbufptr = bufptr ;
	if ((bufptr = strchr(bufptr, '\n')) == NULL) {
	    bufptr = oldbufptr ;
	    break ;		/* no newline to end of buffer */
	}
	if (*(bufptr - 1) == '\\') {
				/* continuation line */
	    *(bufptr - 1) = ' ' ;
	    *bufptr++ = ' ' ;
	} else {
	    *bufptr++ = '\0' ;
	}
    }
    if (bufptr + strlen(bufptr) != mf->mf_end) {
	fatal("Nullbyte in file", mf->mf_name) ;
    }
    mf->mf_lbeg = (char **) check_realloc((char *) mf->mf_lbeg,
					  (lines + 1) * sizeof(char *)) ;
    mf->mf_lbeg[lines] = mf->mf_end ;
    mf->mf_lines = lines ;
}


/* The current line is set to the next line. A pointer to the beginning
   of this line is returned. */
char *mf_nextline(mf)
MFILE *mf;
{
    int line = mf->mf_curline ;
    
    if (line < 0) {
	/* if current position is before the beginning of the file,
	 * go to the first line */
	line = 0 ;
    } else {
	do {
	    if (++line >= mf->mf_lines) {
		/* end of file reached */
		return NULL ;
	    }
	    /* beginning of a real line (not original)
	     * is only after a null byte */
	} while (*(mf->mf_lbeg[line] - 1) != '\0') ;
    }
    mf->mf_curline = line ;
    return mf->mf_lbeg[line] ;
}


/* The current line is set to the previous line. A pointer to the beginning
   of this line is returned. */
char *mf_prevline(mf)
MFILE *mf;
{
    int line = mf->mf_curline ;

    if (--line < 0) {
	/* kind of "virtual" position before the beginning */
	mf->mf_curline = -1 ;
    } else {
	/* look for the beginning of a "real" line. A real line
	 * beginning if found if it is the beginning of the buffer
	 * or otherwise has a null byte in the previous position. */
	while (line > 0 &&
	       *(mf->mf_lbeg[line] - 1) != '\0') {
	    line-- ;
	}
	mf->mf_curline = line ;
    }
    return line >= 0 ? mf->mf_lbeg[line] : NULL ;
}


/* Return a pointer to the specified line in mf. */
char *mf_thisline(mf, lineno)
MFILE *mf;
int lineno ;
{
    return mf->mf_lbeg[lineno - 1] ;
}

/* The file name and line number ptr points into are written into
   *pfilename and *plineno. If ptr does not point into an MFILE
   buffer, 0 is returned, 1 on success. */
int mf_locate(ptr, pfilename, plineno)
char *ptr ;
char **pfilename ;
int *plineno ;
{
    MFILE *mf ;
    int lineno ;

    /* go through mf_structlist and try to locate ptr */
    for (mf = mf_structlist; mf != NULL; mf = mf->mf_next) {
	if ((lineno = mf_locate_in(mf, ptr)) > 0) {
	    *plineno = lineno ;
	    if (pfilename != NULL) {
		*pfilename = mf->mf_name ;
	    }
 	    return 1 ;
	}
    }
    return 0 ;
}

/* The line number ptr points into in the buffer of mf is returned.
   If ptr does not point into the buffer of mf, -1 is returned. */
int mf_locate_in(mf, ptr)
MFILE *mf ;
char *ptr ;
{
    unsigned long here ;
    unsigned long upper ;
    unsigned long lower ;

    /* check bounds first */
    if (ptr >= mf->mf_buffer && ptr <= mf->mf_end) {
	upper = mf->mf_lines - 1 ;
	lower = 0 ;

	/* kind of binary search */
	while (lower <= upper) {
	    here = (lower + upper) / 2 ;
	    if (ptr > mf->mf_lbeg[here]) {
		if (ptr < mf->mf_lbeg[here + 1]) { /* in THIS line */
		    return here + 1 ;
		} else {
		    lower = here + 1 ;
		}
	    } else if (ptr < mf->mf_lbeg[here]) {
		upper = here ;
	    } else {
		return here + 1 ;
	    }
	}
    }
    return -1 ;
}

/* set the current line of mf to the line ptr points to. If ptr does
   not point into the buffer of mf, a fatal error is raised. */
void mf_setline(mf, ptr)
MFILE *mf ;
char *ptr ;
{
    int lineno ;		/* number of line ptr points to */

    lineno = mf_locate_in(mf, ptr) ;
    if (lineno < 0) {
	fatal("ptr not in mf", mf->mf_name) ;
    }
    mf->mf_curline = lineno - 1 ;
				/* mf_locate_in() returns user level
				   line numbers, not an index in mf_lbeg */
}

/*EOF*/
