#include <u.h>
#include <libc.h>
#include <bio.h>
#include "bzlib.h"

static	Biobuf	bin;
static	int	debug;
static	int	verbose;
static	char	*delfile;
static	char	*infile;
static	int	bunzipf(char *file, int stdout);
static	int	bunzip(int ofd, char *ofile, Biobuf *bin);

void
usage(void)
{
	fprint(2, "usage: bunzip2 [-cvD] [file ...]\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	int i, ok, stdout;

	stdout = 0;
	ARGBEGIN{
	case 'D':
		debug++;
		break;
	case 'c':
		stdout++;
		break;
	case 'v':
		verbose++;
		break;
	default:
		usage();
	}ARGEND

	if(argc == 0){
		Binit(&bin, 0, OREAD);
		infile = "<stdin>";
		ok = bunzip(1, "<stdout>", &bin);
	}else{
		ok = 1;
		for(i = 0; i < argc; i++)
			ok &= bunzipf(argv[i], stdout);
	}

	exits(ok ? nil: "errors");
}

static int
bunzipf(char *file, int stdout)
{
	char ofile[64], *s;
	int ofd, ifd, ok;

	infile = file;
	ifd = open(file, OREAD);
	if(ifd < 0){
		fprint(2, "bunzip2: can't open %s: %r\n", file);
		return 0;
	}

	Binit(&bin, ifd, OREAD);
	if(Bgetc(&bin) != 'B' || Bgetc(&bin) != 'Z' || Bgetc(&bin) != 'h'){
		fprint(2, "bunzip2: %s is not a bzip2 file\n", file);
		Bterm(&bin);
		close(ifd);
		return 0;
	}
	Bungetc(&bin);
	Bungetc(&bin);
	Bungetc(&bin);

	if(stdout){
		ofd = 1;
		strcpy(ofile, "<stdout>");
	}else{
		s = strrchr(file, '/');
		if(s != nil)
			s++;
		else
			s = file;
		strecpy(ofile, ofile+sizeof ofile, s);
		s = strrchr(ofile, '.');
		if(s != nil && s != ofile && strcmp(s, ".bz2") == 0)
			*s = '\0';
		else if(s != nil && (strcmp(s, ".tbz") == 0 || strcmp(s, ".tbz2") == 0))
			strcpy(s, ".tar");
		else if(strcmp(file, ofile) == 0){
			fprint(2, "bunzip2: can't overwrite %s\n", file);
			Bterm(&bin);
			close(ifd);
			return 0;
		}

		ofd = create(ofile, OWRITE, 0666);
		if(ofd < 0){
			fprint(2, "bunzip2: can't create %s: %r\n", ofile);
			Bterm(&bin);
			close(ifd);
			return 0;
		}
		delfile = ofile;
	}

	ok = bunzip(ofd, ofile, &bin);
	Bterm(&bin);
	close(ifd);
	if(!ok){
		fprint(2, "bunzip2: can't write %s: %r\n", ofile);
		if(delfile)
			remove(delfile);
	}
	delfile = nil;
	if(!stdout && ofd >= 0)
		close(ofd);
	return ok;
}

static int
bunzip(int ofd, char *ofile, Biobuf *bin)
{
	int e, n, done, onemore;
	char buf[8192];
	char obuf[8192];
	Biobuf bout;
	bz_stream strm;

	USED(ofile);

	memset(&strm, 0, sizeof strm);
	BZ2_bzDecompressInit(&strm, verbose, 0);

	strm.next_in = buf;
	strm.avail_in = 0;
	strm.next_out = obuf;
	strm.avail_out = sizeof obuf;

	done = 0;
	Binit(&bout, ofd, OWRITE);

	/*
	 * onemore is a crummy hack to go 'round the loop
	 * once after we finish, to flush the output buffer.
	 */
	onemore = 1;
	SET(e);
	do {
		if(!done && strm.avail_in < sizeof buf) {
			if(strm.avail_in)
				memmove(buf, strm.next_in, strm.avail_in);
			
			n = Bread(bin, buf+strm.avail_in, sizeof(buf)-strm.avail_in);
			if(n <= 0)
				done = 1;
			else
				strm.avail_in += n;
			strm.next_in = buf;
		}
		if(strm.avail_out < sizeof obuf) {
			Bwrite(&bout, obuf, sizeof(obuf)-strm.avail_out);
			strm.next_out = obuf;
			strm.avail_out = sizeof obuf;
		}
		if(onemore == 0)
			break;
		if(strm.avail_in == 0 && strm.avail_out == sizeof obuf)
			break;
	} while((e=BZ2_bzDecompress(&strm)) == BZ_OK || onemore--);

	if(e != BZ_STREAM_END) {
		fprint(2, "bunzip2: decompress failed\n");
		return 0;
	}

	if(BZ2_bzDecompressEnd(&strm) != BZ_OK) {
		fprint(2, "bunzip2: decompress end failed (can't happen)\n");
		return 0;
	}

	Bterm(&bout);

	return 1;
}
