/*
 * nusq.c - Huffman squeeze/unsqueeze routines
 *
 * NuLib v3.2  March 1992  Freeware (distribute, don't sell)
 * By Andy McFadden (fadden@netcom.com)
 *
 * $Id: nusq.c,v 1.4 1996/11/28 06:16:35 gdr Exp $
 */

#define SEGMENT_NAME "Compress__"

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

#ifdef UNIX
# include <sys/types.h>
# include <unistd.h>
#endif

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

#if defined(__ORCAC__) && !defined(__GNO__)
extern FILE *fdopen(int, char *);
#endif

#if defined(__sun__) && !defined(__SVR4)
#include "sunos4.h"
#endif

#include "nuetc.h"
#include "nuread.h"
#include "nupak.h"

/*
 * usq.c - undo Huffman coding
 * Adapated from code By Marcel J.E. Mol
 * Based on sq3/usq2 by Don Elton
 *
 * Squeezed file format:
 *     2 bytes MAGIC
 *     2 bytes dummy ???  (maybe CRC or checksum; not checked)
 *     filename ended by \0
 *
 *     2 bytes node count
 *     node count node values, each 2 bytes
 *     squeezed data per byte
 *
 * NuFX SQueezed format includes only the node count, node values, and
 *   the data.  The BLU routines are expected to strip off the MAGIC,
 *   checksum, and filename before calling this.
 */

/*char *copyright = "@(#) usq.c  2.1 18/06/88  (c) M.J.E. Mol";*/
#define BUFSIZE 128
#define MAGIC	0xff76		       /* Squeezed file magic */
#define DLE 0x90		       /* repeat byte flag */
#define NOHIST 0		       /* no relevant history */
#define INREP 1 		       /* sending a repeated value */
#define SPEOF 256		       /* special endfile token */
#define NUMVALS 257		       /* 256 data values plus SPEOF */

/* global variable declarations */
char *sfn;			       /* squeezed file name */
struct nd {			       /* decoding tree */
    int child[2];		       /* left, right */
} node[NUMVALS];		       /* use large buffer */
int state;			       /* repeat unpacking state */
int bpos;			       /* last bit position read */
int curin;			       /* last byte value read */
int numnodes;			       /* number of nodes in decode tree */

static unsigned char fromc;	/* for use in text translation */
static BOOLEAN trbool;		/* BOOLEAN version of transfrom */


/* Get an integer from the input stream */
static twobyt
#ifdef __STDC__
get_int(FILE *f)
#else
get_int(f)
FILE *f;
#endif
{
    twobyt val;

    val = (twobyt)getc(f);
    val += (twobyt)getc(f) << 8;
    return (val);
}


static int
#ifdef __STDC__
getc_usq(FILE *f)
#else
getc_usq(f) 		       /* get byte from squeezed file */
FILE *f;			       /* file containing squeezed data */
#endif
{
    register short i;		       /* tree index */

    /*
     * Follow bit stream in tree to a leaf.
     */
#if SHRT_MAX == 0x7fff
#define CHECK_ONE 
#else
#define CHECK_ONE (i <= 0x7fff) &&
#endif

    for (i=0; CHECK_ONE (i>=0); )/* work down(up?) from root */
    {

#undef CHECK_ONE
	 if (++bpos > 7) {
	      if ((curin=getc(f)) == EOF)
		   return(EOF);
	      bpos = 0;

	      /* move a level deeper in tree */
	      i = node[i].child[1 & curin];
	 }
	 else i = node[i].child[1 & (curin >>= 1)];
    }

    /* decode fake node index to original data value */
    i = -(i + 1);

    /* decode special endfile token to normal EOF */
    return ((i==SPEOF) ? EOF : i);
}


/*  putc-ncr -- decode non-repeat compression.	Bytes are passed one
 *		at a time in coded format, and are written out uncoded.
 *		The data is stored normally, except that runs of more
 *		than two characters are represented as:
 *
 *			 <char> <DLE> <count>
 *
 *		With a special case that a count of zero indicates a DLE
 *		as data, not as a repeat marker.
 */
static void
#ifdef __STDC__
putc_ncr(unsigned char c, FILE *t)
#else
putc_ncr(c, t)	       /* put NCR coded bytes */
unsigned char c;		       /* next byte of stream */
FILE *t;			       /* file to receive data */
#endif
{
    static int lastc;	      /* last character seen */

    /* if converting line terminators, do so now */
    if (trbool && (c == fromc))
#ifdef __appleiigs__
	c = 0x0d;
#elif defined(UNIX)
	c = 0x0a;
#else
	c = 0x0d;  /* No CRLF stuff in unSQueeze... sorry */
#endif

    switch (state) {		       /* action depends on our state */
	case NOHIST:		       /* no previous history */
	    if (c==DLE) 	       /* if starting a series */
		 state = INREP;        /* then remember it next time */
	    else putc(lastc=c, t);     /* else nothing unusual */
	    return;

	case INREP:		       /* in a repeat */
	    if (c)		       /* if count is nonzero */
		while (--c)	       /* then repeatedly ... */
		    putc(lastc, t);    /* ... output the byte */
	    else putc(DLE, t);	       /* else output DLE as data */
	    state = NOHIST;	       /* back to no history */
	    return;

	default:
	    fprintf(stderr, "%s: bad NCR unpacking state (%d)",
				prgName, state);
    }
}


static int
#ifdef __STDC__
init_usq(FILE *f)
#else
init_usq(f) 		       /* initialize Huffman unsqueezing */
FILE *f;			       /* file containing squeezed data */
#endif
{
    register int i;		       /* node index */

    switch (transfrom) {
    case -1:  /* no translation */
	trbool = 0;
	break;
    case 0:  /* from ProDOS */
	trbool = 1;
	fromc = 0x0d;
	break;
    case 1:  /* from UNIX */
	trbool = 1;
	fromc = 0x0a;
	break;
    case 2:  /* from MS-DOS... this needs fixing */
	trbool = 1;
	fromc = 0x0a;  /* just turn LFs into whatever... */
	break;
    default:  /* unknown */
	fprintf(stderr, "%s: unknown translation type %d\n", prgName, trbool);
	fprintf(stderr, "%s: assuming conversion from CR\n", prgName);
	trbool = 1;  /* should just ignore flag, but other procs do this */
	fromc = 0x0d;
	break;
    }

    bpos = 99;			       /* force initial read */
    numnodes = get_int(f);	       /* get number of nodes */

    if (numnodes<0 || numnodes>=NUMVALS) {
	 fprintf(stderr, "%s: usq: archived file has invalid decode tree\n",
							prgName);
	 return (-1);
    }

    /* initialize for possible empty tree (SPEOF only) */
    node[0].child[0] = -(SPEOF + 1);
    node[0].child[1] = -(SPEOF + 1);

    for (i=0; i<numnodes; ++i) {	/* get decoding tree from file */
	 node[i].child[0] = get_int(f);
	 node[i].child[1] = get_int(f);
    }

    return (0);
}


/*
 * Unsqueeze a file
 */
static int
#ifdef __STDC__
unsqueeze(FILE *sfp, FILE *dfp)
#else
unsqueeze(sfp, dfp)
FILE *sfp, *dfp;
#endif
{
    register int c;			/* one char of stream */

    state = NOHIST;		       /* initial repeat unpacking state */

    if (init_usq(sfp))		       /* init unsqueeze algorithm */
	return 1;
    while ((c=getc_usq(sfp)) != EOF)   /* and unsqueeze file */
	 putc_ncr(c, dfp);

    return (0);			       /* file is okay */
}


/*
 * main entrance to unsqueeze
 *
 * We reset the file posn to where it should be according to "length"; note
 * that "length" is not actually used by the unsqueeze routines.  We have
 * do to this because fdopen() sticks about 8K or so in a buffer...
 *
 * Note also that we dup() the file descriptors before starting.  This
 * is so that we can cleanly fclose() the file descriptors when we're done,
 * but still keep the file open.
 */
void
#ifdef __STDC__
unpak_SQU (int srcfd, int dstfd, long length)
#else
unpak_SQU(srcfd, dstfd, length)
int srcfd, dstfd;
long length;  /* #of bytes we're expected to read */
#endif
{
    FILE *srcfp, *dstfp;		/* File pointers for squ/dest file */
    int srcfd2, dstfd2;
    long finalposn;
    static char *procName = "unpak_SQU";

    finalposn = lseek(srcfd, (off_t)0, S_REL) + length;  /* where we should end up */
    srcfd2 = dup(srcfd);
    dstfd2 = dup(dstfd);
    if ((srcfp = fdopen(srcfd2, FREAD_STR)) == NULL)
	Fatal("Can't fdopen() archive", procName);
    if ((dstfp = fdopen(dstfd2, FWRITE_STR)) == NULL)
	Fatal("Can't fdopen() dest file", procName);

    unsqueeze(srcfp, dstfp);  /* unsqueeze the file */
    fclose(srcfp);  /* (was fflush) (this isn't really necessary) */
    fclose(dstfp);  /* (was fflush) This is *very* necessary */

    if (lseek(srcfd, (off_t)finalposn, S_ABS) < 0)  /* set file posn back */
	Fatal("Can't reset final posn", procName);
    /* note that this lets things continue even if unSQueezing failed */
}
