/*
 * interactive diff, inspired/stolen from
 * kernighan and pike, _unix programming environment_.
 */

#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/bio.h>

int diffbflag;
int diffwflag;

void copy(Biobuf*, char*, Biobuf*, char*);
void idiff(Biobuf*, char*, Biobuf*, char*, Biobuf*, char*, Biobuf*, char*);
int opentemp(char*, int, long);
void rundiff(char*, char*, int);

void
usage(void)
{
	fprint(2, "usage: idiff [-bw] file1 file2\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	int fd, ofd;
	char diffout[40], idiffout[40];
	Biobuf *b1, *b2, bdiff, bout, bstdout;
	Dir *d;

	ARGBEGIN{
	default:
		usage();
	case 'b':
		diffbflag++;
		break;
	case 'w':
		diffwflag++;
		break;
	}ARGEND

	if(argc != 2)
		usage();

	if((d = dirstat(argv[0])) == nil)
		sysfatal("stat %s: %r", argv[0]);
	if(d->mode&DMDIR)
		sysfatal("%s is a directory", argv[0]);
	free(d);
	if((d = dirstat(argv[1])) == nil)
		sysfatal("stat %s: %r", argv[1]);
	if(d->mode&DMDIR)
		sysfatal("%s is a directory", argv[1]);
	free(d);

	if((b1 = Bopen(argv[0], OREAD)) == nil)
		sysfatal("open %s: %r", argv[0]);
	if((b2 = Bopen(argv[1], OREAD)) == nil)
		sysfatal("open %s: %r", argv[1]);

	strcpy(diffout, "/tmp/idiff.XXXXXX");
	fd = opentemp(diffout, ORDWR|ORCLOSE, 0);
	strcpy(idiffout, "/tmp/idiff.XXXXXX");
	ofd = opentemp(idiffout, ORDWR|ORCLOSE, 0);
	rundiff(argv[0], argv[1], fd);
	seek(fd, 0, 0);
	Binit(&bdiff, fd, OREAD);
	Binit(&bout, ofd, OWRITE);
	idiff(b1, argv[0], b2, argv[1], &bdiff, diffout, &bout, idiffout);
	Bterm(&bdiff);
	Bflush(&bout);
	seek(ofd, 0, 0);
	Binit(&bout, ofd, OREAD);
	Binit(&bstdout, 1, OWRITE);
	copy(&bout, idiffout, &bstdout, "<stdout>");
	exits(nil);
}

int
opentemp(char *template, int mode, long perm)
{
	int fd, i;
	char *p;	

	p = strdup(template);
	if(p == nil)
		sysfatal("strdup out of memory");
	fd = -1;
	for(i=0; i<10; i++){
		mktemp(p);
		if(access(p, 0) < 0 && (fd=create(p, mode, perm)) >= 0)
			break;
		strcpy(p, template);
	}
	if(fd < 0)
		sysfatal("could not create temporary file");
	strcpy(template, p);
	free(p);

	return fd;
}

void
rundiff(char *arg1, char *arg2, int outfd)
{
	char *arg[10], *p;
	int narg, pid;
	Waitmsg *w;

	narg = 0;
	arg[narg++] = "/bin/diff";
	arg[narg++] = "-n";
	if(diffbflag)
		arg[narg++] = "-b";
	if(diffwflag)
		arg[narg++] = "-w";
	arg[narg++] = arg1;
	arg[narg++] = arg2;
	arg[narg] = nil;

	switch(pid = fork()){
	case -1:
		sysfatal("fork: %r");

	case 0:
		dup(outfd, 1);
		close(0);
		exec("/bin/diff", arg);
		sysfatal("exec: %r");

	default:
		w = wait();
		if(w==nil)
			sysfatal("wait: %r");
		if(w->pid != pid)
			sysfatal("wait got unexpected pid %d", w->pid);
		if((p = strchr(w->msg, ':')) && strcmp(p, ": some") != 0)
			sysfatal("%s", w->msg);
		free(w);
	}
}

void
runcmd(char *cmd)
{
	char *arg[10];
	int narg, pid, wpid;

	narg = 0;
	arg[narg++] = "/bin/rc";
	arg[narg++] = "-c";
	arg[narg++] = cmd;
	arg[narg] = nil;

	switch(pid = fork()){
	case -1:
		sysfatal("fork: %r");

	case 0:
		exec("/bin/rc", arg);
		sysfatal("exec: %r");

	default:
		wpid = waitpid();
		if(wpid < 0)
			sysfatal("wait: %r");
		if(wpid != pid)
			sysfatal("wait got unexpected pid %d", wpid);
	}
}

void
parse(char *s, int *pfrom1, int *pto1, int *pcmd, int *pfrom2, int *pto2)
{
	*pfrom1 = *pto1 = *pfrom2 = *pto2 = 0;

	s = strchr(s, ':');
	if(s == nil)
		sysfatal("bad diff output0");
	s++;
	*pfrom1 = strtol(s, &s, 10);
	if(*s == ','){
		s++;
		*pto1 = strtol(s, &s, 10);
	}else
		*pto1 = *pfrom1;
	if(*s++ != ' ')
		sysfatal("bad diff output1");
	*pcmd = *s++;
	if(*s++ != ' ')
		sysfatal("bad diff output2");
	s = strchr(s, ':');
	if(s == nil)
		sysfatal("bad diff output3");
	s++;
	*pfrom2 = strtol(s, &s, 10);
	if(*s == ','){
		s++;
		*pto2 = strtol(s, &s, 10);
	}else
		*pto2 = *pfrom2;
}

void
skiplines(Biobuf *b, char *name, int n)
{
	int i;

	for(i=0; i<n; i++){
		while(Brdline(b, '\n')==nil){
			if(Blinelen(b) <= 0)
				sysfatal("early end of file on %s", name);
			Bseek(b, Blinelen(b), 1);
		}
	}
}

void
copylines(Biobuf *bin, char *nin, Biobuf *bout, char *nout, int n)
{
	char buf[4096], *p;
	int i, m;

	for(i=0; i<n; i++){
		while((p=Brdline(bin, '\n'))==nil){
			if(Blinelen(bin) <= 0)
				sysfatal("early end of file on %s", nin);
			m = Blinelen(bin);
			if(m > sizeof buf)
				m = sizeof buf;
			m = Bread(bin, buf, m);
			if(Bwrite(bout, buf, m) != m)
				sysfatal("error writing %s: %r", nout);
		}
		if(Bwrite(bout, p, Blinelen(bin)) != Blinelen(bin))
			sysfatal("error writing %s: %r", nout);
	}
}

void
copy(Biobuf *bin, char *nin, Biobuf *bout, char *nout)
{
	char buf[4096];
	int m;

	USED(nin);
	while((m = Bread(bin, buf, sizeof buf)) > 0)
		if(Bwrite(bout, buf, m) != m)
			sysfatal("error writing %s: %r", nout);
}

void
idiff(Biobuf *b1, char *name1, Biobuf *b2, char *name2, Biobuf *bdiff, char *namediff, Biobuf *bout, char *nameout)
{
	char buf[256], *p;
	int interactive, defaultanswer, cmd, diffoffset;
	int n, from1, to1, from2, to2, nf1, nf2;
	Biobuf berr;

	nf1 = 0;
	nf2 = 0;
	interactive = 1;
	defaultanswer = 0;
	Binit(&berr, 2, OWRITE);
	while(diffoffset = Boffset(bdiff), p = Brdline(bdiff, '\n')){
		p[Blinelen(bdiff)-1] = '\0';
		parse(p, &from1, &to1, &cmd, &from2, &to2);
		p[Blinelen(bdiff)-1] = '\n';
		n = to1-from1 + to2-from2 + 1;	/* #lines from diff */
		if(cmd == 'c')
			n += 2;
		else if(cmd == 'a')
			from1++;
		else if(cmd == 'd')
			from2++;
		if(interactive){
			p[Blinelen(bdiff)-1] = '\0';
			fprint(2, "%s\n", p);
			p[Blinelen(bdiff)-1] = '\n';
			copylines(bdiff, namediff, &berr, "<stderr>", n);
			Bflush(&berr);
		}else
			skiplines(bdiff, namediff, n);
		do{
			if(interactive){
				fprint(2, "? ");
				memset(buf, 0, sizeof buf);
				if(read(0, buf, sizeof buf - 1) < 0)
					sysfatal("read console: %r");
			}else
				buf[0] = defaultanswer;

			switch(buf[0]){
			case '>':
				copylines(b1, name1, bout, nameout, from1-nf1);
				skiplines(b1, name1, to1-from1);
				skiplines(b2, name2, from2-nf2);
				copylines(b2, name2, bout, nameout, to2-from2);
				break;
			case '<':
				copylines(b1, name1, bout, nameout, to1-nf1);
				skiplines(b2, name2, to2-nf2);
				break;
			case '=':
				copylines(b1, name1, bout, nameout, from1-nf1);
				skiplines(b1, name1, to1-from1);
				skiplines(b2, name2, to2-nf2);
				if(Bseek(bdiff, diffoffset, 0) != diffoffset)
					sysfatal("seek in diff output: %r");
				copylines(bdiff, namediff, bout, nameout, n+1);
				break;
			case '!':
				runcmd(buf+1);
				break;
			case 'q':
				if(buf[1]=='<' || buf[1]=='>' || buf[1]=='='){
					interactive = 0;
					defaultanswer = buf[1];
				}else
					fprint(2, "must be q<, q>, or q=\n");
				break;
			default:
				fprint(2, "expect: <, >, =, q<, q>, q=, !cmd\n");
				break;
			}
		}while(buf[0] != '<' && buf[0] != '>' && buf[0] != '=');
		nf1 = to1;
		nf2 = to2;
	}
	copy(b1, name1, bout, nameout);
}
