/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

#include "jove.h"
#include "fp.h"
#include "jctype.h"
#include "disp.h"
#include "fmt.h"

#ifdef MAC
#	include "mac.h"
#else /* !MAC */
#	include <sys/stat.h>
#endif /* !MAC */

#ifdef RAINBOW
private int rbwrite(int, char *, int);
#endif

#ifndef L_SET
# define L_SET 0
#endif

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

private File	openfiles[MAXFILES];	/* must be zeroed initially */

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

	for (fp = openfiles, i = 0; i < MAXFILES; i++, fp++)
		if (fp->f_flags == 0)
			break;

	if (i == MAXFILES) {
		complain("[Too many open files!]");
		/* NOTREACHED */
	}
	fp->f_bufsize = buf_size;
	fp->f_cnt = 0;
	fp->f_fd = fd;
	fp->f_flags = flags;
	if (buffer == NULL) {
		buffer = emalloc((size_t)buf_size);
		fp->f_flags |= F_MYBUF;
	}
	fp->f_base = fp->f_ptr = buffer;
	fp->f_name = copystr(name);

	return fp;
}

void
gc_openfiles(void)
{
	File	*fp;

	for (fp = openfiles; fp < &openfiles[MAXFILES]; fp++)
		if (fp->f_flags != 0 && (fp->f_flags & F_LOCKED) == 0)
			f_close(fp);
}

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

	switch (F_MODE(flags)) {
	case F_READ:
		fd = open(name, O_RDONLY | O_BINARY | O_CLOEXEC);
		break;

	case F_APPEND:
		fd = open(name, O_WRONLY | O_BINARY | O_CLOEXEC);
		if (fd != -1) {
			(void) lseek(fd, (off_t)0, 2);
			break;
		}
		/* FALLTHROUGH */
	case F_WRITE:
#ifdef O_CREAT
		fd = open(name, O_CREAT | O_TRUNC | O_BINARY | O_RDWR | O_CLOEXEC,
# ifdef UNIX
			(jmode_t)CreatMode
# else
			S_IWRITE | S_IREAD
# endif
			);
#else
		fd = creat(name, (jmode_t)CreatMode);
#endif
#ifdef O_TRUNC_BROKEN
		/*
		 * Cygwin 3.1.2 open(..., O_TRUNC) seems to
		 * not truncate files on VirtualBox shared
		 * folders, perhaps on any network drive? But
		 * it has ftruncate and that appears to
		 * workaround the problem. Since most uses of
		 * this function will subsequently write to
		 * the minibuf, seems pointless to report
		 * an error on ftruncate, which really should
		 * never happen if we successfully opened the file!
		 */
		if (fd >= 0)
			(void) ftruncate(fd, 0);
#endif
		break;

	default:
		fd = -1;	/* avoid uninitialized complaint from gcc -W */
		error("invalid F_MODE");
		/* NOTREACHED */
	}
	if (fd == -1)
		return NULL;

	return fd_open(name, flags, fd, buffer, buf_size);
}

void
f_close(File *fp)
{
	const char *what = "close";
	int	err = 0;

	if ((fp->f_flags & (F_WRITE|F_APPEND))
	&& (fp->f_flags & F_ERR) == 0)
	{
		flushout(fp);
#ifdef USE_FSYNC
		if (fsync(fp->f_fd) != 0) {
			what = "fsync";
			err = errno;
		}
#endif
	}
	if (close(fp->f_fd) != 0 && err == 0)
		err = errno;
	if (fp->f_flags & F_MYBUF)
		free(fp->f_base);
	free(fp->f_name);
	fp->f_flags = 0;	/* indicates that we're available */
	if (err != 0) {
		/* It would be nice to print fp->f_name, but it's gone.
		 * Perhaps we should allow the memory to leak so that we
		 * could easily print it.
		 */
		error("[%s error: %s]", what, strerror(err));
		/* NOTREACHED */
	}
}

ZXchar
f_filbuf(File *fp)
{
	if (fp->f_flags & (F_EOF|F_ERR))
		return EOF;

	fp->f_ptr = fp->f_base;
#ifndef MSDOS
	do {
#endif /* MSDOS */
		fp->f_cnt = read(fp->f_fd, fp->f_base, (size_t) fp->f_bufsize);
#ifndef MSDOS
	} while (fp->f_cnt == -1 && errno == EINTR);
#endif /* MSDOS */
	if (fp->f_cnt == -1) {
		/* I/O error -- treat as EOF */
		writef("[Read error: %s]", strerror(errno));
		fp->f_flags |= F_ERR | F_EOF;
		return EOF;
	}
	if (fp->f_cnt == 0) {
		fp->f_flags |= F_EOF;
		return EOF;
	}
	io_chars += fp->f_cnt;
	return f_getc(fp);
}

void
putstr(const char *s)
{
	char	c;

	while ((c = *s++) != '\0')
		scr_putchar(c);
}

void
fputnchar(char *s, int n, File *fp)
{
	while (--n >= 0)
		f_putc(*s++, fp);
}

#ifndef NO_JSTDOUT
void
flushscreen(void)
{
	flushout(jstdout);
}
#endif /* !NO_JSTDOUT */

void
f_seek(File *fp, off_t offset)
{
	if (fp->f_flags & (F_WRITE|F_APPEND))
		flushout(fp);
	fp->f_cnt = 0;		/* next read will f_filbuf(), next write
				   will flush() with no bad effects */
	lseek(fp->f_fd, offset, L_SET);
}

void
flushout(File *fp)
{
	if (fp->f_flags & (F_READ | F_STRING | F_ERR)) {
		if (fp->f_flags != F_STRING)
			abort();	/* IMPOSSIBLE */
		/* We just banged into the end of a string.
		 * In the interests of continuing, we will cause
		 * the rest of the output to be be heaped in the
		 * last position.  Surely it will end up as a NUL. UGH!
		 */
		fp->f_cnt = 1;
		fp->f_ptr = &fp->f_base[fp->f_bufsize - 1];
	} else {
		char	*p = fp->f_base;

		for (;;) {
			JSSIZE_T n = fp->f_ptr - p,
				wr;

			if (n <= 0)
				break;

#ifdef RAINBOW
			wr = rbwrite(fp->f_fd, p, (size_t)n);
#else
			wr = write(fp->f_fd, p, (size_t)n);
#endif
			if (wr >= 0) {
				p += wr;
			} else {
#ifndef MSDOS
				if (errno != EINTR) {
#endif /* MSDOS */
#ifndef NO_JSTDOUT
					if (fp == jstdout)
						break;	/* bail out, silently */
#endif
					fp->f_flags |= F_ERR;
					error("[I/O error(%s); file = %s, fd = %d]",
						strerror(errno), fp->f_name, fp->f_fd);
					/* NOTREACHED */
#ifndef MSDOS
				}
#endif /* MSDOS */
			}
		}

		fp->f_cnt = fp->f_bufsize;
		fp->f_ptr = fp->f_base;
	}
}

jbool
f_gets(File *fp, char *buf, size_t max)
{
	char	*cp = buf,
		*endp = buf + max - 1;
	ZXchar	c;

	if (fp->f_flags & F_EOF)
		return YES;

	while ((c = f_getc(fp)) != EOF && c != EOL) {
		/* We can't store NUL in our buffer, so ignore it.
		 * Similarly, we can only store characters less than NCHARS.
		 * Of course, with a little ingenuity we could store NUL:
		 * NUL could be represented by EOL.
		 */
		if (c == '\0'
#if NCHARS != UCHAR_ROOF
		|| c >= NCHARS
#endif
		)
			continue;

		if (cp >= endp) {
			fp->f_flags |= F_LINETOOLONG;
			add_mess(" [Line too long]");
                        rbell();
			return YES;
		}
		*cp++ = c;
	}
	*cp = '\0';
	if (c == EOF) {
		if (cp != buf)
			add_mess(" [Incomplete last line]");
		return YES;
	}
#ifdef USE_CRLF
	/* a CR followed by a LF is treated as a NL.
	 * Bug: the line-buffer is effectively shortened by one character.
	 */
	if (cp != buf && cp[-1] == '\r')
		*--cp = '\0';
#endif /* USE_CRLF */
	io_lines += 1;
	return NO;	/* this means okay */
}

/* skip to beginning of next line, i.e., next read returns first
 * character of new line
 */
void
f_toNL(File *fp)
{
	if (fp->f_flags & F_EOF)
		return;

	for (;;) {
		switch (f_getc(fp)) {
		case EOF:
			fp->f_flags |= F_EOF;
			/*FALLTHROUGH*/
		case EOL:
			return;
		}
	}
}

#ifdef PIPEPROCS
size_t
f_readn(File *fp, char *addr, size_t n)
{
	size_t	nleft;

	for (nleft = n; nleft > 0; nleft--) {
		ZXchar	c = f_getc(fp);

		if (f_eof(fp))
			break;

		*addr++ = c;
	}
	return n - nleft;
}
#endif /* PIPEPROCS */

/* ScrBufSize is the size of the buffer for jstdout.  It is also the
 * number of characters to be output between checks for input, so
 * it is meaningful even if jstdout isn't used.  Its value is set by
 * settout based on the baud rate of output (on systems with baud rates).
 */
#ifdef NO_JSTDOUT
int	ScrBufSize = 256;
#else
int	ScrBufSize = 1;	/* until settout decides a better value & allocates a buf */
#endif

#ifndef NO_JSTDOUT
private char	one_buf;

private File	stdout_File = {1, 1, 1, F_WRITE, &one_buf, &one_buf, (char *)NULL};
File	*jstdout = &stdout_File;
#endif

#ifdef RAINBOW

/*
 * use the Rainbow's video output function
 */

#include <dos.h>

private int
rbwrite(fd, buf, cnt)
int fd;
char *buf;
int cnt;
{
	union REGS vr;

	if (fd != 1) {
		write(fd, buf, cnt);
	} else {
		while (cnt-- > 0) {
			vr.x.ax = *buf++;
			vr.x.di = 0;
			int86(0x18, &vr, &vr);
		}
	}
}
#endif /* RAINBOW */
