/*
 * Routines for a set of stacks of buffers for buffering the large files
 * of JDic in memory.  The file can be ONLY READ!
 *
 * This file is freely distributable.  However, it may not be included or 
 * incorporated into any commercial product without the author's consent.
 * Without such consent, no fee beyond costs of materials and postage may
 * be charged for distribution of this file.
 *
 * Copyright Roger Ang, May 1995.
 */

#include "stack_buf.h"

#ifdef __AMIGA__
#include <exec/memory.h>
#include <proto/exec.h>
#endif

SB_buffer* SB_buffer_create(lo, hi, buf)
unsigned long lo, hi;
void* buf;
{
  SB_buffer *new_sb;

  new_sb = (SB_buffer*) malloc (sizeof(struct _stackbuf));
  new_sb->lo = lo;
  new_sb->hi = hi;
  new_sb->buffer = buf;
  new_sb->next = NULL;

  return new_sb;
}


void SB_buffer_delete(s)
SB_buffer* s;
{
  if (!s)  return;

  if (s->next) {
    fprintf(stderr, "\nSB_buffer_delete(): buffer still in stack.\n");
    return;
  }

  if (s->buffer)    free((char*) s->buffer);

  free((char *) s);
}

/* add s to head of slist */
SB_buffer* SB_buffer_add(slist, s)
SB_buffer *slist, *s;
{

  if (!s) {
    fprintf(stderr, "\nSB_buffer_add(): NULL parameter.\n");
    return slist;
  }

  if (!slist)  return s;

  s->next = slist;

  return s;
}


SB_buffer* SB_buffer_remove(slist, s)
SB_buffer *slist, *s;
{
  SB_buffer *stmp;

  if (!slist || !s) {
    fprintf(stderr, "\nSB_buffer_remove(): NULL parameter.\n");
    return slist;
  }

  if (s == slist) { 
    /* remove head */
    stmp = s->next;
    s->next = NULL;
    return stmp;
  }

  /* find prev */
  for (stmp = slist; stmp; stmp = stmp->next)
    if (stmp->next == s)  break;

  if (stmp) {
    stmp->next = s->next;
    s->next = NULL;
  }
  return slist;
}

SB_buffer* SB_buffer_find_by_index(slist, i)
SB_buffer *slist;
unsigned long i;
{
  SB_buffer *stmp;

#ifdef DEBUG
  /* turn off checks to try and squeeze out more performance */
  if (!slist) {
    fprintf(stderr, "\nSB_buffer_find_by_index(): NULL parameter.\n");
    return NULL;
  } 
#endif

  for (stmp = slist; stmp; stmp = stmp->next)
    if ((stmp->lo <= i) && (stmp->hi >= i))
       return stmp;

  return NULL;
}

SB_bufferset* SB_bufferset_create(bufvar, buffers, num_bufs)
void **bufvar;
SB_buffer *buffers;
int num_bufs;
{
  SB_bufferset *new_bs;

  new_bs = (SB_bufferset*) malloc (sizeof(struct _bufstack));
  new_bs->bufvar = bufvar;
  new_bs->buffers = buffers;
  new_bs->num_bufs = num_bufs;
  new_bs->next = NULL;

  return new_bs;
}

void SB_bufferset_delete(bs)
SB_bufferset *bs;
{
  if (!bs) {
    fprintf(stderr, "\nSB_bufferset_delete(): NULL parameter.\n");
    return;
  }
  
  if (bs->next) {
    fprintf(stderr, "\nSB_bufferset_delete(): set still in list.\n");
    return;
  }

  if (bs->buffers) {
    fprintf(stderr, "\nSB_bufferset_delete(): set not empty.\n");
    return;
  }

  free((char *) bs);
}


/* add s to head of slist */
SB_bufferset* SB_bufferset_add(slist, s)
SB_bufferset *slist, *s;
{
  if (!s) {
    fprintf(stderr, "\nSB_bufferset_add(): NULL parameter.\n");
    return slist;
  }

  if (!slist)  return s;

  s->next = slist;

  return s;
}


SB_bufferset* SB_bufferset_remove(slist, s)
SB_bufferset *slist, *s;
{
  SB_bufferset *stmp;

  if (!slist || !s) {
    fprintf(stderr, "\nSB_bufferset_remove(): NULL parameter.\n");
    return slist;
  }

  if (s == slist) { 
    /* remove head */
    stmp = s->next;
    s->next = NULL;
    return stmp;
  }

  /* find prev */
  for (stmp = slist; stmp; stmp = stmp->next)
    if (stmp->next == s)  break;

  if (stmp) {
    stmp->next = s->next;
    s->next = NULL;
  }

  return slist;
}

SB_bufferset* SB_bufferset_find_by_var(slist, bufvar)
SB_bufferset *slist;
void **bufvar;
{
  SB_bufferset *stmp;

#ifdef DEBUG
  /* turn off checks to try and squeeze out more performance */
  if (!slist) {
    fprintf(stderr, "\nSB_bufferset_find_by_var(): NULL parameter.\n");
    return NULL;
  } 
#endif

  for (stmp = slist; stmp; stmp = stmp->next)
    if (stmp->bufvar == bufvar)
       return stmp;

  return NULL;
}


SB_bufferset *var_SBuffered(bufvar)
void **bufvar;
{
  SB_bufferset *this_bs;

  if (SB_global_bufferset) {
    this_bs = SB_bufferset_find_by_var(SB_global_bufferset, bufvar);
    return this_bs;
  }
  return NULL;
}


/* returns 1 if okay, 0 if a problem */
int SBuffer_var(bufvar)
void **bufvar;
{
  SB_bufferset *this_bs;

  if (!bufvar) {
    fprintf(stderr, "\nSBuffer_var(): NULL parameter.\n");
    return 0;
  }

  if (var_SBuffered(bufvar))  return 1;  /* var already buffered */

  this_bs = SB_bufferset_create(bufvar, NULL, 0);

  SB_global_bufferset = SB_bufferset_add(SB_global_bufferset, this_bs);

  return 1;
}


/*
 * Try to get a character from buffer in memory.
 * If character not in memory, load buffer from "filename"
 * 'this_bs' is the bufferset to search.
 * 'ind' is the index (byte offset in the file)
 * 'diclen' is the file length (actually, it's the file length + 1
 * due to JDic quirks)
 */
unsigned char get_SBuffered_char(filename, this_bs, ind, diclen)
char *filename;
SB_bufferset *this_bs;
unsigned long ind, diclen;
{
  SB_buffer *this_sb = NULL;
  FILE *fp;
  unsigned char *ch_buf;
  unsigned long tmp, tmp_ind, blocks_read, bytes_read;

#ifdef DEBUG
  /* turn off checks to try and squeeze out more performance */
  /* NULL parameter */
  if ((!this_bs) || (!filename)) {
    fprintf(stderr, "\nget_SBuffered_char(): NULL parameter.\n");
    return 0;
  }  

  if (ind > diclen)  return 0; 
#endif

  /* JDic strangeness, first and last char should be a line feed */
  if ((ind == 0) || (ind == diclen))
    return 10;

  if (this_bs->buffers)
    this_sb = SB_buffer_find_by_index(this_bs->buffers, ind);

  if (this_sb) {
    /* found buffer with index */
    tmp_ind = ind - this_sb->lo;
    ch_buf = (unsigned char *) this_sb->buffer; 
    return ch_buf[tmp_ind]; 
  }
  else {    /* character at index not in memory */
    /* loop, try to allocate a buffer, if fails, free a buffer */
    do {
      if (SB_max_buffers > 0) {
        ch_buf = (unsigned char*) malloc(SB_buffer_size+1);
      }
      else {
        ch_buf = NULL;
      } 

      if (!ch_buf) {
        /* try to free up some memory */
        if (!free_SBuffer()) {
          fprintf(stderr, "Could not allocate memory SB buffer.\n");
          return 0;
	}
      }
    } while (!ch_buf);

    /* buffer allocated, read from file */
    fp = fopen(filename, "rb");                                
    if (fp == NULL ) {
      fprintf(stderr, "\nCannot open file: %s\n", filename);
      free(ch_buf);
      return 0;
    }

    tmp_ind = (ind/SB_buffer_size) * SB_buffer_size;

    /* NOTE!: fread in my compiler also stores the last partial block 
       from the end of the file into the buffer.  So, I don't need
       to worry about reading too many blocks */

#ifdef LATTICE

    if (tmp_ind == 0) {
      /* Because JDic the text file is actually loaded at index 1 and
         index 0 is an added linefeed dealt with above */
      fseek(fp, tmp_ind, 0);
      fread((unsigned char*) (ch_buf+1), 1024, SB_buffer_size/1024, fp);
    }
    else {
      fseek(fp, (tmp_ind-1), 0);
      fread((unsigned char*) ch_buf, 1024, SB_buffer_size/1024, fp);
    }

#else
    /* not using Lattice C compiler, i.e. fread doesn't store in
       partial blocks. */

    /* remember added linefeed char. is also counted in diclen */
    if ((diclen - tmp_ind - 1) >= SB_buffer_size) {

      if (tmp_ind == 0) {
        /* Because JDic the text file is actually loaded at index 1 and
           index 0 is an added linefeed dealt with above */
        fseek(fp, tmp_ind, 0);
        fread((unsigned char*) ch_buf+1, 1024, SB_buffer_size/1024, fp);
      }
      else {
        fseek(fp, (tmp_ind-1), 0);
        fread((unsigned char*) ch_buf, 1024, SB_buffer_size/1024, fp);
      }
    }
    else {  /* buffer is for end of file */
      blocks_read = (diclen - tmp_ind - 1)/1024;
      bytes_read = (diclen - tmp_ind - 1) % 1024;
      if (tmp_ind == 0) {
        /* first and only buffer for file */
        fseek(fp, tmp_ind, 0);

        if (blocks_read) {
          fread((unsigned char*) ch_buf+1, 1024, blocks_read, fp);
	}
        if (bytes_read) {
          fread((unsigned char*) (ch_buf+blocks_read*1024+1), bytes_read,
  	        1, fp);
	}
      }
      else {
        fseek(fp, (tmp_ind-1), 0);

        if (blocks_read) {
          fread((unsigned char*) ch_buf, 1024, blocks_read, fp);
	}

        bytes_read++;
        fread((unsigned char*) (ch_buf+blocks_read*1024),
               bytes_read, 1, fp); 
      }
    }

#endif

    fclose(fp);

    /* figure out hi index and create buffer for stack */
    if ((diclen - tmp_ind - 1) >= SB_buffer_size) 
      tmp = tmp_ind + SB_buffer_size - 1;
    else
      tmp = diclen;

    this_sb = SB_buffer_create(tmp_ind, tmp, (void *) ch_buf);

    this_bs->buffers = SB_buffer_add(this_bs->buffers, this_sb);
    (this_bs->num_bufs)++;
    SB_max_buffers--;

    tmp_ind = ind - this_sb->lo;
    return ch_buf[tmp_ind];
  }
}

/* 
 * Try to free 1 buffer.  
 * Returns 1 if successful, 0 if no buffer was freed.
 */
int free_SBuffer()
{
  SB_bufferset *tmp_bs, *this_bs;
  SB_buffer *this_sb;

  if (!SB_global_bufferset)  return 0;

  this_bs = SB_global_bufferset;
  for (tmp_bs = SB_global_bufferset; tmp_bs; tmp_bs = tmp_bs->next)
    if (tmp_bs->num_bufs > this_bs->num_bufs)
      this_bs = tmp_bs;

  if (this_bs->num_bufs == 0)    return 0; /* no buffers allocated */      

  /* free a buffer, get last on list (it was first one allocated) */
  this_sb = this_bs->buffers; 
  while (this_sb->next)
    this_sb = this_sb->next;
  this_bs->buffers = SB_buffer_remove(this_bs->buffers, this_sb);
  SB_buffer_delete(this_sb);
  (this_bs->num_bufs)--;

  SB_max_buffers++;

  return 1;
}


/*
 * Remove all buffers.  But the vars are still set for buffering.
 */
void freeall_SBuffers()
{
  SB_bufferset *this_bs;
  SB_buffer *this_sb;

  if (!SB_global_bufferset)
    return;

  for (this_bs = SB_global_bufferset; this_bs; this_bs = this_bs->next) {
    while (this_bs->buffers != NULL) {
      this_sb = this_bs->buffers; 
      this_bs->buffers = SB_buffer_remove(this_bs->buffers, this_sb);
      SB_buffer_delete(this_sb);
      SB_max_buffers++;
    }
    this_bs->num_bufs = 0;
  }
}


/*
 * Try to get an index from buffer in memory.
 * If not in memory, load buffer from "filename"
 * 'this_bs' is the bufferset to search.
 * 'ind' is the index (byte offset in the file)
 * 'diclen' is the file length (actually, it's the file length + 1
 * due to JDic quirks)
 */
unsigned long get_SBuffered_ind(filename, this_bs, ind, diclen)
char *filename;
SB_bufferset *this_bs;
unsigned long ind, diclen;
{
  SB_buffer *this_sb = NULL;
  FILE *fp;
  unsigned long *ind_buf;
  unsigned long tmp, tmp_ind, blocks_read, bytes_read;

#ifdef DEBUG
  /* turn off checks to try and squeeze out more performance */
  /* NULL parameter */
  if ((!this_bs) || (!filename)) {
    fprintf(stderr, "\nget_SBuffered_ind(): NULL parameter.\n");
    return 0;
  }  

  if (ind > diclen)  return 0; 
#endif

  if (this_bs->buffers)
    this_sb = SB_buffer_find_by_index(this_bs->buffers, ind);

  if (this_sb) {
    /* found buffer with index */
    tmp_ind = ind - this_sb->lo;
    ind_buf = (unsigned long *) this_sb->buffer; 
    return ind_buf[tmp_ind]; 
  }
  else {    /* character at index not in memory */
    /* loop, try to allocate a buffer, if fails, free a buffer */
    do {
      if (SB_max_buffers > 0) {
        ind_buf = (unsigned long*) malloc(SB_buffer_size);
      }
      else {
        ind_buf = NULL;
      } 

      if (!ind_buf) {
        /* try to free up some memory */
        if (!free_SBuffer()) {
          fprintf(stderr, "Could not allocate memory SB buffer.\n");
          return 0;
	}
      }
    } while (!ind_buf);

    /* buffer allocated, read from file */
    fp = fopen(filename, "rb");                                
    if (fp == NULL ) {
      fprintf(stderr, "\nCannot open file: %s\n", filename);
      free(ind_buf);
      return 0;
    }
    tmp = SB_buffer_size/sizeof(long);
    tmp_ind = (ind/tmp) * tmp;

    /* NOTE!: fread in my compiler also stores the last partial block 
       from the end of the file into the buffer.  So, I don't need
       to worry about reading too many blocks */

#ifdef LATTICE
    fseek(fp, (tmp_ind*sizeof(long)), 0);
    fread((unsigned char*) ind_buf, 1024, SB_buffer_size/1024, fp);

#else
    /* not using Lattice C compiler, i.e. fread doesn't store in
       partial blocks. */

    /* remember added linefeed char. is also counted in diclen */
    if ((diclen - tmp_ind)*sizeof(long) >= SB_buffer_size) {
      tmp++;
      fseek(fp, (tmp_ind*sizeof(long)), 0);
      fread((unsigned char*) ind_buf, 1024, (tmp*sizeof(long))/1024, fp);
    }
    else {  /* buffer is for end of file */
      blocks_read = ((diclen - tmp_ind)*sizeof(long))/1024;
      bytes_read = ((diclen - tmp_ind)*sizeof(long)) % 1024;
      fseek(fp, (tmp_ind*sizeof(long)), 0);

      if (blocks_read) {
        fread((unsigned char*) ind_buf, 1024, blocks_read, fp);
      }

      if (bytes_read) {
        fread((unsigned char*) (ind_buf+(blocks_read*1024)),bytes_read,1,fp);
      }
    }

#endif

    fclose(fp);

    /* figure out hi index and create buffer for stack */
    if ((diclen - tmp_ind)*sizeof(long) >= SB_buffer_size)
      tmp = tmp_ind + (SB_buffer_size/sizeof(long)) - 1;
    else
      tmp = diclen;

    this_sb = SB_buffer_create(tmp_ind, tmp, (void *) ind_buf);

    this_bs->buffers = SB_buffer_add(this_bs->buffers, this_sb);
    (this_bs->num_bufs)++;
    SB_max_buffers--;

    tmp_ind = ind - this_sb->lo;
    return ind_buf[tmp_ind];
  }
}

