/*
 * nushk.c - P8 ShrinkIt compress/uncompress routines
 *
 * By Andy McFadden (fadden@cory.berkeley.edu)
 * NuLib v2.2  April 1990  Freeware (distribute, don't sell)
 */

#include "nudefs.h"
#include <stdio.h>
#include <fcntl.h>

#ifdef MSDOS     /* For file IO */
# include <io.h>
# include <sys\types.h>
# include <sys\stat.h>
# include <errno.h>
#endif

#include "nuread.h"  /* need CalcCRC() */
#include "nupak.h"
#include "nuetc.h"

#define BLKSIZ	4096
/*#define DEBUG  /* do verbose debugging output */
/*#define DEBUG1  /* debugging output in main routine */

static onebyt *ibuf;	/* large buffer (usually set to packBuffer) */
onebyt lbuf[BLKSIZ+1];	/* temporary buffer for storing data after LZW */
onebyt rbuf[BLKSIZ+1];	/* temporary buffer for storing data after RLE */


/*
 * Fake ShrinkIt compression routines.
 *
 * Only removes repeated characters; doesn't actually do the LZW.  This
 * means that the compression achieved will not be all that great (if it
 * compresses at all).
 */

#define ESCAPE_CHAR	0xdb

/*
 * Do run-length encoding
 *
 * Takes input from srcptr, and writes to dstptr.  Maximum expansion is
 * (BLKSIZ / 2) + (BLKSIZ / 2) * 3 == 2 * BLKSIZ
 * Output of form  <DLE> char count
 */
int do_RLE(srcptr, dstptr)
onebyt *srcptr, *dstptr;
{
    int found, scount, dcount;
    onebyt c, lastc, tlastc;

    c = *(srcptr++);  scount = 1;
    dcount = 0;
    found = 1;  /* one char has been found */
    lastc = '\0';
    while (scount < BLKSIZ) {
	tlastc = lastc;
	lastc = c;
	c = *(srcptr++);  scount++;

	if (found == 1) {  /* no run found */
	    if (c != lastc) {  /* no run starting */
		if (lastc == ESCAPE_CHAR) {
		    *(dstptr++) = ESCAPE_CHAR;  dcount++;
		    *(dstptr++) = lastc;  dcount++;
		    *(dstptr++) = 0;  dcount++;  /* found one */
		} else {
		    *(dstptr++) = lastc;  dcount++;
		}
		found = 1;
	    } else {
		found = 2;  /* they matched, so two in a row */
	    }

	} else if (found == 2) {  /* got two, try for three */
	    if (c != lastc) {  /* only got two in a row */
		if (lastc == ESCAPE_CHAR) {  /* and tlastc as well */
		    *(dstptr++) = ESCAPE_CHAR;  dcount++;
		    *(dstptr++) = lastc;  dcount++;
		    *(dstptr++) = 1;  dcount++;  /* found two */
		} else {
		    *(dstptr++) = tlastc;  dcount++;
		    *(dstptr++) = lastc;  dcount++;
		}
		found = 1;
	    } else {  /* found 3, got a run going */
		found = 3;
	    }

	} else {  /* (found >= 3), got a run going */
	    if (c == lastc) {  /* found another */
		found++;
	    }
	    if ((c != lastc) || (found > 256)) {  /* end, or too many */
		*(dstptr++) = ESCAPE_CHAR;  dcount++;
		*(dstptr++) = lastc;  dcount++;
		*(dstptr++) = (found > 256) ? 255 : found-1;
		dcount++;
		found = 1;  /* c has something other than the run char	*/
			    /* or found is 257-256 = 1		 	*/
	    }
	}
    }  /* while */

    /* reached end of buffer; flush what was left */
    if (found == 1) {
	if (c == ESCAPE_CHAR) {
	    *(dstptr++) = ESCAPE_CHAR;  dcount++;
	    *(dstptr++) = c;  dcount++;
	    *(dstptr++) = 0;  dcount++;
	} else {
	    *(dstptr++) = c;  dcount++;
	}

    } else if (found == 2) {
	/* maybe have if lastc == c == ESCAPE_CHAR? */
	if (lastc == ESCAPE_CHAR) {
	    *(dstptr++) = ESCAPE_CHAR;  dcount++;
	    *(dstptr++) = lastc;  dcount++;
	    *(dstptr++) = 0;  dcount++;
	} else {
	    *(dstptr++) = lastc;  dcount++;
	}
	if (c == ESCAPE_CHAR) {
	    *(dstptr++) = ESCAPE_CHAR;  dcount++;
	    *(dstptr++) = c;  dcount++;
	    *(dstptr++) = 0;  dcount++;
	} else {
	    *(dstptr++) = c;  dcount++;
	}

    } else {  /* found >= 3, in the middle of processing a run */
	*(dstptr++) = ESCAPE_CHAR;  dcount++;
	*(dstptr++) = c;  dcount++;
	*(dstptr++) = found-1;  dcount++;
    }

    return (dcount);
}


/*
 * Main entry point.
 *
 * Returns actual thread_format used.
 */
long pak_SHK(srcfd, dstfd, length, copybuf)
int srcfd, dstfd;
long length;		/* uncompressed size */
onebyt *copybuf;
{
    unsigned int partial;   /* size for partial read/write */
    onebyt *rptr;
    register int idx;
    onebyt scratch[8];
    long srcposn,	/* start in source file */
	 startposn,	/* start in dest file */
	 endposn;	/* end in dest file */
    long unc_len = length,
	 comp_len = 0L;
    twobyt CRC;
    int rlesize;  /* length after RLE */
    int sc;  /* spin counter */
    static char *procName = "pak_SHK";

    CRC = 0;
    if ((srcposn = lseek(srcfd, 0L, S_REL)) < 0)	/* only used if */
	Fatal("Bad seek (srcposn)", procName);		/* compress fails */
    if ((startposn = lseek(dstfd, 0L, S_REL)) < 0)
	Fatal("Bad seek (startposn)", procName);
    lseek(dstfd, 4L, S_REL);  /* leave room for 4-byte header */
    comp_len += 4L;

    sc = 0;
    do {  /* have to handle when length == 0L */
	if (length > (long) BLKSIZ) {
	    partial = (unsigned int) BLKSIZ;
	    length -= (long) BLKSIZ;
	} else {
	    partial = (unsigned int) length;
	    length = 0L;
	    for (idx = partial; idx < BLKSIZ; idx++)  /* fill in zeroes */
		*(copybuf + idx) = 0;
	}

	if (partial > 0) {  /* should work anyway, but let's be careful */
	    if (read(srcfd, copybuf, partial) != partial)
		Fatal("Source read failed", procName);
	}
	/* calc CRC on all 4096 bytes */
	CRC = CalcCRC(CRC, (onebyt *) copybuf, BLKSIZ); 
	rlesize = do_RLE(copybuf, copybuf + BLKSIZ+1);  /* pack 4096 bytes */
	if (rlesize < 0x1000) {  /* did it pack or expand? */
	    rptr = copybuf + BLKSIZ+1;  /* use packed version */
	} else {
	    rlesize = 0x1000;  /* just store original */
	    rptr = copybuf;
	}
	scratch[0] = (onebyt) (rlesize & 0x00ff);
	scratch[1] = (onebyt) ((rlesize >> 8) & 0x00ff);
	scratch[2] = 0;  /* LZW off */
	if (write(dstfd, scratch, 3) != 3)
	    Fatal("Dest hdr write failed", procName);
	comp_len += 3;
	comp_len += rlesize;
	if (comp_len > unc_len) {
	    goto bad_compress;		/* you didn't see this */
	}
	if (write(dstfd, rptr, rlesize) != rlesize)  /* need to do CRLF */
	    Fatal("Dest write failed", procName);

	sc++;
	if (sc == 15) {
	    sc = 0;
	    Spin();
	}
    } while (length != 0L);

    if ((endposn = lseek(dstfd, 0L, S_REL)) < 0)
	Fatal("Bad seek (now)", procName);
    if (lseek(dstfd, startposn, S_ABS) < 0)
	Fatal("Bad seek (to4)", procName);
    scratch[0] = (char) CRC;
    scratch[1] = (char) (CRC >> 8);
    scratch[2] = 0;
    scratch[3] = ESCAPE_CHAR;
    if (write(dstfd, scratch, 4) != 4)
	Fatal("Dest hdr write failed", procName);
    if (lseek(dstfd, endposn, S_ABS) < 0)
	Fatal("Bad seek (last)", procName);

    if (comp_len != endposn - startposn) {
	printf(
	    "internal error: comp_len=%ld, endposn=%ld, startposn=%ld (%ld)\n",
		comp_len, endposn, startposn, endposn - startposn);
    }
    packedSize = comp_len;
    return (0x0002);	/* SHK packing */

bad_compress:	/* I'm too lazy to do a procedure call... */

    if (verbose) { printf("storing...");  fflush(stdout); }
    if (lseek(srcfd, srcposn, S_ABS) < 0)
	Fatal("Bad seek (srcposn in bad_compress)", procName);
    if (lseek(dstfd, startposn, S_ABS) < 0)
	Fatal("Bad seek (startposn in bad_compress)", procName);
    FCopy(srcfd, dstfd, unc_len, copybuf, FALSE);
    packedSize = unc_len;
    return (0x0000);	/* no compression */
}


/*
 * P8 ShrinkIt uncompression routines
 *
 * Copyright 1989 Kent Dickey
 * C translation by Kent Dickey / Andy McFadden
 * Modifications for LZW-II by Andy Nicholas / Andy McFadden
 */

static int inf;  /* to make Getc() calls happy */
static BOOLEAN type2;	/* true if working with LZW-II format */

static onebyt escape_char;

typedef struct {
    unsigned char chr;
    int prefix;
} Table_ent;

static Table_ent Real_tab[BLKSIZ-256];  /* first 256 don't exist */
static Table_ent *Table;

static int Mask_tab[16] = {
	0x0000, 0x01ff, 0x03ff, 0x03ff, 0x07ff,
	0x07ff, 0x07ff, 0x07ff, 0x0fff, 0x0fff,
	0x0fff, 0x0fff, 0x0fff, 0x0fff, 0x0fff,
	0x0fff
};
static int Number[16] = {
	0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 };

static onebyt Stack[100];  /* simulated stack; should be <= 64 */
static int out_bytes, stack_ptr, entry, at_bit, at_byte;
static onebyt last_byte;  /* used in get_code */


/* fake Getc(); easier to make this a macro than to change the code */
#ifdef DEBUG
onebyt Getc(foo)
int foo;  /* this is ignored */
{
    return (*(ibuf++));
}
#else  /* if not debugging, use a macro */
# define Getc(foo) *(ibuf++)
#endif /* DEBUG */


/*
 * Stack operations; used by undo_LZW
 */
#ifdef DEBUG
void push(a_byte)
unsigned char a_byte;
{
    if (stack_ptr > 100) {
	printf("\n*** stack_ptr exceeded 100 in push() [%d]\n", stack_ptr);
	exit (-1);
    }
    Stack[stack_ptr++] = a_byte;
}
#else  /* if not debugging, use a macro */
# define push(a_byte) Stack[stack_ptr++] = a_byte
#endif /* DEBUG */


#ifdef DEBUG
void dump_stack(buffer)
unsigned char *buffer;
{
    printf("--- Going to dump stack, stack_ptr = %d, out_bytes = %d\n",
	stack_ptr, out_bytes);
    while (stack_ptr > 0) {
	*(buffer + out_bytes++) = Stack[--stack_ptr];
    }
}
#else  /* if not debugging, use a macro */
# define dump_stack(buffer) while (stack_ptr > 0) { \
				*( buffer +out_bytes++) = Stack[--stack_ptr];\
			    }
#endif /* DEBUG */


/*
 * Decipher LZW codes.
 */
static int get_code(/*Buffer*/)
/*unsigned char *Buffer;*/
{
    int num_bits, old_bit, last_bit;
    long value, mask;
    unsigned char byte1, byte2, byte3;  /* get compressed chars... */

#ifdef DEBUG
    printf("ENT: bit=%d byte=%-4d last_byte=$%.2x ",
	at_bit, at_byte, last_byte);
    printf("Entry: %.4x \n", entry);
#endif

    num_bits = ((entry+1) >> 8);  /* get hi-byte of entry */
    last_bit = at_bit + Number[num_bits] + 8;
    old_bit = at_bit;
#ifdef DEBUG
    if (at_byte >= BLKSIZ) {
	fprintf(stderr, "at_byte exceeded BLKSIZ (4096) in get_code()\n");
	exit (-1);
    }
#endif
    if (at_bit == 0)
	last_byte = Getc(inf);
    byte1 = last_byte;  /* first byte = last one used */
    byte2 = Getc(inf);
    if (last_bit > 16) {  /* get 3rd byte if nec. */
	byte3 = Getc(inf);
	last_byte = byte3;
    } else {
	byte3 = 0;
	last_byte = byte2;
    }
    value = ((((long)byte3 << 8) + (long)byte2) << 8) + (long)byte1;
/*    value = (((Buffer[at_byte+2] << 8) + Buffer[at_byte+1]) << 8) +	*/
/*	Buffer[at_byte];						*/

    mask = (long) Mask_tab[num_bits];
    at_byte += (last_bit >> 3);  /* new byte */
    at_bit = (last_bit & 0x07);

#ifdef DEBUG
    printf("| EX: value=$%.6x mask=$%.4x return=$%.3x\n",
	value, mask, ((value >> old_bit) & mask));
#endif
    if (old_bit)
	return ((value >> old_bit) & mask);
    else
	return (value & mask);  /* shifting by zero may be undefined */
}


/*
 * Un-LZW a range of bytes
 *
 * Reads data with get_code (eventually from packBuffer) and stores the
 * output in "buffer".
 */
static void undo_LZW(buffer, length)
unsigned char *buffer;  /* where to put output */
int length;  /* uncompressed length of output */
{
    int oldc, incode, finalc, ptr;

    /* initialize variables */
    Table = Real_tab-256;
    entry = 0x101;  /* start at $101 */
    at_bit = at_byte = 0;
    out_bytes = 0;
    stack_ptr = 0;

    last_byte = 0;  /* init last_byte */
    oldc = incode = get_code(/*buffer*/);
    finalc = (oldc & 0xff);
    *(buffer + out_bytes++) = (unsigned char) incode;

    /* main loop */
    while (out_bytes < length) {
	incode = ptr = get_code(/*buffer*/);
	if (ptr >= entry) {
	    push(finalc);
	    ptr = oldc;
	}
	while (ptr > 0xff) {
	    push(Table[ptr].chr);
	    ptr = Table[ptr].prefix;
	}

	/* ptr is now < 0x100 */
	finalc = ptr;
	push(finalc);
	dump_stack(buffer);
	Table[entry].chr = (finalc & 0xff);  /* mask to get unsigned?? byte */
	Table[entry].prefix = oldc;
	entry++;
	oldc = incode;
    }
}


/*
 * Second pass... undo the Run Length Encoding.
 *
 * Copy data from inbuffer to outbuffer.  Keep going until we've got
 * exactly BLKSIZ bytes.  Note that this uses codes of the form
 *   <DLE> char count
 * which is different from some other programs.
 */
static void undo_RLE(inbuffer, outbuffer)
unsigned char *inbuffer, *outbuffer;
/*int length;  /* how many bytes from LZW; just to make sure... */
{
    int total, count;  /* count is RLE reps */
    unsigned char c;

#ifdef DEBUG
    /*printf("Starting undo_RLE, length = %d\n", length);*/
#endif
    total = 0;
    while (total < BLKSIZ) {
	c = *(inbuffer++);  /*length--;*/
	if (c == (onebyt) escape_char) {
	    c = *(inbuffer++);  /*length--;*/
	    count = *(inbuffer++);  /*length--;*/
	    total += count +1;  /* count of zero -> 1 byte */
	    while (count-- >= 0) {
		*(outbuffer++) = c;  /*Putc(c, outf);*/
	    }
	} else {
	    *(outbuffer++) = c;  /*Putc(c, outf);*/
	    total++;
	}
    }

    if (total != 4096)
	fprintf(stderr, "internal error: bad undo_RLE\n");
#ifdef DEBUG
/*    printf("Exiting undo_RLE, length = %d (should be 0), total = %d (4096)\n",
	length, total);*/
#endif
}


/*
 * Main entry point.
 *
 * This is among the more hellish things I've written.  Uses
 *   a large buffer for efficiency reasons, and unpacks a stream of bytes
 *   (LZW-II improves things a little).
 * If you find this hard to understand, imagine what it was like to debug.
 *
 * Could use some cleaning up, esp argument list...
 */
void
unpak_SHK(srcfd,dstfd,comp_thread_eof,thread_eof,buffer, use_type2, thread_crc)
int srcfd, dstfd;
fourbyt comp_thread_eof, thread_eof;
onebyt *buffer;
BOOLEAN use_type2;		/* true if we should expect LZW-II */
twobyt thread_crc;
{
    twobyt CRC, blkCRC;
    onebyt vol;
    onebyt *wrbuf;  /* points to buffer we're about to write */
    short unlen, lzwflag, rleflag, complen;	/* should be short */
    unsigned int partial, toread, still_in_buf;
    fourbyt tmp4;  /* temporary 4-byte variable */
    int cc;
    static char *procName = "unpak_SHK";

    CRC = 0;
    type2 = use_type2;

    /* read min(PAKBUFSIZ, comp_thread_eof) bytes into buffer */
    if (comp_thread_eof > (fourbyt) PAKBUFSIZ) {
	toread = (unsigned int) PAKBUFSIZ;
	comp_thread_eof -= (fourbyt) PAKBUFSIZ;
    } else {
	toread = (unsigned int) comp_thread_eof;  /* read it all... */
	comp_thread_eof = (fourbyt) 0;
    }

    /* do initial read */
#ifdef DEBUG1
    printf("initial read = %u\n", toread);
#endif
    if ((cc = read(srcfd, buffer, toread)) < toread) {
#ifdef DEBUG1
	printf("Only read %d bytes\n", cc);
#endif
	Fatal("Bad read during uncompress", procName);
    }
    ibuf = buffer;  /* set input pointer to start of buffer */

    /* get header data */
    if (type2) {
	blkCRC = thread_crc;
    } else {
	blkCRC = Getc(inf);
	blkCRC += (Getc(inf) << 8);
    }
    vol = (char) Getc(inf);  /* disk volume #; not used here */
    escape_char = (char) Getc(inf);  /* RLE delimiter */

#ifdef DEBUG1
    printf("vol = %d, escape_char = %x\n", vol, escape_char);
#endif

    /*
     * main loop
     */
    while (thread_eof != (fourbyt) 0) {

	/* note "unlen" is un-LZWed length (i.e., after RLE) */
	if (type2) {
	    unlen = Getc(inf);
	    unlen += (Getc(inf) << 8);
	    lzwflag = (unlen & 0x8000) ? 1 : 0;	/* flag is hi bit */
	    unlen &= 0x1fff;			/* strip extra stuff */
	    rleflag = (unlen != BLKSIZ);
	    if (lzwflag) {	/* will the real length bytes please stand up*/
		complen = Getc(inf);
		complen += (Getc(inf) << 8);
	    }
	} else {
	    unlen = Getc(inf);
	    unlen += (Getc(inf) << 8);
	    lzwflag = Getc(inf);
	    rleflag = (unlen != BLKSIZ);
	}
#ifdef DEBUG1
	printf("Length after RLE = %d ($%.4x)\n", unlen, unlen);
	printf("LZW flag = %d, RLE flag = %d\n", lzwflag, rleflag);
	if (lzwflag != 0 && lzwflag != 1) {  /* this is weird... */
	    for (lzwflag = -6; lzwflag < 3; lzwflag++) {
		printf("foo %d: %.2x\n", lzwflag, *(ibuf+lzwflag));
	    }
	}
	if (type2 && lzwflag) {
	    printf("Length after RLE+LZW = %d ($%.4x)\n", complen, complen);
	}
#endif

	/* If it looks like we're going to run out of room, shift & read
	/* Mostly a guess; LZW length is less than unlen...  This is
	/* complicated and very prone to errors.
	/* tmp4 is the number of bytes between the current ptr and the end;
	/* some (16-bit) compilers yack if it's all one statement.*/
	tmp4 = (fourbyt) buffer + (fourbyt) PAKBUFSIZ;
	tmp4 -= (fourbyt) ibuf;
	if (tmp4 < (unlen + 6)) {  /* 6 = 3/4 byte header + two just in case */
	    still_in_buf = tmp4;

#ifdef DEBUG1
	    printf("--- unlen = %d, space left = %d bytes\n",
		    unlen, still_in_buf);
#endif
	    BCopy((onebyt *) ibuf, (onebyt *) buffer, still_in_buf, FALSE);
	    if (comp_thread_eof != (fourbyt) 0) {  /* no read, just shift */
		if (comp_thread_eof > ((fourbyt) PAKBUFSIZ - still_in_buf)){
		    toread = (unsigned int) PAKBUFSIZ - still_in_buf;
		    comp_thread_eof -= (fourbyt) PAKBUFSIZ - still_in_buf;
		} else {
		    toread = (unsigned int) comp_thread_eof;
		    comp_thread_eof = (fourbyt) 0;
		}
#ifdef DEBUG1
		printf("--- reading another %u bytes\n", toread);
#endif
		if (read(srcfd, buffer+still_in_buf, toread) < toread)
		    Fatal("Unable to read [middle]", procName);
		if (verbose) Spin();
	    }
	    ibuf = buffer;
	}
    
	/* how much of the buffered data do we really need? */
	if (thread_eof > (fourbyt) BLKSIZ) {
	    partial = (unsigned int) BLKSIZ;
	    thread_eof -= (fourbyt) BLKSIZ;
	} else {
	    partial = (unsigned int) thread_eof;  /* last block of file */
	    thread_eof = (fourbyt) 0;
	}

	/*
	 * undo_LZW reads from ibuf (using Getc()) and writes to lbuf
	 * undo_RLE reads from where you tell it and writes to rbuf
	 *
	 * This is really insane...
	 */
	if (lzwflag && rleflag) {
	    undo_LZW(lbuf, unlen);  /* from ibuf -> lbuf */
	    undo_RLE(lbuf, rbuf);  /* from lbuf -> rbuf */
	    wrbuf = rbuf;  /* write rbuf */
	    CRC = CalcCRC(CRC, (onebyt *) rbuf, BLKSIZ);  /* always 4K bytes */
	} else if (lzwflag && !rleflag) {
	    undo_LZW(lbuf, unlen);  /* from ibuf -> lbuf */
	    wrbuf = lbuf;  /* write lbuf */
	    CRC = CalcCRC(CRC, (onebyt *) lbuf, BLKSIZ);
	} else if (!lzwflag && rleflag) {
	    undo_RLE(ibuf, rbuf);  /* from ibuf -> rbuf */
	    wrbuf = rbuf;  /* write rbuf */
	    CRC = CalcCRC(CRC, (onebyt *) rbuf, BLKSIZ);
	    ibuf += unlen;  /* have to skip over RLE-only data */
			/* normally ibuf is advanced by Getc() calls */
	} else {
	    wrbuf = ibuf;  /* write ibuf */
	    CRC = CalcCRC(CRC, (onebyt *) ibuf, BLKSIZ);
	    ibuf += partial;  /* skip over uncompressed data */
			/* normally ibuf is advanced by Getc() calls */
	}
#ifdef DEBUG1
	printf("Writing %d bytes.\n", partial);
#endif
	if (crlf(dstfd, wrbuf, partial) < partial)  /* write wrbuf */
	    Fatal("Bad write", procName);
    }

    if (CRC != blkCRC) {
	fprintf(stderr, "WARNING: CRC does not match...");
	if (verbose) fprintf(stderr, "\n");
	else fprintf(stderr, "extract with V suboption to see filenames.\n");
    }
}

