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

#define	DIRECTORY(s)		((s)->qid.type&QTDIR)
#define	REGULAR_FILE(s)		((s)->type == 'M' && !DIRECTORY(s))

Biobuf	stdout;

char mode;			/* '\0', 'e', 'f', 'h' */
char bflag;			/* ignore multiple and trailing blanks */
char rflag;			/* recurse down directory trees */
char mflag;			/* pseudo flag: doing multiple files, one dir */
int anychange;

static char *tmp[] = {"/tmp/diff1XXXXXXXXXXX", "/tmp/diff2XXXXXXXXXXX"};
static int whichtmp;
static char *progname;
static char usage[] = "diff [ -acefmnbwr ] file1 ... file2\n";

static void
rmtmpfiles(void)
{
	while (whichtmp > 0) {
		whichtmp--;
		remove(tmp[whichtmp]);
	}
}

void
done(int status)
{
	rmtmpfiles();
	switch(status)
	{
	case 0:
		exits("");
	case 1:
		exits("some");
	default:
		exits("error");
	}
	/*NOTREACHED*/
}

void
panic(int status, char *fmt, ...)
{
	va_list arg;

	Bflush(&stdout);

	fprint(2, "%s: ", progname);
	va_start(arg, fmt);
	vfprint(2, fmt, arg);
	va_end(arg);
	if (status)
		done(status);
		/*NOTREACHED*/
}

static int
catch(void *a, char *msg)
{
	USED(a);
	panic(2, msg);
	return 1;
}

int
mkpathname(char *pathname, char *path, char *name)
{
	if (strlen(path) + strlen(name) > MAXPATHLEN) {
		panic(0, "pathname %s/%s too long\n", path, name);
		return 1;
	}
	sprint(pathname, "%s/%s", path, name);
	return 0;
}

static char *
mktmpfile(int input, Dir **sb)
{
	int fd, i;
	char *p;
	char buf[8192];

	atnotify(catch, 1);
/*
	p = mktemp(tmp[whichtmp++]);
	fd = create(p, OWRITE, 0600);
*/
	fd = mkstemp(p=tmp[whichtmp++]);
	if (fd < 0) {
		panic(mflag ? 0: 2, "cannot create %s: %r\n", p);
		return 0;
	}
	while ((i = read(input, buf, sizeof(buf))) > 0) {
		if ((i = write(fd, buf, i)) < 0)
			break;
	}
	*sb = dirfstat(fd);
	close(fd);
	if (i < 0) {
		panic(mflag ? 0: 2, "cannot read/write %s: %r\n", p);
		return 0;
	}
	return p;
}

static char *
statfile(char *file, Dir **sb)
{
	Dir *dir;
	int input;

	dir = dirstat(file);
	if(dir == nil) {
		if (strcmp(file, "-") || (dir = dirfstat(0)) == nil) {
			panic(mflag ? 0: 2, "cannot stat %s: %r\n", file);
			return 0;
		}
		free(dir);
		return mktmpfile(0, sb);
	}
	else if (!REGULAR_FILE(dir) && !DIRECTORY(dir)) {
		free(dir);
		if ((input = open(file, OREAD)) == -1) {
			panic(mflag ? 0: 2, "cannot open %s: %r\n", file);
			return 0;
		}
		file = mktmpfile(input, sb);
		close(input);
	}
	else
		*sb = dir;
	return file;
}

void
diff(char *f, char *t, int level)
{
	char *fp, *tp, *p, fb[MAXPATHLEN+1], tb[MAXPATHLEN+1];
	Dir *fsb, *tsb;

	if ((fp = statfile(f, &fsb)) == 0)
		goto Return;
	if ((tp = statfile(t, &tsb)) == 0){
		free(fsb);
		goto Return;
	}
	if (DIRECTORY(fsb) && DIRECTORY(tsb)) {
		if (rflag || level == 0)
			diffdir(fp, tp, level);
		else
			Bprint(&stdout, "Common subdirectories: %s and %s\n",
				fp, tp);
	}
	else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb))
		diffreg(fp, tp);
	else {
		if (REGULAR_FILE(fsb)) {
			if ((p = utfrrune(f, '/')) == 0)
				p = f;
			else
				p++;
			if (mkpathname(tb, tp, p) == 0)
				diffreg(fp, tb);
		}
		else {
			if ((p = utfrrune(t, '/')) == 0)
				p = t;
			else
				p++;
			if (mkpathname(fb, fp, p) == 0)
				diffreg(fb, tp);
		}
	}
	free(fsb);
	free(tsb);
Return:
	rmtmpfiles();
}

void
main(int argc, char *argv[])
{
	char *p;
	int i;
	Dir *fsb, *tsb;
	extern int _p9usepwlibrary;

	_p9usepwlibrary = 0;
	Binit(&stdout, 1, OWRITE);
	progname = *argv;
	while (--argc && (*++argv)[0] == '-' && (*argv)[1]) {
		for (p = *argv+1; *p; p++) {
			switch (*p) {

			case 'e':
			case 'f':
			case 'n':
			case 'c':
			case 'a':
				mode = *p;
				break;

			case 'w':
				bflag = 2;
				break;

			case 'b':
				bflag = 1;
				break;

			case 'r':
				rflag = 1;
				mflag = 1;
				break;

			case 'm':
				mflag = 1;
				break;

			case 'h':
			default:
				progname = "Usage";
				panic(2, usage);
			}
		}
	}
	if (argc < 2)
		panic(2, usage, progname);
	if ((tsb = dirstat(argv[argc-1])) == nil)
		panic(2, "can't stat %s\n", argv[argc-1]);
	if (argc > 2) {
		if (!DIRECTORY(tsb))
			panic(2, usage, progname);
		mflag = 1;
	}
	else {
		if ((fsb = dirstat(argv[0])) == nil)
			panic(2, "can't stat %s\n", argv[0]);
		if (DIRECTORY(fsb) && DIRECTORY(tsb))
			mflag = 1;
		free(fsb);
	}
	free(tsb);
	for (i = 0; i < argc-1; i++)
		diff(argv[i], argv[argc-1], 0);
	done(anychange);
	/*NOTREACHED*/
}

static char noroom[] = "out of memory - try diff -h\n";

void *
emalloc(unsigned n)
{
	register void *p;

	if ((p = malloc(n)) == 0)
		panic(2, noroom);
	return p;
}

void *
erealloc(void *p, unsigned n)
{
	register void *rp;

	if ((rp = realloc(p, n)) == 0)
		panic(2, noroom);
	return rp;
}
