/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: ch.c,v 1.12.2.1 92/06/24 13:43:20 marker Exp $"

/*
 * Low level character input from the input file.
 * We use these special purpose routines which optimize moving
 * both forward and backward from the current read pointer.
 */

#include "less.h"
#include <ctype.h>
#include <wctype.h>
#ifndef multibyte
#define multibyte       (_ctype[520]>1)
#endif

public int file = -1;	/* File descriptor of the input file */

/*
 * Pool of buffers holding the most recently used blocks of the input file.
 */
struct buf {
	struct buf *next, *prev;
	long block;
	WCHAR_T data[BUFSIZ];
};

static long file_tell;	/* tells where the position is in data file */
public struct block_inf scan[MAXBLOCK];
struct buf  *currentbuf;

static struct buf *bufs = NULL;
public int nbufs;

/*
 * The buffer pool is kept as a doubly-linked circular list,
 * in order from most- to least-recently used.
 * The circular list is anchored by buf_anchor.
 */
static struct {
	struct buf *next, *prev;
} buf_anchor;
#define	END_OF_CHAIN	((struct buf *)&buf_anchor)
#define	buf_head	buf_anchor.next
#define	buf_tail	buf_anchor.prev

/*
 * If we fail to allocate enough memory for buffers, we try to limp
 * along with a minimum number of buffers.  
 */
#define	DEF_NBUFS	2	/* Minimum number of buffers */

extern int clean_data;
extern int ispipe;
extern int sigs;

#if LOGFILE
extern int logfile;
#endif

/*
 * Current position in file.
 * Stored as a block number and an offset into the block.
 */
public long ch_block;
public int ch_offset;

/* 
 * Length of file, needed if input is a pipe.
 */
static POSITION ch_fsize;

/*
 * Largest block number read if input is standard input (a pipe).
 */
static long last_piped_block;

/*
 * Get the character pointed to by the read pointer.
 * ch_get() is a macro which is more efficient to call
 * than fch_get (the function), in the usual case 
 * that the block desired is at the head of the chain.
 */
#define	ch_get()   ((buf_head->block == ch_block) ? \
			buf_head->data[ch_offset] : fch_get())
	static int
fch_get()
{
	register struct buf *bp;
	register int n;
	register int end;
	POSITION pos;
	/*
	 * Look for a buffer holding the desired block.
	 */
	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
		if (bp->block == ch_block)
			goto found;
	/*
	 * Block is not in a buffer.  
	 * Take the least recently used buffer 
	 * and read the desired block into it.
	 */
	bp = buf_tail;
	bp->block = ch_block;
	pos = get_block_pos(ch_block);       /* the head address of a block
						is stored in scan[] */
	if (scan[ch_block].block_beg == -1)  /* set the new block head address */
		scan[ch_block].block_beg = pos;
	file_tell = pos;

	if (ispipe)
	{
		/*
		 * The block requested should be one more than
		 * the last block read.
		 */
		if (ch_block != ++last_piped_block)
		{
			/* This "should not happen". */
			char message[80];
			sprintf(message, "Pipe error: last %ld, want %ld\n",
				(long)last_piped_block-1, (long)ch_block);
			error(message);
			quit();
		}
	} else
		lseek(file, pos, 0);

	/*
	 * Read the block.  This may take several reads if the input
	 * is coming from standard input, due to the nature of pipes.
	 */
	end = 0;
	/* read and convert the data, store in the buffer */
	while ((n = readwchar(file,&bp->data[end], BUFSIZ-end)) > 0)
		if ((end += n) >= BUFSIZ)
			break;
	scan[ch_block].block_size = file_tell - pos; /* set the block size in multibyte */
	if (n < 0)
	{
		error("read error");
		quit();
	}

#if LOGFILE
	/*
	 * If we have a log file, write this block to it.
	 */
	if (logfile >= 0 && end > 0)
	{
		char *logpt;
		WCHAR_T *bpdata;
		bpdata = (WCHAR_T *)malloc(4 * end + 1);
		logpt = (char *) malloc(4 * end +1);
		if (bpdata == NULL || logpt == NULL)
			error("No memory available when writing logfile");
		(void)wsncpy(bpdata, bp->data, end);
		(void)wstostr(logpt, bpdata);
		write(logfile, logpt, strlen(logpt));
		free(bpdata);
		free(logpt);
	}
#endif

	/*
	 * Set an EOF marker in the buffered data itself.
	 * Then ensure the data is "clean": there are no 
	 * extra EOF chars in the data and that the "meta"
	 * bit (the 0200 bit) is reset in each char.
	 */
	if (end < BUFSIZ)
	{
		ch_fsize = pos + end;
		bp->data[end] = EOF;
	}

	/* the data type here is WCHAR_T, the bit 0x1FFFF is used to
	 * to be a control key */
	if (!clean_data)
		while (--end >= 0)
		{
			if (!multibyte)
				bp->data[end] &= 0177;
			if (bp->data[end] == EOF)
				bp->data[end] = '@';
		}

    found:
	/* if (buf_head != bp) {this is guaranteed by the ch_get macro} */
	{
		/*
		 * Move the buffer to the head of the buffer chain.
		 * This orders the buffer chain, most- to least-recently used.
		 */
		bp->next->prev = bp->prev;
		bp->prev->next = bp->next;

		bp->next = buf_head;
		bp->prev = END_OF_CHAIN;
		buf_head->prev = bp;
		buf_head = bp;
	}
	if (ch_offset < UNBUFFERED_LINE)  /* if the ch_offset hasn't been set */
		get_ch_offset(ch_offset-UNBUFFERED, bp->data);
	return (bp->data[ch_offset]);
}

#if LOGFILE
/*
 * Close the logfile.
 * If we haven't read all of standard input into it, do that now.
 */
	public void
end_logfile()
{
	static int tried;

	if (logfile < 0)
		return;
	if (!tried && ch_fsize == NULL_POSITION)
	{
	extern WCHAR_T	w_mesg[];

		tried = 1;
		lower_left();
		clear_eol();
		so_enter();
		putstr(strtows(w_mesg,
			"finishing logfile... (interrupt to abort)"));
		so_exit();
		flush();
		while (sigs == 0 && ch_forw_get() != EOF)
			;
	}
	close(logfile);
	logfile = -1;
}
#endif

/*
 * Determine if a specific block is currently in one of the buffers.
 */
	static int
buffered(block)
	long block;
{
	register struct buf *bp;

	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
		if (bp->block == block) {
			currentbuf = bp;
			return (1);
		}
	return (0);
}

/*
 * Seek to a specified position in the file.
 * Return 0 if successful, non-zero if can't seek there.
 */
	public int
ch_seek(pos)
	register POSITION pos;
{
	long new_block;
	int i;
	for (i =0; i< MAXBLOCK; i++){
		if (scan[i].block_beg == -1) 
		{
			  if (i == 0) new_block = 0;
			  else new_block = i;
			  break;
		}
		if ((pos >= scan[i].block_beg) && (pos <= scan[i].block_beg +
							scan[i].block_size -1 ))
		{  
			new_block = i;
			break;
		}
	}
	if (!ispipe || new_block == last_piped_block + 1 || buffered(new_block))
	{
		int leftl; 
		WCHAR_T *pt;
		/*
		 * Set read pointer.
		 */
		ch_block = new_block;
		if (!buffered(new_block)) {
			if (scan[new_block].block_beg == -1)
				ch_offset = 0;
			else ch_offset = pos - scan[new_block].block_beg + UNBUFFERED;
			/*
			 * if the data is not buffered, we cannot measure how long
			 * the ch_offset is. Set it with a special value and calculate
			 * it when that buffer is read in 
			 */
			return(0);
		}
		get_ch_offset(pos-scan[new_block].block_beg,currentbuf->data);
		return (0);
	}
	return (1);
}

/*
 * Seek to the end of the file.
 */
	public int
ch_end_seek()
{
	if (ispipe)
	{
		/*
		 * Do it the slow way: read till end of data.
		 */
		while (ch_forw_get() != EOF)
			;
	} else
	{
		long flen = (POSITION)lseek(file, (off_t)0, 2);
		extern int reachPoint();
		int flag;
		register int c;
		
		flag = reachPoint(flen) ;
		(void) ch_seek((POSITION) ch_tell());
		if ( !flag )		/* goto the end of the file */
			while ((c = ch_forw_get()) != EOF);
	}
	return (0);
}

/*
 * Seek to the beginning of the file, or as close to it as we can get.
 * We may not be able to seek there if input is a pipe and the
 * beginning of the pipe is no longer buffered.
 */
	public int
ch_beg_seek()
{
	register struct buf *bp, *firstbp;

	/*
	 * Try a plain ch_seek first.
	 */
	if (ch_seek((POSITION)0) == 0)
		return (0);

	/*
	 * Can't get to position 0.
	 * Look thru the buffers for the one closest to position 0.
	 */
	firstbp = bp = buf_head;
	if (bp == END_OF_CHAIN)
		return (1);
	while ((bp = bp->next) != END_OF_CHAIN)
		if (bp->block < firstbp->block)
			firstbp = bp;
	ch_block = firstbp->block;
	ch_offset = 0;
	return (0);
}

/*
 * Return the length of the file, if known.
 */
	public POSITION
ch_length()
{
	if (ispipe)
		return (ch_fsize);
	return ((POSITION)(lseek(file, (off_t)0, 2)));
}

/*
 * Return the current position in the file.
 */
	public POSITION
ch_tell()
{
	register WCHAR_T  *pt;
	register int	offlen, i;

	pt = buf_head->data;
	for (i = 0, offlen = 0; i< ch_offset; i++)
		offlen += get_wchar_width(pt[i]);
	offlen += (scan[ch_block].block_beg == -1) ? 
		((ch_block == 0) ? 
		0 : scan[ch_block - 1].block_beg + scan[ch_block - 1].block_size + 1)
		  : scan[ch_block].block_beg;
	return (offlen);
}

/*
 * Get the current char and post-increment the read pointer.
 */
	public int
ch_forw_get()
{
	register int c;

	if (buf_head == 0) {
		return EOF;
	}
	c = ch_get();
	if (c != EOF && ++ch_offset >= BUFSIZ)
	{
		ch_offset = 0;
		ch_block ++;
	}
	return (c);
}

/*
 * Pre-decrement the read pointer and get the new current char.
 */
	public int
ch_back_get()
{
	register int c;

	if (--ch_offset < 0)
	{
		if (ch_block <= 0 || (ispipe && !buffered(ch_block-1)))
		{
			ch_offset = 0;
			return (EOF);
		}
		ch_offset = BUFSIZ - 1;
		ch_block--;
	}
	c = ch_get();
	return (c);
}

block_init()
{
	register int i;
	for (i = 0; i < MAXBLOCK; i++)
	{
		scan[i].block_beg = (long) -1;
		scan[i].block_size = BUFSIZ;
	}
}

/*
 * Initialize the buffer pool to all empty.
 * Caller suggests that we use want_nbufs buffers.
 */
	public void
ch_init(want_nbufs)
	int want_nbufs;
{
	register struct buf *bp;
	char *calloc();

	if (nbufs < want_nbufs)
	{
		/*
		 * We don't have enough buffers.  
		 * Free what we have (if any) and allocate some new ones.
		 */
		if (bufs != NULL)
			free((char *)bufs);
		bufs = (struct buf *) calloc(want_nbufs, sizeof(struct buf));
		nbufs = want_nbufs;
		if (bufs == NULL)
		{
			/*
			 * Couldn't get that many.
			 * Try for a small default number of buffers.
			 */
			char message[80];
			sprintf(message,
			  "Cannot allocate %d buffers.  Using %d buffers.", 
			  nbufs, DEF_NBUFS);
			error(message);
			bufs = (struct buf *) calloc(DEF_NBUFS, sizeof(struct buf));
			nbufs = DEF_NBUFS;
			if (bufs == NULL)
			{
				/*
				 * Couldn't even get the smaller number of bufs.
				 * Something is wrong here, don't continue.
				 */
				sprintf(message, 
				"Cannot even allocate %d buffers!  Quitting.",
				  DEF_NBUFS);
				error(message);
				quit();
				/*NOTREACHED*/
			}
		}
	}

	/*
	 * Initialize the buffers to empty.
	 * Set up the circular list.
	 */
	for (bp = &bufs[0];  bp < &bufs[nbufs];  bp++)
	{
		bp->next = bp + 1;
		bp->prev = bp - 1;
		bp->block = (long)(-1);
	}

	bufs[0].prev = bufs[nbufs-1].next = END_OF_CHAIN;
	buf_head = &bufs[0];
	buf_tail = &bufs[nbufs-1];
	last_piped_block = -1;
	ch_fsize = NULL_POSITION;
	block_init();
	file_tell = 0;
	(void) ch_seek((POSITION)0);
}

extern	eucwidth_t  eucp;

int
readwchar(d, buf, nwbytes)
int d;
wchar_t *buf;
register int nwbytes;
{
	register int cc, n;
	register char *cp;
	register wchar_t *wp;
	int eol = 0, err = 0;
	char cbuf[BUFSIZ + 5];
	int  MULTI_BYTE_MAX = 5;
	extern char	badEUC;

	if (nwbytes > BUFSIZ) {
#ifdef DEBUG
		puts("w_read: too many bytes required");
#endif
		nwbytes = BUFSIZ;
	}
	wp = buf;
	while (nwbytes > 0 && !eol) {
		cc = 0;
	reread:
		if ((n = read(d, &cbuf[cc], nwbytes)) < 0)
			return(n);
		else if (n == 0)
			break;
		if (n < nwbytes)
			eol = 1;
		cc += n;
		cp = cbuf;
		file_tell += n;
		if (!multibyte) {
			n = (cc < nwbytes ? cc : nwbytes);
			nwbytes -= n;
			while (n--)
				*wp++ = (wchar_t) *cp++;
			continue;
		}
		while (nwbytes > 0 && cc > 0) {
			if ((n = mbtowc(wp, cp, cc)) < 0) {
				if (cc < MULTI_BYTE_MAX && err++ < MULTI_BYTE_MAX) {
					bcopy(cp, cbuf, cc);
					goto reread;
				}
			}

			if (n <= 0) {
				if (!badEUC) {
					badEUC++;
					error("Data may be binary.  Expecting multibyte EUC");
				}
				n = 1;
				*wp = (wchar_t) *cp;
			}
			cp += n;
			cc -= n;
			wp++;
			--nwbytes;
			err = 0;
		}
	}
	return(wp - buf);
}

get_block_pos(block_no)
long block_no;
{
	if (scan[block_no].block_beg != -1)
		return scan[block_no].block_beg;
	else if (block_no == 0)
		return 0;
	return(scan[block_no - 1].block_beg + scan[block_no - 1].block_size);
}

get_wchar_width(c)
WCHAR_T c;
{
	if (isascii(c))
		return 1;
	else if (iscodeset1(c))
		return eucp._eucw1;
	else if (iscodeset2(c))
		return eucp._eucw2 + 1;
	else
		return eucp._eucw3 + 1;
}

get_ch_offset(offset, data)
int	offset;
WCHAR_T  *data;
{
	register  WCHAR_T *pt = data;
	ch_offset = 0;
	while(offset > 0 ){
		offset -= get_wchar_width(*pt++);
		ch_offset++;
	}
}
