#include <u.h>
#include <libc.h>
#include <bio.h>
#include <flate.h>
#include "gzip.h"

static	int	gzipf(char*, int);
static	int	gzip(char*, long, int, Biobuf*);
static	int	crcread(void *fd, void *buf, int n);
static	int	gzwrite(void *bout, void *buf, int n);

static	Biobuf	bout;
static	ulong	crc;
static	ulong	*crctab;
static	int	debug;
static	int	eof;
static	int	level;
static	ulong	totr;
static	int	verbose;

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

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

	level = 6;
	stdout = 0;
	ARGBEGIN{
	case 'D':
		debug++;
		break;
	case 'v':
		verbose++;
		break;
	case 'c':
		stdout = 1;
		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

	crctab = mkcrctab(GZCRCPOLY);
	ok = deflateinit();
	if(ok != FlateOk)
		sysfatal("deflateinit failed: %s", flateerr(ok));

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

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

	ifd = open(file, OREAD);
	if(ifd < 0){
		fprint(2, "gzip: can't open %s: %r\n", file);
		return 0;
	}
	dir = dirfstat(ifd);
	if(dir == nil){
		fprint(2, "gzip: can't stat %s: %r\n", file);
		close(ifd);
		return 0;
	}
	if(dir->mode & DMDIR){
		fprint(2, "gzip: 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.tgz", f);
		}else
			snprint(ofile, sizeof(ofile), "%s.gz", f);
		ofd = create(ofile, OWRITE, 0666);
		if(ofd < 0){
			fprint(2, "gzip: can't open %s: %r\n", ofile);
			close(ifd);
			return 0;
		}
	}

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

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

static int
gzip(char *file, long mtime, int ifd, Biobuf *bout)
{
	int flags, err;

	flags = 0;
	Bputc(bout, GZMAGIC1);
	Bputc(bout, GZMAGIC2);
	Bputc(bout, GZDEFLATE);

	if(file != nil)
		flags |= GZFNAME;
	Bputc(bout, flags);

	Bputc(bout, mtime);
	Bputc(bout, mtime>>8);
	Bputc(bout, mtime>>16);
	Bputc(bout, mtime>>24);

	Bputc(bout, 0);
	Bputc(bout, GZOSINFERNO);

	if(flags & GZFNAME)
		Bwrite(bout, file, strlen(file)+1);

	crc = 0;
	eof = 0;
	totr = 0;
	err = deflate(bout, gzwrite, (void*)ifd, crcread, level, debug);
	if(err != FlateOk){
		fprint(2, "gzip: deflate failed: %s\n", flateerr(err));
		return 0;
	}

	Bputc(bout, crc);
	Bputc(bout, crc>>8);
	Bputc(bout, crc>>16);
	Bputc(bout, crc>>24);

	Bputc(bout, totr);
	Bputc(bout, totr>>8);
	Bputc(bout, totr>>16);
	Bputc(bout, totr>>24);

	return 1;
}

static int
crcread(void *fd, void *buf, int n)
{
	int nr, m;

	nr = 0;
	for(; !eof && n > 0; n -= m){
		m = read((int)(uintptr)fd, (char*)buf+nr, n);
		if(m <= 0){
			eof = 1;
			if(m < 0)
				return -1;
			break;
		}
		nr += m;
	}
	crc = blockcrc(crctab, crc, buf, nr);
	totr += nr;
	return nr;
}

static int
gzwrite(void *bout, void *buf, int n)
{
	if(n != Bwrite(bout, buf, n)){
		eof = 1;
		return -1;
	}
	return n;
}
