#ifndef lint
static char sccsid[] = "@(#)compact.c	4.7 (Berkeley) 8/25/84";
#endif

/*
 *  Adaptive Huffman code input to output
 *
 *  On - line algorithm
 *
 *  Does not prepend decoding tree
 *
 *  Written by Colin L. Mc Master (UCB) February 28, 1979
 */
#include <strings.h>
#include "compact.h"

union	cio d;
union	cio c;
int	bits;
char	*infname;			/* input file's name */
char	fname[MAXPATHLEN+1];		/* output file's name */
struct	stat ucfstatus;			/* uncompacted file status */

int	verbose = 0;

main(argc, argv)
	int argc;
	char *argv[];
{
	register short j;

	argc--, argv++;
	if (argc > 0 && strcmp(*argv, "-v") == 0) {
		verbose++;
		argc--, argv++;
	}
	dir[513].next = NULL;
	for (head = dir + (j = 513); j--; ) {
		dirp = head--;
		head->next = dirp;
	}
	bottom = dirp->pt = dict;
	dict[0].sons[LEFT].top = dict[0].sons[RIGHT].top = dirp;
	dirq = dirp->next;
	in[EF].flags = FBIT | SEEN;
	if (argc == 0)
		exit(compact("-"));
	for (j = 0; j < argc; j++) {
		if (verbose && argc > 0)
			printf("%s: ", argv[j]);
		if (compact(argv[j]))
			exit(1);
	}
	exit(0);
}

/*
 * Compact a single file ("-" implies stdin).
 */
compact(file)
	char *file;
{
	int j, ignore;
	FILE *setup();

	for (j = 256; j--; )
		in[j].flags = 0;
	bottom->sons[RIGHT].top->next = flist;
	bottom->sons[RIGHT].top = dirp;
	flist = dirq;
	cfp = uncfp = NULL;
	if (strcmp(file, "-") != 0) {
		char *cp, *tp;

		/* verify .C suffix fits */
		cp = rindex(file, '/');
		if (cp == 0)
			cp = file;
		else
			cp++;
		tp = index(cp, '\0');
		if (tp - cp > MAXNAMLEN || strlen(file) + 2 >= MAXPATHLEN) {
			fprintf(stderr, "%s: File name too long\n", file);
			goto bad;
		}
		uncfp = fopen(file, "r");
		if (uncfp == NULL) {
			fprintf(stderr, "compact: "), perror(file);
			goto bad;
		}
		fstat(fileno(uncfp), &ucfstatus);
		if ((ucfstatus.st_mode & S_IFMT) != S_IFREG) {
			fprintf(stderr, "%s: Not a regular file.\n", file);
			goto bad;
		}
	} else
		uncfp = stdin;
	infname = file;

	cfp = setup(uncfp, &ignore);
	if (cfp == NULL) {
		if (ignore)
			goto done;
		goto bad;
	}
	if (compress(uncfp, cfp)) {
		if (cfp != stdout)
			(void) unlink(fname);
		goto bad2;
	}
	encode(EF);
	if (bits) {
		d.integ <<= 16 - bits;
		putc(d.chars.hib, cfp);
		if (bits > 8)
			putc(d.chars.lob, cfp);
		bits = 0;
	}
	fflush(cfp);
	if (ferror(uncfp) || ferror(cfp)) {
		if (cfp != stdout) {
			fprintf(stderr, "compact: ");
			if (ferror(cfp))
				perror(fname);
			else
				perror(infname);
			(void) unlink(fname);
		}
		goto bad;
	}
	if (cfp != stdout) {
		struct stat cfstatus;
		longint csize, ucsize;

		(void) fstat(fileno(cfp), &cfstatus);
		csize = cfstatus.st_size;
		ucsize = ucfstatus.st_size;
		if (csize >= ucsize) {
			fprintf("%s: ", infname);
			(void) unlink(fname);
			printf("Not compacted, does not save bytes.\n");
			goto done;
		}
		if (verbose) {
			FILE *fd;
			longint n, m;

			while (ucsize - csize > 21474) {
				ucsize /= 10;
				csize /= 10;
			}
			n = 100000 * (ucsize - csize) / ucsize + 5;
			m = (n % 1000) / 10;
			bits = m % 10 + '0';
			c.integ = m / 10 + '0';
			printf("%ld.%c%c%% compression\n",
			    n / 1000, c.chars.lob, bits);
		}
		if (unlink(infname) < 0)
			fprintf(stderr, "compact: "), perror(infname);
	}
done:
	if (cfp != NULL && cfp != stdout)
		fclose(cfp);
	if (uncfp != NULL)
		fclose(uncfp);
	return (0);
bad2:
	fprintf(stderr, "compact: ");
	if (cfp != stdout)
		perror(infname);
	else
		fprintf(stderr,
	    "Unsuccessful compact of standard input to standard output.\n");
bad:
	if (cfp != NULL && cfp != stdout)
		fclose(cfp);
	if (uncfp != NULL)
		fclose(uncfp);
	return (1);
}

encode(ch)
	int ch;
{
	register struct node *pp;
	int stack[17];
	register int stbits = 1, *sp = &stack[0], rbits = bits;
	register union cio *dp = &d;
	union cio c;

	c.integ = ch;
	*sp = in[ch].flags & FBIT;
	pp = in[ch].fp;

	while (pp->fath.fp) {
		*sp <<= 1;
		if (pp->fath.flags & FBIT)
			(*sp)++;
		stbits++;
		if ((stbits &= 017) == 0)
			sp++;
		pp = pp->fath.fp;
	}

	/* pop the output stack */
	do {
		while (stbits-- > 0) {
			dp->integ <<= 1;
			if (*sp & 01)
				dp->integ++;
			++rbits;
			if ((rbits &= 017) == 0) {
				putc(dp->chars.hib, cfp);
				putc(dp->chars.lob, cfp);
				if (ferror(cfp))
					goto done;
			}
			*sp >>= 1;
		}
		stbits = 16;
	} while (--sp >= &stack[0]);
done:
	bits = rbits;
}

compress(uncfp, cfp)
	register FILE *uncfp, *cfp;
{
	register union cio *dp = &d;
	register union cio *cp = &c;

	cp->integ = (dp->integ >> 8) & 0377;
	for (; cp->integ != EOF; cp->integ = getc(uncfp)) {
		if ((in[cp->integ].flags & SEEN) == 0) {
			register short j, m;

			encode(NC);
			uptree(NC);
			insert(cp->integ);

			m = 0200;
			for (j = 8; j--; m >>= 1) {
				dp->integ <<= 1;
				if (m & cp->integ)
					dp->integ++;
				++bits;
				if ((bits &= 017) == 0) {
					putc(dp->chars.hib, cfp);
					putc(dp->chars.lob, cfp);
				}
			}
		} else
			encode(cp->integ);
		if (ferror(cfp)) {
			perror(fname);
			return (1);
		}
		uptree(cp->integ);
	}
	if (ferror(uncfp)) {
		perror(infname);
		return (1);
	}
	return (0);
}

FILE *
setup(uncfp, ignore)
	FILE *uncfp;
	int *ignore;
{
	FILE *cfp = NULL;
	register union cio *dp = &d;
	register union cio *cp = &c;

	dp->integ = getc(uncfp);
	if (*ignore = (dp->integ == EOF))
		goto bad;
	cp->integ = getc(uncfp);
	if (*ignore = (cp->integ == EOF))
		goto bad;
	dp->chars.hib = cp->integ & 0377;
	if ((dp->integ &= 0177777) == COMPACTED) {
		fprintf(stderr, "%s: Already compacted.\n", infname);
		*ignore = 1;
		goto bad;
	}
	if (dp->integ == PACKED) {
		fprintf(stderr,
		    "%s: Already packed using pack program, use unpack.\n",
		    infname);
		*ignore = 1;
		goto bad;
	}
	if (strcmp(infname, "-") != 0) {
		sprintf(fname, "%s.C", infname);
		cfp = fopen(fname, "w");
		if (cfp == NULL)
			goto bad2;
		(void) fchmod(fileno(cfp), ucfstatus.st_mode);
	} else
		cfp = stdout;
	cp->integ = COMPACTED;
	putc(cp->chars.lob, cfp);
	putc(cp->chars.hib, cfp);
	if (ferror(cfp))
		goto bad2;
	bits = 8;
	cp->integ = dp->integ & 0377;

	in[NC].fp = in[EF].fp = dict[0].sons[LEFT].sp.p = bottom = dict + 1;
	bottom->sons[LEFT].count = bottom->sons[RIGHT].count =
	    dict[0].sons[RIGHT].count = 1;
	dirp->next = dict[0].sons[RIGHT].top = bottom->sons[LEFT].top =
	    bottom->sons[RIGHT].top = dirq = NEW;
	dirq->next = NULL;
	dict[0].fath.fp = NULL;
	dirq->pt = bottom->fath.fp = in[cp->integ].fp = dict;
	in[cp->integ].flags = (FBIT | SEEN);
	in[NC].flags = SEEN;
	dict[0].fath.flags = RLEAF;
	bottom->fath.flags = (LLEAF | RLEAF);
	dict[0].sons[LEFT].count = 2;

	dict[0].sons[RIGHT].sp.ch = cp->integ;
	bottom->sons[LEFT].sp.ch = NC;
	bottom->sons[RIGHT].sp.ch = EF;
	return (cfp);
bad2:
	if (cfp && cfp != stdout) {
		fprintf(stderr, "compact: "), perror(fname);
		(void) unlink(fname);
	}
bad:
	if (cfp && cfp != stdout)
		fclose(cfp);
	return (NULL);
}
