/*************************************************************************
 ***                        ch.c                         (JJB TEMPLAR) ***
 *** Date modifications begun: 7/8/89.                                 ***
 *** Last modified: 13/8/89.                                           ***
 *************************************************************************/
/*** 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 <exec/memory.h>
#include <libraries/dos.h>

extern int  fast_line;

BPTR    file = NULL;    /* 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;
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 */

/* Current position in file. Stored as block num. and offset into the block. */
static long ch_block;
static int  ch_offset;

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

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


/*=============================fch_get===================================*/
/* 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 char       *cp;
register int        n;
register int        end;
LONG                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;
    Seek(file, pos, OFFSET_BEGINNING);

    end = Read(file, &bp->data[0], BUFSIZ);     /* Read block */
    if (end < 0) {                              /* Check if bombed out */
        error("read error",1);
        cleanup(NULL,50);
    }
  /* Zap through and convert zeros to asterisks */
    if (!fast_line) {
        cp = &bp->data[0];
        for (n = 0; n < end; n++,cp++) if (!(*cp)) *cp = '@';
    }

  /* 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;
    }

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]);
}

static int  buffered(block) /*===========================================*/
long        block;          /* Is block in one of the buffers?           */
{
register struct buf *bp;

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

int     ch_seek(pos) /*==================================================*/
register LONG   pos; /* Seek to a specific pos in file.                  */
{                    /* Zero if successful, non-zero if fails.           */
long    new_block;
    new_block = pos / BUFSIZ;
  /* Set read pointer. */
    ch_block = new_block;
    ch_offset = pos % BUFSIZ;
    return(0);
}

void    end_seek() /*====================================================*/
{                  /* Seek to end of file.                               */
    Seek(file, 0, OFFSET_END);
    ch_seek(Seek(file, 0, OFFSET_END)); /* Second Seek returns final pos */
}

LONG    ch_length() /*===================================================*/
{
    Seek(file, 0, OFFSET_END);
    return(Seek(file, 0, OFFSET_END));
}

LONG    ch_tell() /*=====================================================*/
{                 /* Return cur position in file.                        */
    return(ch_block * BUFSIZ + ch_offset);
}

int     ch_forw_get() /*=================================================*/
{                     /* Get char, post-int read pointer.                */
register int    c;

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

int     ch_back_get() /*=================================================*/
{                     /* Pre-dec read pointer, get char.                 */
register int    c;

    if (--ch_offset < 0) {
        if (ch_block <= 0) {
            ch_offset = 0;
            return (EOF);
        }
        ch_offset = BUFSIZ - 1;
        ch_block--;
    }
    c = ch_get();
    return(c);
}

void    ch_init(want_nbufs) /*===========================================*/
int     want_nbufs;         /* Init buffer pool to all empty.            */
{
register struct buf *bp;
char    message[80];

    if (nbufs < want_nbufs) {
        /* We don't have enough buffers.
           Free what we have (if any) and allocate some new ones. */
        if (bufs) FreeMem((char *)bufs,sizeof(struct buf) * nbufs);
        bufs = (struct buf *)AllocMem(want_nbufs * sizeof(struct buf),MEMF_CLEAR);
        nbufs = want_nbufs;
        if (bufs == NULL) {     /* Try for less buffers */
            sprintf(message,"Cannot allocate %d buffers.  Using %d buffers.",nbufs,DEF_NBUFS);
            error(message,1);

            bufs = (struct buf *)AllocMem(DEF_NBUFS * sizeof(struct buf),MEMF_CLEAR);
            nbufs = DEF_NBUFS;
            if (bufs == NULL) {     /* Dammit! No RAM for just small buffers */
                sprintf(message,"Cannot even allocate %d buffers!  Quitting.\n",DEF_NBUFS);
                error(message,1);
                cleanup(NULL,50);
	    }
        }
    }

  /* 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 = -1L;
    }
    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;
    ch_seek(0);
}

void    ch_memdump() /*==================================================*/
{                    /* So cleanup() can catch memory to free, no check. */
    if (bufs) FreeMem((char *)bufs,sizeof(struct buf) * nbufs);
}
