/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/

#include "jove.h"

RCS("$Id: fp.c,v 14.32.0.3 1993/07/23 03:20:59 tom Exp tom $")

#include "io.h"

#define MAXFILES	20	/* good enough for my purposes */

private File _openfiles[MAXFILES] ZERO;

/* this deals with screen output before the terminal buffer is setup. */

private char one_buf;
private File _stdout = {1, 1, 1, F_WRITE|F_LOCKED|F_BINARY, &one_buf, &one_buf};
public File *stdout = &_stdout;


File *
fd_open(name, flags, fd, buffer, buf_size)
const char	*name;
register int	flags;
char		*buffer;
int		buf_size;
{
	register File	*fp;
	register char	*bp;

	for (fp = _openfiles; fp->f_flags != 0; fp++)
		if (fp == &_openfiles[MAXFILES-1])
			complain("[Too many open files!]");
	fp->f_bufsize = buf_size;
	fp->f_cnt = 0;
	fp->f_fd = fd;
	if ((bp = buffer) == NULL) {
		bp = emalloc(buf_size);
		flags |= F_MYBUF;
	}
	fp->f_flags = flags;
	fp->f_base = fp->f_ptr = bp;
	fp->f_name = copystr(name);

	return fp;
}

File *
f_open(name, flags, buffer, buf_size)
const char	*name;
char		*buffer;
{
	register int	fd;
	register const char	*fname = rel_name(name);

	switch (F_MODE(flags)) {
	default:
		fd = -1;
		break;
	case F_READ:
		fd = open(fname, O_RDONLY);
		break;
	case F_APPEND:
		if ((fd = open(fname, O_WRONLY)) >= 0) {
			lseek(fd, (off_t) 0, SEEK_END);
			break;
		}
		/* fall through... */
	case F_WRITE:
		fd = creat(fname, CreatMode);
		break;
	}
	if (fd < 0)
		return NULL;
	return fd_open(name, flags, fd, buffer, buf_size);
}

void
f_close(fp)
register File	*fp;
{
	flush(fp);
#ifdef BSD4_2
	if (fp->f_flags & (F_WRITE|F_APPEND))
		fsync(fp->f_fd);
#endif
	close(fp->f_fd);
	if (fp->f_flags & F_MYBUF)
		free(fp->f_base);
	free(fp->f_name);
	fp->f_flags = 0;	/* indicates that we're available */
}

void
gc_openfiles()
{
	register File	*fp = _openfiles;
	register int	flags;

	do {
		if ((flags = fp->f_flags) != 0 && (flags & F_LOCKED) == 0)
			f_close(fp);
	} while (++fp < &_openfiles[MAXFILES]);
}

filbuf(fp)
register File	*fp;
{
	if (fp->f_flags & (F_EOF|F_ERR))
		return EOF;
    {
	register int	n;
#ifdef CRLF
retry:
#endif
	n = read(fp->f_fd, fp->f_ptr = fp->f_base, fp->f_bufsize);

	if ((fp->f_cnt = n) <= 0) {
		register int	eflag = F_EOF;

		if (n < 0) {
			add_mess("[Read error %s]", syserr());
			eflag = F_ERR;
		}
		fp->f_flags |= eflag;

		return EOF;
	}
	io_chars += n;
    }
#if F_BINARY

	if (!(fp->f_flags & F_BINARY)) {

#   ifdef CRLF	/* collapse <cr><nl> pairs to <nl> */

		register char	*sp = fp->f_base,
				*dp = sp,
				*ep = sp + fp->f_cnt;
		int		delayed_cr = 0;

		if (fp->f_flags & F_PEND_CR) {	/* do we have a pending CR? */
			fp->f_flags &= ~F_PEND_CR;
			if (*sp != LF)
				delayed_cr++;
		}
		do {
			if ((*dp++ = *sp++) == CR) {
				if (sp == ep) {
					fp->f_flags |= F_PEND_CR;
					--dp;
					break;
				}
				if (*sp == LF)
					--dp;
			}
		} while (sp < ep);
		fp->f_cnt = dp - fp->f_base;
		if (delayed_cr)
			return CR;
		/* this can happen only if we have a single CR at EOF */
		if (fp->f_cnt == 0)
			goto retry;
#   endif /* CRLF */

#   ifdef MAC	/* convert <cr> in <nl> and vice versa */

		register char	*s = fp->f_base,
				*e = sp + fp->f_cnt;

		while (s < e) {
			switch (*s++) {
			case CR: s[-1] = LF; break;
			case LF: s[-1] = CR; break;
			}
		}
#   endif
	}
#endif /* F_BINARY */

	--fp->f_cnt;	/* we made sure that fp->f_cnt > 0 if we got here. */
	return *fp->f_ptr++;
}

void
putstr(s)
register const char	*s;
{
	register int	c;
	register File	*f = stdout;

	while (c = *s++)
		putc(c, f);
}

void
fputnchar(buf, n, fp)
const void_*	buf;
register int	n;
register File	*fp;
{
	register const char	*s = buf;

	while (--n >= 0)
		putc(*s++, fp);
}

/* get a number of characters -- returns 0 if successful, EOF on failure */

fgetnchar(buf, n, fp)
void_*		buf;
register int	n;
register File	*fp;
{
	register char	*s = buf;

	while (--n >= 0)
		*s++ = getc(fp);
	return (fp->f_flags & F_EOF) ? EOF : 0;
}

#define NOCHAR	-1024	/* "impossible" char code (even for signed chars) */

void
flusho()
{
	_flush(NOCHAR, stdout);
}

void
flush(fp)
File	*fp;
{
	_flush(NOCHAR, fp);
}

_flush(c, fp)
register File	*fp;
{
	register int	n;
	register char	*s,
			*e;
#ifdef CRLF
	char	transl_buf[BLKSIZ+1];
#endif
	if (fp->f_flags & (F_READ | F_STRING | F_ERR))
		return;

	e = fp->f_ptr;
	s = fp->f_base;

	errno = 0;
#if F_BINARY

	if (!(fp->f_flags & F_BINARY)) {

#   ifdef CRLF	/* expand <nl> to <cr><nl> */

		register char	*d = transl_buf,
				*prev = s;
		register char	ch;

		while (s < e) {
			if ((ch = *s++) == LF)
				*d++ = CR;
			*d++ = ch;
			if (d >= &transl_buf[BLKSIZ]) {
				n = d - transl_buf;
				d = transl_buf;
				if (write(fp->f_fd, d, n) != n) {
				    	fp->f_flags |= F_ERR;
					error("[I/O error(%s); file=%s, fd=%d]",
					      syserr(), fp->f_name, fp->f_fd);
				}
				io_chars += n;
				prev = s;
			}
		}
		/*
		 * Leave remainder for the next time (so that we will
		 * generally keep in line with physical block boundary)
		 * UNLESS we did not write anything at all, or this was
		 * a partially filled buffer (i.e. explicit fflush, or
		 * upon closing a file), or the previous write exactly
		 * filled the request.
		 */
		if ((s = prev) < e) {
			if (s > fp->f_base && fp->f_cnt < 0) {
				fp->f_cnt = fp->f_bufsize - (e - s);
				/* relocate remainder to start of StdIObuf */
				d = fp->f_base;
				do *d++ = *s++; while (s < e);
				fp->f_ptr = d;
				goto ret;	/* yuck! */
			}
			/* cheat: let 's' and 'e' point into translated buf */
			s = transl_buf;
			e = d;
		}
#   endif /* DOS */

#   ifdef MAC	/* convert <cr> in <nl> and vice versa */

		while (s < e) {
			switch (*s++) {
			case CR: s[-1] = LF; break;
			case LF: s[-1] = CR; break;
			}
		}
		s = fp->f_base;
#   endif /* MAC */
	}
#endif /* F_BINARY */

	if ((n = (e - s)) > 0 && write(fp->f_fd, s, n) != n && fp != stdout) {
	    	fp->f_flags |= F_ERR;
		error("[I/O error(%s); file=%s, fd=%d]",
			syserr(), fp->f_name, fp->f_fd);
	}
	io_chars += n;

	fp->f_cnt = fp->f_bufsize;
	fp->f_ptr = fp->f_base;
ret:
	if (c != NOCHAR)	/* there is always room in the buffer */
		--fp->f_cnt, *fp->f_ptr++ = c;
	if (fp == stdout)
		OkayAbort = YES;
}

f_gets(fp, buf, size)
register File	*fp;
char	*buf;
{
	register char	*cp = buf;
	register int	c;
	register char	*endp = cp + size - 1;

	if (fp->f_flags & F_EOF)
		return EOF;
	while ((c = getc(fp)) != '\n') {
		if (c <= 0) {
			if (c == '\0') {
#ifdef NULLCHARS
				c = '\n';	/* kludge to read nulls */
#else
				continue;	/* sorry we don't read nulls */
#endif
			} else

			/*
			 * presumably we have c == EOF here, but on systems
			 * that sign-extend characters it may be a valid
			 * character. So check File flags if there's really
			 * something rotten.
			 */
#if (!__CHAR_UNSIGNED__)
			if (fp->f_flags & (F_ERR|F_EOF)) /* the next block */
#endif
		    {
			*cp = '\0';
			if (cp != buf && (fp->f_flags & F_EOF)) {
				/*
				 * if we are a real EOF (not read error),
				 * show message and return EOF the *next* time.
				 */
				add_mess(" [Incomplete last line]");
				break;
			}
			return EOF;
		    }
		}
		if (cp >= endp) {
			add_mess(" [Line too long]");
			rbell();
			fp->f_cnt++, fp->f_ptr--;	/* unget character */
			*cp = '\0';
			return EOF;
		}
		*cp++ = c;
	}
	*cp = '\0';
	io_lines++;
	return NIL;	/* this means okay */
}

#include "temp.h"	/* for BLKSIZ_SHIFT */

void
f_seekblk(fp, bno)
register File	*fp;
{
	fp->f_flags &= ~(F_ERR|F_EOF);
	fp->f_cnt = 0;
	lseek(fp->f_fd, (long) bno << BLKSIZ_SHIFT, SEEK_SET);
}

/*
 * Do a binary search on a File.  This assumes the entries in the file are
 * in lexical order. `searchstr' gives the entry to match, `prefix_len' is
 * the minimum length of a match in order to qualify as an entry at all,
 * (it can be zero, as it in fact is for searches in tagfiles), and
 * `match_len' gives the length of an exact match.  A further restriction
 * to an exact match is that the match should be followed by whitespace
 * (in fact by any control character).
 *
 * This is a generalization of the Fast Tags code in JOVE 4.14.
 * The help file now benefits from it too!
 *
 * Warning: there is some very low-level File fiddling going on here which
 * is not for the weak at heart!
 */

void
f_bsearch(fp, searchstr, prefix_len, match_len)
register File	*fp;
const char	*searchstr;	/* search string: prefix+actual match */
int		prefix_len;	/* length of required prefix */
int		match_len;	/* length of match string */
{
	struct stat	stbuf;
	register int	c;
	register unsigned
			mid,		/* bno of midpoint */
			upper,		/* bno of upper bound */
			lower,		/* bno of lower bound */
			curr,		/* current block */
			matchlim;

	if (fstat(fp->f_fd, &stbuf) < 0)
		return;

	lower = 0;
	mid = 0;
	upper = (int)(stbuf.st_size >> BLKSIZ_SHIFT);

#ifndef TINY
	/*
	 * Yet another kludge: if we are BEFORE the entry and we match
	 * more than `matchlim' characters, we declare it a match right
	 * away. This tends to avoid overshoot.  We determine the
	 * --somewhat arbitrary-- value of `matchlim' here.
	 */
	matchlim = (c = prefix_len) + upper / 26;	/* heuristics... */
	/*
	 * We try to be choose the initial midpoint cleverly,
	 * i.e., for the most common case of lowercase-only keys.
	 */
	c = searchstr[c/*prefix_len*/];
	curr = (upper * ((c < 'a') ? 3 : (c <= 'z') ? (c & 037) + 3 : 29)) >> 5;
#else
	curr = (upper + lower) >> 1;
#endif
	/*
	 * Invariant: if there is a line matching the search string, it
	 * begins somewhere after position lower, and begins at or before
	 * upper.  There is one possible exception: if lower is 0, the
	 * match might be on the very first line.
	 *
	 * The loop condition is chosen so that when the loop terminates,
	 * the file pointer is positioned at lower, so we can plunge into
	 * the sequential search right away.
	 */
	for (; (upper > lower + 1 || mid > lower); curr = (upper + lower) >> 1) {
		mid = curr;
		f_seekblk(fp, mid);

    nextblock:	/* Ugh! do it by hand... */
		c = filbuf(fp);

		if (mid == 0)
			/* unget character */
			fp->f_cnt++, fp->f_ptr--;
		else
    skip_to_nl:		/* skip to start of line */
			while (c != '\n' && --fp->f_cnt >= 0)
				c = *fp->f_ptr++;

		if (fp->f_cnt <= match_len) {
			/*
			 * There are too few characters left to make up a
			 * match, so go load the next block, unless we
			 * are near the end of the file.  In that case we
			 * assume we are AFTER the search string.
			 */
			if (++curr < upper)
				goto nextblock;		/*~~~Yuck!~~~*/

			upper = mid;		/* original midpoint */
			continue;
		}
#define	match c /* alias */

		/* peek into the File's buffer (Ouch!) */
		match = numcomp(searchstr, fp->f_ptr);

		/* if prefix didn't match, try next line */
		if (match < prefix_len) {
			fp->f_cnt--;
			c = *fp->f_ptr++;
			goto skip_to_nl;		/*~~~Spagetti!~~~*/
		}
		/* we got an exact match, so bail out */
		if (match >= match_len && (fp->f_ptr[match_len] <= ' '))
			break;

		/* got a partial match here, see which way it goes */
		if (fp->f_ptr[match] < searchstr[match]) {
#ifndef TINY
			if (match > matchlim)
				break;
#endif
			lower = curr;	/* we are BEFORE entry */
		}
		else
			upper = mid;	/* we are AFTER entry */
	}
}

/*
 * search next occurrence of a pattern in File.
 * (use genbuf as line buffer so the match is not lost upon exit)
 * The file will be positioned at the line following the matching line
 * or at EOF if the match fails. This is efficient enough to replace
 * a lot of ad-hoc file search routines.
 * returns line number in file if successful, 0 on failure.
 */

#include "re.h"

int
re_fsearch(fp, pattern)
register File	*fp;
const char	*pattern;
{
	register char	*line = genbuf;

	REcompile(pattern, YES, compbuf);
	while (f_gets(fp, line, LBSIZE) == 0 /*!EOF*/) {
		if (re_sindex(line, 0, compbuf))
			return io_lines;
	}
	return NO;
}

/*======================================================================
 * $Log: fp.c,v $
 * Revision 14.32.0.3  1993/07/23  03:20:59  tom
 * (f_gets): ungobble char when line is truncated.
 *
 * Revision 14.32  1993/06/09  01:02:20  tom
 * (f_gets): remove F_IGN_0 kludge.
 *
 * Revision 14.31.0.2  1993/03/25  21:30:47  tom
 * allow compile-time CRLF option on non-DOS systems.
 *
 * Revision 14.31  1993/02/16  05:47:55  tom
 * remove (void) casts; lotsa random optimizations.
 *
 * Revision 14.30  1993/01/26  18:43:09  tom
 * cleanup whitespace; some random optimizations; flush() now handles io_chars.
 *
 * Revision 14.28  1992/10/24  01:24:18  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.26  1992/08/26  23:56:52  tom
 * add RCS directives.
 *
 */
