/*
 * When throwing out a cache, set q->blocknum to -1 to force cache to be
 * reloaded as well as remallocked.
 *
 *
 */

/* Bleedin' mark has ripped out half the stdio emulation. :-( */
/* All the FOPEN() etc upper-case macros pass the calls on to
 either real stdio, or - on DOS - yet another level of wrappers --
 one which extends the number of open files allowed by closing
 and reopening. Makes good use of the LRU code... */

static char *FILEIO_C =
"$Header: D:/mcl/dx2/squidge/fileio.c_v 1.10.1.1 19 Jul 1991 11:54:38 mcl $";

#include <stdio.h>
#include <stdlib.h>
#include "grope.h"
#include "freel.h"
#ifdef SYS_MSDOS /* On MSDOS, use 'eXtended' file interface
 * because of limit on open files */
#include "xfile.h"
#endif

#include "lru.h"
#include "extern.h" /* Needs xfile first */
#include "computil.h"
#include "blokcomp.h"

#define NO_FAKE_FILEIO
#include "fileio.h" /* Needs extern first */
#include "findblck.h"
#include "sperror.h"

#if defined(SYS_RISCOS) && defined(PROFILE)
extern void _mapstore(void);
extern void _fmapstore(char *f);

void
FileIO_dump_profile(void)
{
 _fmapstore("fileio-pro");
}
#endif

#if defined(MEMDEBUG)
#include <mnemosyn.h> /* Brilliant heap-debugging package */
#endif

/***************************** SUPPORT ROUTINES *****************************/

#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))

#define MAX_CACHE_ENTRIES 50


/* static QFILE *QFile_LRU = NULL; -- superceded by Marks code */
static C_DATA *QFile_global_inbuff = NULL;
static C_DATAINDEX QFile_global_insize = 0;




#define EXITDECL static int rcexit(int rc)
EXITDECL; /* Daft declaration style to help with mkptypes and ifdefs... */

static void
PRELOAD_INDEX(QFILE * q, BLOCKINDEX first_wanted_block, BLOCKINDEX cache_size)
{
 BLOCKINDEX i = 0L;
 FILEINDEX next_block, last_wanted_block;

#ifdef DEBUG
 sperror(__LINE__, __FILE__, "preload-index: cache size = %ld\n", cache_size);
 sperror(__LINE__, __FILE__, " first-wanted_block = %ld\n", first_wanted_block);
#endif

 if (q->blockstart == NULL) {
 Check(q->blockstart = malloc((size_t) cache_size * sizeof(FILEINDEX)));
 q->max_cache_entries = cache_size;
 q->cache_base = 0;
 }

 FSEEK(q->f, q->idx_pos + (sizeof(FILEINDEX) * first_wanted_block), SEEK_SET);
 if (first_wanted_block + cache_size > q->idx_entries) {
 last_wanted_block = q->idx_entries;
 } else {
 last_wanted_block = first_wanted_block + cache_size;
 }
 for (q->blocknum = 0L;
 q->blocknum < last_wanted_block - first_wanted_block;
 q->blocknum += 1L) {
 (void) get4(next_block, q->f);
 q->blockstart[i] = next_block;
#ifdef DEBUG
 sperror(__LINE__, __FILE__, " q->blockstart[%ld] = %ld\n", i, next_block);
#endif
 i += 1;
 }
#ifdef DEBUG
 sperror(__LINE__, __FILE__, "cache is now on for %ld to %ld inclusive\n", first_wanted_block,
 last_wanted_block - 1L);
#endif
 q->cache_base = (int) first_wanted_block;
}

static
 FILEINDEX
BLOCK_START(QFILE * q, BLOCKINDEX blockid)
{
 FILEINDEX next_block;

 if (q->blocknum == -1
 || (!q->cache_present)
 || blockid - q->cache_base < 0
 || blockid - q->cache_base >= q->max_cache_entries) {
 /* change to q->cache_size */
 /* This item not in cache; reset & read some more */
 PRELOAD_INDEX(
 q,
 MAX(0, MIN(blockid, q->idx_entries - q->max_cache_entries)),
 /* ditto */
 /*
 * If near the end, go backwards a little to take as much as we can
 * get. (But don't go backwards past the start of the file)
 */
 q->max_cache_entries);
 /* ditto */
 }
#ifdef DEBUG
 sperror(__LINE__, __FILE__, "block_start: blockid=%ld cache_base=%d\n", blockid, q->cache_base);
#endif
 next_block = q->blockstart[blockid - q->cache_base];

#ifdef DEBUG
 sperror(__LINE__, __FILE__, "block_start: returning mapped block number %ld\n", next_block);
#endif
 return (next_block);
}

static void
GET_BLOCK_INFO(
 QFILE *q,
 BLOCKINDEX next_block,
 C_DATAINDEX *insize,
 COMPTYPE *comp_type,
 C_DATAINDEX *expected_size)
{
#ifdef DEBUG
 sperror(__LINE__, __FILE__, "seeking to %d\n", (int) next_block);
#endif
 FSEEK(q->f, next_block, SEEK_SET);
 (void) get2((*insize), q->f);
 (void) get2((*expected_size), q->f);
 if (FERROR(q->f)) {
 sperror(__LINE__, __FILE__, "couldn't read 2-byte blocksize\n");
 }
 (void) get1((*comp_type), q->f);
 if (FERROR(q->f)) {
 sperror(__LINE__, __FILE__, "couldn't read compression type\n");
 }
}


int
qreadblock(QFILE * q, BLOCKINDEX blockid)
{
 FILEINDEX next_block; /* Note: return code not checked anywhere */
 C_DATAINDEX insize, expected_size;
 COMPTYPE comp_type;

#ifdef never
 if ((q->blocknum != blockid) &&
 !FindBlock(q,blockid))
#endif
 if (!FindBlock(q,blockid))
 {
 if (blockid >= q->idx_entries)
 {
 sperror(__LINE__, __FILE__,
 "qreadblock: program error - cannot read block %ld (last is %ld)",
 blockid, q->idx_entries - 1);
 exit(0);
 }
#ifdef never
 if (q->blocknum == blockid)
 {
 return (q->outsize);
 }
#endif
 q->outsize = 0;
 next_block = BLOCK_START(q, blockid);
 q->blocknum = blockid;

 GET_BLOCK_INFO(q, next_block, &insize, &comp_type, &expected_size);

 insize = FREAD( /* q->inbuff */ QFile_global_inbuff, 1, insize, q->f);
 if (FERROR(q->f))
 {
 sperror(__LINE__, __FILE__, "unsquidge: %s\n", strerror(errno));
 return (EOF);
 }
 if (insize == 0U)
 return (EOF); /* Not sure how it could happen, but... */

 AddBlock(q,blockid);/* this sets outbuff to a correctly sized buffer */
 if (!unsquidge( QFile_global_inbuff,
 insize,
 q->outbuff,
 &q->outsize,
 comp_type, expected_size))
 {
 sperror(__LINE__, __FILE__, "Decompression error!\n");
 return (EOF);
 }
 }
 return (q->outsize);
}

static
long
qprivread(QFILE * q, void *vbuff, long bytes)
{
 FILEINDEX firstbyte = q->filepos;
 FILEINDEX lastbyte = q->filepos + bytes; /* Exclusive pointer */
 BLOCKINDEX firstblock = firstbyte / q->blocksize;
 BLOCKINDEX lastblock = (lastbyte - 1) / q->blocksize; /* Inclusive block index */
 BLOCKINDEX blocknum;
 C_DATAINDEX start, length;
 FILEINDEX count = 0L;
 char *dest = vbuff; /* mark - don't need 'far' any more */

 if ((q->filepos == q->file_length) && q->yellow_card) {
 return (EOF);
 }
 if (lastbyte > q->file_length) {
 lastbyte = q->file_length;
 bytes = lastbyte - q->filepos;
 q->yellow_card = (0 == 0);
 }
 if (bytes == 0L) {
 return (0L);
 }
 start = (int) (firstbyte % q->blocksize);
 if ((start < 0) || (start >= q->blocksize)) {
 sperror(__LINE__, __FILE__, "Brain-failure -- firstbyte = %ld, start = %d\n",
 firstbyte, start);
 exit(EXIT_ERR);
 }
 if (firstblock == lastblock) {
 /* Simple extraction from within one block */
 length = (int) bytes;
 } else {
 /* From firstbyte to end of block */
 length = q->blocksize - start;
 }
 /* Do first block */
 (void) qreadblock(q, firstblock);
 memcpy(dest, &q->outbuff[start], length);
 dest += length;
 count += length;

 if (lastblock > firstblock + 1) {
 for (blocknum = firstblock + 1; blocknum < lastblock; blocknum++) {
 /* Do whole blocks inbetween. */
 start = 0;
 length = q->blocksize;
 (void) qreadblock(q, blocknum);
 memcpy(dest, &q->outbuff[start], length);
 dest += length;
 count += length;
 }
 }
 if (lastblock >= firstblock + 1) {
 /* from start of lastblock to some point within lastblock */
 start = 0;
 if ((lastbyte % q->blocksize) == 0) {
 sperror(__LINE__, __FILE__, "Gotcha!!\n");
 length = q->blocksize;
 } else
 length = (int) (lastbyte % q->blocksize); /* beware overflow */
 /* Do lastblock */
 (void) qreadblock(q, lastblock);
 memcpy(dest, &q->outbuff[start], length);
 dest += length;
 count += length;
 }
 if (count != bytes)
 sperror(__LINE__, __FILE__, "Sanity check fails\n");
 q->filepos += count;
 return (count);
}


static int
READ_HEADER(QFILE * q, FILEINDEX * idx_pos, FILEINDEX * idx_entries,
 FILEINDEX * file_length, C_DATAINDEX * blocksize)
{
 long check_magic;

 (void) get4(check_magic, q->f); /* magic number check */
 if (check_magic != SQUIDGE_MAGIC) {
 return (0 != 0);
 }
 (void) get4(*idx_pos, q->f);/* Start of index */
 (void) get4(*idx_entries, q->f); /* Count of index entries */
 (void) get4(*file_length, q->f); /* Size of expanded file */
 (void) get2(*blocksize, q->f);
 return (0 == 0);
}


/***************************** STDIO EMULATION *****************************/

static int qfiles_open=0;

int
qfclose(QFILE * q)
{
 if (q == NULL) {
 sperror(__LINE__, __FILE__, "fileio.c: qfclose(NULL) ?\n");
 return (0 != 0);
 }
 if (q->q_magic != Q_MAGIC) { /* implies X_MAGIC */
 return (FCLOSE((BINARYFILE) q));
 }
 decomp_terminate();
 if (FCLOSE(q->f) != 0)
 WRERR(out); /* less than informative choice of error
 * message */

 /* Still to unhook this Q file from the linked list of Q files. */
 qfiles_open--;
 if (qfiles_open == 0)
 {
 free(QFile_global_inbuff);
 QFile_global_inbuff = NULL;
 QFile_global_insize=0;
 }
 if (q->blockstart != NULL) {
 free(q->blockstart);
 q->blockstart = NULL;
 }
 free(q);
 return (0 == 0);
}



QFILE *
qfopen(const char *file, const char *fmode)
{
 QFILE *q = NULL;
 static long gfid=0L;

 qfiles_open += 1;
 if (*file == '\0') {
 sperror(__LINE__, __FILE__, "fileio.c: "
 "qfopen() should not be passed a null string unless debugging\n");
 qfiles_open -= 1; /* undo error */
 return (NULL);
 }
 Check(q = malloc(sizeof(QFILE)));
 q->q_magic = Q_MAGIC;
 q->f = FOPEN(file, RB);
 q->filepos = 0L;
 q->yellow_card = (0 != 0);
 q->cache_present = (0 == 0);
 q->next = NULL; /* For use in LRU of buffers */
 q->blockstart = NULL;
 q->outbuff = NULL;
 q->fid = gfid++;

 if (q->f == BADFILE) {
 sperror(__LINE__, __FILE__, "unsquidge: cannot open input file '%s'\n", file);
 qfiles_open -= 1; /* undo error */
 return (NULL);
 }
 if (!READ_HEADER(q, &q->idx_pos, &q->idx_entries,
 &q->file_length, &q->blocksize)) { /* Might leak heap? */
#ifdef WARN_NON_SQUIDGE
 sperror(__LINE__, __FILE__,
 "qfopen: file %s is not a squidged file! (re-opening as ordinary file)\n",
 file);
#endif
 FCLOSE(q->f);
 free(q);
 qfiles_open -= 1; /* Don't count non-Q files when keeping tally */
 return ((QFILE *)FOPEN(file, fmode)); /* NOTE: RETURNING XFILE * NOW (X_MAGIC?) */
 }
 if (q->blocksize > MAXBUFF) {
 sperror(__LINE__, __FILE__, "Dunno how this happened: q->blocksize = %u, MAXBUFF = %u\n",
 q->blocksize, MAXBUFF);
 }
 /* Check(q->inbuff = malloc(q->blocksize)); */
 if (QFile_global_inbuff == NULL) {
 Check(QFile_global_inbuff = malloc(q->blocksize));
 QFile_global_insize=q->blocksize;

 /* Needs better error testing if fails */
 } else {
 if (q->blocksize > QFile_global_insize) {
 free(QFile_global_inbuff); /* effectively, realloc it at the
 * larger size */
 Check(QFile_global_inbuff = malloc(q->blocksize));
 QFile_global_insize=q->blocksize;
 }
 }
 PRELOAD_INDEX(q, 0, MAX_CACHE_ENTRIES);
 /* this one is probably OK though */
 /* A value which can't happen (ie no valid buffer) */
 q->blocknum = INVALID_BUFFER;
 decomp_init(q->blocksize);
 return (q);
}


size_t
qfread(void *ptr, size_t size, size_t nmemb, QFILE * stream)
{
 long bytes;

 if (stream == NULL) {
 sperror(__LINE__, __FILE__, "fileio.c: qfread(ptr, %d, %d, NULL)\n", size, nmemb);
 exit(1);
 }
 if (stream->q_magic != Q_MAGIC) { /* NOTE: IMPLIES X_MAGIC */
 return (FREAD(ptr, size, nmemb, (BINARYFILE) stream));
 }
 /*
 * In some cases where you didn't get everything -- you asked for 'units'
 * complete items (eg if size = 4, and nmemb = 10, you expected 40 bytes
 * and got 38, you will receive 9 as the result) -- the file-pointer may
 * be two bytes further on than you expect.
 */
 bytes = qprivread(stream, ptr, (long) size * (long) nmemb);
 return ((size_t) (bytes / (long) size));
}


int
qfseek(QFILE * q, long int where, int seektype)
{
 int rc = 0;
 if (q == NULL) {
 sperror(__LINE__, __FILE__, "fileio.c: qfseek(NULL, %ld, %d)\n", where, seektype);
 exit(1);
 }
 if (q->q_magic != Q_MAGIC) { /* NOTE: IMPLIES X_MAGIC */
 return (FSEEK((BINARYFILE) q, where, seektype));
 }
 q->yellow_card = (0 != 0);
 if (seektype == SEEK_SET) {
 q->filepos = where; /* absolute move */
 } else if (seektype == SEEK_END) {
 if (where > 0L) {
 sperror(__LINE__, __FILE__, "%s%ld%s\n", "qfseek(file, ", where,
 ", SEEK_SET) -- are you sure it shouldn't be a negative offset?");
 where = -where;
 rc = -1;
 }
 q->filepos = q->file_length + where; /* end of file */
 } else if (seektype == SEEK_CUR) {
 q->filepos += where; /* relative move */
 } else {
 sperror(__LINE__, __FILE__, "qfseek: unknown seek type %d\n", seektype);
 rc = -1;
 }
 if (q->filepos < 0L) {
 sperror(__LINE__, __FILE__,
 "qfseek: warning -- seeked to before start of file! (offset %ld)\n",
 q->filepos);
 q->filepos = 0L;
 rc = -1;
 }
 if (q->filepos > q->file_length) {
 sperror(__LINE__, __FILE__,
 "qfseek: warning -- seeked to after end of file! (offset %ld)\n",
 q->filepos);
 q->filepos = q->file_length;
 rc = -1;
 }
 return (rc);
}

long
qftell(QFILE * q)
{
 if (q == NULL) {
 sperror(__LINE__, __FILE__, "fileio.c: qftell(NULL)\n");
 exit(1);
 }
 if (q->q_magic != Q_MAGIC) { /* IMPLIES X_MAGIC */
 return (FTELL((BINARYFILE) q));
 }
 if (q == NULL)
 return (0L); /* perhaps -1? *//* Or exit? */
 return (q->filepos);
}


/* I put this back in -- mark had removed it. need it for zcat test prog. */
int
qfgetc(QFILE * stream)
{
 char c;
 if (stream == NULL) {
 sperror(__LINE__, __FILE__, "fileio.c: qfgetc(NULL)\n");
 exit(1);
 }
 if (stream->q_magic != Q_MAGIC) {
 return (FGETC((FILE *) stream));
 }
 /* Hideously non-optimal at the moment. Could be speeded up *A LOT* */
 if (qprivread(stream, &c, 1) != 1)
 return (EOF);
 return (c);
}

/*
 * These sneaky "#define"s are to stop mkptypes putting them in the header
 * file :-)
 */

EXITDECL
{
 exit(rc); /* allows use in expressions :-) */
 return (rc);
}

#ifdef DBMAIN
char *q_pos = NULL;
void qstart(void *source,long start,long finish,char *qbuffer)
{
size_t did_read;

 if (qfseek((QFILE *)source,start,SEEK_SET) == -1L)
 {
 sperror(__LINE__,__FILE__,"Seek fail from source file @%ld",start);
 sexit(1);
 }
 if ((did_read =
 qfread(qbuffer,1,(size_t)(finish - start),(QFILE *)source)) !=
 (size_t)(finish - start))
 {
 sperror(__LINE__,__FILE__,"Read fail from source file @%ld, %d<>%d",
 start,did_read,(size_t)(finish - start));
 sperror(__LINE__,__FILE__,"Error number = %d, %s",errno,strerror(errno));

 sexit(1);
 }

 qbuffer[finish - start - 1] = '\0';

#ifdef DBMAIN
 spprint("Just read %d bytes to %p\n",(size_t)(finish - start),qbuffer);
#endif
 q_pos = qbuffer;
 return;
}




#define OUTFILE "fileio.$$$"
#define MAX_FILE 32
#define MAINDECL int main(int argc, char **argv)

#define MAXITS 10

MAINDECL
{
int i;
int j;
int r;
boolean wrote;
QFILE *q_file[MAX_FILE];
char buffer[512];
XFILE *outfile;
int count;

 for (i = 0;i < MAX_FILE;i++)
 q_file[i] = NULL;

 i = 0;
 while (--argc)
 {
 q_file[i++] = qfopen(*++argv,"rb");
 if (i >= MAX_FILE)
 break;
 }

 for (j = 0; j < i;j++)
 {
 qfread(buffer,1,128,q_file[j]);
 qfseek(q_file[j],0L,SEEK_SET);
 }
#ifdef never
 qstart(q_file[j],0,128,buffer);
#endif

 if ((outfile = xfopen(OUTFILE,"w")) == NULL)
 {
 sperror(__LINE__,__FILE__,"Can't open %s\n",OUTFILE);
 exit(1);
 }

 for (count = 0;count<MAXITS;count++)
 {
 for (j = 0; j < i;j++)
 {
 wrote = FALSE;
 if (q_file[j] != NULL)
 {
 sperror(__LINE__,__FILE__,"File %d\n",j);
 if ((r = qfread(buffer,1,sizeof(buffer),q_file[j] )) > 0)
 {
 xfwrite(buffer,1,r,outfile);
 sperror(__LINE__,__FILE__,"Block = %d\n",r);
 wrote = TRUE;
 }
 else
 {
 qfclose(q_file[j] );
 q_file[j] = NULL;
 }
 }
 }
 if (wrote == FALSE)
 break;
 }
 FreeSFCB();
 exit(0);
 return 0;
}
#endif
