/*
 * 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"


/* Prototypes for functions defined in ch.c */

static int fch_get __PROTO((void));
static int buffered __PROTO((long block));


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

/*
 * Pool of buffers holding the most recently used blocks of the input file.
 */
#define BUFSIZ  1024
struct buf {
        struct buf *next, *prev;
        long block;
        char data[BUFSIZ];
};
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 */

#ifndef AMIGA
extern int clean_data;
#endif
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.
 */
static long ch_block;
static 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())

#ifdef __STDC__
static int fch_get (void)
#else
        static int
fch_get()
#endif
{
        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 = ch_block * BUFSIZ;
        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;
        while ((n = read(file, &bp->data[end], BUFSIZ-end)) > 0)
                if ((end += n) >= BUFSIZ)
                        break;

        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)
                write(logfile, bp->data, end);
#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;
        }

#ifndef AMIGA
        if (!clean_data)
#endif
                while (--end >= 0)
                {
#ifdef EIGHTBIT
                        /* We handle all 8-bit characters, except these,
                           which are flags for special printing
                         */
                        switch ( bp->data[end] )
                        {
                        case UL_CHAR:
                        case UE_CHAR:
                        case BO_CHAR:
                        case BE_CHAR:
                        case IT_CHAR:
                        case IE_CHAR:
                        case NV_CHAR:
                        case NE_CHAR:
                                bp->data[end] = '\177';
                        default:
                                break;
                        }
#else
#ifdef ANSIGR
                        if ( (unsigned char)(bp->data[end]) != 0x9b )
#endif
                        bp->data[end] &= 0177;
#endif
                        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;
        }
        return (int)(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)
        {
                tried = 1;
                lower_left();
                clear_eol();
                so_enter();
                putstr("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.
 */
#ifdef __STDC__
static int buffered (long block)
#else
        static int
buffered(block)
        long block;
#endif
{
        register struct buf *bp;

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

/*
 * Seek to a specified position in the file.
 * Return 0 if successful, non-zero if can't seek there.
 */
#ifdef __STDC__
int ch_seek (register POSITION pos)
#else
        public int
ch_seek(pos)
        register POSITION pos;
#endif
{
        long new_block;

        new_block = pos / BUFSIZ;
        if (!ispipe || new_block == last_piped_block + 1 || buffered(new_block))
        {
                /*
                 * Set read pointer.
                 */
                ch_block = new_block;
                ch_offset = pos % BUFSIZ;
                return (0);
        }
        return (1);
}

/*
 * Seek to the end of the file.
 */
#ifdef __STDC__
int ch_end_seek (void)
#else
        public int
ch_end_seek()
#endif
{
        if (ispipe)
        {
                /*
                 * Do it the slow way: read till end of data.
                 */
                while (ch_forw_get() != EOF)
                        ;
        } else
        {
                (void) ch_seek((POSITION)(lseek(file, (offset_t)0, 2)));
        }
        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.
 */
#ifdef __STDC__
int ch_beg_seek (void)
#else
        public int
ch_beg_seek()
#endif
{
        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.
 */
#ifdef __STDC__
POSITION ch_length (void)
#else
        public POSITION
ch_length()
#endif
{
        if (ispipe)
                return (ch_fsize);
        return ((POSITION)(lseek(file, (offset_t)0, 2)));
}

/*
 * Return the current position in the file.
 */
#ifdef __STDC__
POSITION ch_tell (void)
#else
        public POSITION
ch_tell()
#endif
{
        return (ch_block * BUFSIZ + ch_offset);
}

/*
 * Get the current char and post-increment the read pointer.
 */
#ifdef __STDC__
int ch_forw_get (void)
#else
        public int
ch_forw_get()
#endif
{
        register int c;

        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.
 */
#ifdef __STDC__
int ch_back_get (void)
#else
        public int
ch_back_get()
#endif
{
        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);
}

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

        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 %ld buffers.  Using %ld 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 %ld 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;
        (void) ch_seek((POSITION)0);
}
