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

static	int	bzipf(char*, int, int);
static	int	bzip(char*, long, int, Biobuf*);

static	Biobuf	bout;
static	int	level;
static	int	debug;
static	int	verbose;


static void
usage(void)
{
	fprint(2, "usage: bzip2 [-vcnD] [-1-9] [file ...]\n");
	exits("usage");
}

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

	level = 6;
	stdout = 0;
	mtime = time(nil);
	ARGBEGIN{
	case 'D':
		debug++;
		break;
	case 'v':
		verbose++;
		break;
	case 'c':
		stdout++;
		break;
	case 'n':
		mtime = 0;
		break;
	case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		level = ARGC() - '0';
		break;
	default:
		usage();
		break;
	}ARGEND

	if(argc == 0){
		Binit(&bout, 1, OWRITE);
		ok = bzip(nil, mtime, 0, &bout);
		Bterm(&bout);
	}else{
		ok = 1;
		for(i = 0; i < argc; i++)
			ok &= bzipf(argv[i], !mtime, stdout);
	}
	exits(ok ? nil: "errors");
}

static int
bzipf(char *file, int nomtime, int stdout)
{
	Dir *dir;
	char ofile[128], *f, *s;
	int ifd, ofd, ok;

	ifd = open(file, OREAD);
	if(ifd < 0){
		fprint(2, "bzip2: can't open %s: %r\n", file);
		return 0;
	}
	dir = dirfstat(ifd);
	if(dir == nil){
		fprint(2, "bzip2: can't stat %s: %r\n", file);
		close(ifd);
		return 0;
	}
	if(dir->mode & DMDIR){
		fprint(2, "bzip2: can't compress a directory\n");
		close(ifd);
		free(dir);
		return 0;
	}

	if(stdout){
		ofd = 1;
		strcpy(ofile, "<stdout>");
	}else{
		f = strrchr(file, '/');
		if(f != nil)
			f++;
		else
			f = file;
		s = strrchr(f, '.');
		if(s != nil && s != ofile && strcmp(s, ".tar") == 0){
			*s = '\0';
			snprint(ofile, sizeof(ofile), "%s.tbz", f);
		}else
			snprint(ofile, sizeof(ofile), "%s.bz2", f);
		ofd = create(ofile, OWRITE, 0666);
		if(ofd < 0){
			fprint(2, "bzip2: can't open %s: %r\n", ofile);
			free(dir);
			close(ifd);
			return 0;
		}
	}

	if(verbose)
		fprint(2, "compressing %s to %s\n", file, ofile);

	Binit(&bout, ofd, OWRITE);
	ok = bzip(file, nomtime ? 0 : dir->mtime, ifd, &bout);
	if(!ok || Bflush(&bout) < 0){
		fprint(2, "bzip2: error writing %s: %r\n", ofile);
		if(!stdout)
			remove(ofile);
	}
	Bterm(&bout);
	free(dir);
	close(ifd);
	close(ofd);
	return ok;
}

static int
bzip(char *file, long mtime, int ifd, Biobuf *bout)
{
	int e, n, done, onemore;
	char buf[IOUNIT];
	char obuf[IOUNIT];
	Biobuf bin;
	bz_stream strm;

	USED(file);
	USED(mtime);

	memset(&strm, 0, sizeof strm);
	BZ2_bzCompressInit(&strm, level, verbose, 0);

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

	done = 0;
	Binit(&bin, ifd, OREAD);

	/*
	 * 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;
	} while((e=BZ2_bzCompress(&strm, done ? BZ_FINISH : BZ_RUN)) == BZ_RUN_OK || e == BZ_FINISH_OK || onemore--);

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

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

	Bterm(&bin);

	return 1;
}
