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

struct line {
	int	serial;
	int	value;
};
extern struct line *file[2];
extern int len[2];
extern long *ixold, *ixnew;
extern int *J;

static Biobuf *input[2];
static char *file1, *file2;
static int firstchange;

#define MAXLINELEN	4096
#define MIN(x, y)	((x) < (y) ? (x): (y))

static int
readline(Biobuf *bp, char *buf)
{
	int c;
	char *p, *e;

	p = buf;
	e = p + MAXLINELEN-1;
	do {
		c = Bgetc(bp);
		if (c < 0) {
			if (p == buf)
				return -1;
			break;
		}
		if (c == '\n')
			break;
		*p++ = c;
	} while (p < e);
	*p = 0;
	if (c != '\n' && c >= 0) {
		do c = Bgetc(bp);
		while (c >= 0 && c != '\n');
	}
	return p - buf;
}

#define HALFLONG 16
#define low(x)	(x&((1L<<HALFLONG)-1))
#define high(x)	(x>>HALFLONG)

/*
 * hashing has the effect of
 * arranging line in 7-bit bytes and then
 * summing 1-s complement in 16-bit hunks 
 */
static int
readhash(Biobuf *bp, char *buf)
{
	long sum;
	unsigned shift;
	char *p;
	int len, space;

	sum = 1;
	shift = 0;
	if ((len = readline(bp, buf)) == -1)
		return 0;
	p = buf;
	switch(bflag)	/* various types of white space handling */
	{
	case 0:
		while (len--) {
			sum += (long)*p++ << (shift &= (HALFLONG-1));
			shift += 7;
		}
		break;
	case 1:
		/*
		 * coalesce multiple white-space
		 */
		for (space = 0; len--; p++) {
			if (isspace((uchar)*p)) {
				space++;
				continue;
			}
			if (space) {
				shift += 7;
				space = 0;
			}
			sum += (long)*p << (shift &= (HALFLONG-1));
			shift += 7;
		}
		break;
	default:
		/*
		 * strip all white-space
		 */
		while (len--) {
			if (isspace((uchar)*p)) {
				p++;
				continue;
			}
			sum += (long)*p++ << (shift &= (HALFLONG-1));
			shift += 7;
		}
		break;
	}
	sum = low(sum) + high(sum);
	return ((short)low(sum) + (short)high(sum));
}

Biobuf *
prepare(int i, char *arg)
{
	struct line *p;
	int j, h;
	Biobuf *bp;
	char *cp, buf[MAXLINELEN];
	int nbytes;
	Rune r;

	bp = Bopen(arg, OREAD);
	if (!bp) {
		panic(mflag ? 0: 2, "cannot open %s: %r\n", arg);
		return 0;
	}
	if (binary)
		return bp;
	nbytes = Bread(bp, buf, MIN(1024, MAXLINELEN));
	if (nbytes > 0) {
		cp = buf;
		while (cp < buf+nbytes-UTFmax) {
			/*
			 * heuristic for a binary file in the
			 * brave new UNICODE world
			 */
			cp += chartorune(&r, cp);
			if (r == 0 || (r > 0x7f && r <= 0xa0)) {
				binary++;
				return bp;
			}
		}
		Bseek(bp, 0, 0);
	}
	p = MALLOC(struct line, 3);
	for (j = 0; h = readhash(bp, buf); p[j].value = h)
		p = REALLOC(p, struct line, (++j+3));
	len[i] = j;
	file[i] = p;
	input[i] = bp;			/*fix*/
	if (i == 0) {			/*fix*/
		file1 = arg;
		firstchange = 0;
	}
	else
		file2 = arg;
	return bp;
}

static int
squishspace(char *buf)
{
	char *p, *q;
	int space;

	for (space = 0, q = p = buf; *q; q++) {
		if (isspace((uchar)*q)) {
			space++;
			continue;
		}
		if (space && bflag == 1) {
			*p++ = ' ';
			space = 0;
		}
		*p++ = *q;
	}
	*p = 0;
	return p - buf;
}

/*
 * need to fix up for unexpected EOF's
 */
void
check(Biobuf *bf, Biobuf *bt)
{
	int f, t, flen, tlen;
	char fbuf[MAXLINELEN], tbuf[MAXLINELEN];

	ixold[0] = ixnew[0] = 0;
	for (f = t = 1; f < len[0]; f++) {
		flen = readline(bf, fbuf);
		ixold[f] = ixold[f-1] + flen + 1;		/* ftell(bf) */
		if (J[f] == 0)
			continue;
		do {
			tlen = readline(bt, tbuf);
			ixnew[t] = ixnew[t-1] + tlen + 1;	/* ftell(bt) */
		} while (t++ < J[f]);
		if (bflag) {
			flen = squishspace(fbuf);
			tlen = squishspace(tbuf);
		}
		if (flen != tlen || strcmp(fbuf, tbuf))
			J[f] = 0;
	}
	while (t < len[1]) {
		tlen = readline(bt, tbuf);
		ixnew[t] = ixnew[t-1] + tlen + 1;	/* fseek(bt) */
		t++;
	}
}

static void
range(int a, int b, char *separator)
{
	Bprint(&stdout, "%d", a > b ? b: a);
	if (a < b)
		Bprint(&stdout, "%s%d", separator, b);
}

static void
fetch(long *f, int a, int b, Biobuf *bp, char *s)
{
	char buf[MAXLINELEN];
	int maxb;

	if(a <= 1)
		a = 1;
	if(bp == input[0])
		maxb = len[0];
	else
		maxb = len[1];
	if(b > maxb)
		b = maxb;
	if(a > maxb)
		return;
	Bseek(bp, f[a-1], 0);
	while (a++ <= b) {
		readline(bp, buf);
		Bprint(&stdout, "%s%s\n", s, buf);
	}
}

typedef struct Change Change;
struct Change
{
	int a;
	int b;
	int c;
	int d;
};

Change *changes;
int nchanges;

void
change(int a, int b, int c, int d)
{
	char verb;
	char buf[4];
	Change *ch;

	if (a > b && c > d)
		return;
	anychange = 1;
	if (mflag && firstchange == 0) {
		if(mode) {
			buf[0] = '-';
			buf[1] = mode;
			buf[2] = ' ';
			buf[3] = '\0';
		} else {
			buf[0] = '\0';
		}
		Bprint(&stdout, "diff %s%s %s\n", buf, file1, file2);
		firstchange = 1;
	}
	verb = a > b ? 'a': c > d ? 'd': 'c';
	switch(mode) {
	case 'e':
		range(a, b, ",");
		Bputc(&stdout, verb);
		break;
	case 0:
		range(a, b, ",");
		Bputc(&stdout, verb);
		range(c, d, ",");
		break;
	case 'n':
		Bprint(&stdout, "%s:", file1);
		range(a, b, ",");
		Bprint(&stdout, " %c ", verb);
		Bprint(&stdout, "%s:", file2);
		range(c, d, ",");
		break;
	case 'f':
		Bputc(&stdout, verb);
		range(a, b, " ");
		break;
	case 'c':
	case 'a':
		if(nchanges%1024 == 0)
			changes = erealloc(changes, (nchanges+1024)*sizeof(changes[0]));
		ch = &changes[nchanges++];
		ch->a = a;
		ch->b = b;
		ch->c = c;
		ch->d = d;
		return;
	}
	Bputc(&stdout, '\n');
	if (mode == 0 || mode == 'n') {
		fetch(ixold, a, b, input[0], "< ");
		if (a <= b && c <= d)
			Bprint(&stdout, "---\n");
	}
	fetch(ixnew, c, d, input[1], mode == 0 || mode == 'n' ? "> ": "");
	if (mode != 0 && mode != 'n' && c <= d)
		Bprint(&stdout, ".\n");
}

enum
{
	Lines = 3		/* number of lines of context shown */
};

int
changeset(int i)
{
	while(i<nchanges && changes[i].b+1+2*Lines > changes[i+1].a)
		i++;
	if(i<nchanges)
		return i+1;
	return nchanges;
}

void
flushchanges(void)
{
	int a, b, c, d, at;
	int i, j;

	if(nchanges == 0)
		return;
	
	for(i=0; i<nchanges; ){
		j = changeset(i);
		a = changes[i].a-Lines;
		b = changes[j-1].b+Lines;
		c = changes[i].c-Lines;
		d = changes[j-1].d+Lines;
		if(a < 1)
			a = 1;
		if(c < 1)
			c = 1;
		if(b > len[0])
			b = len[0];
		if(d > len[1])
			d = len[1];
		if(mode == 'a'){
			a = 1;
			b = len[0];
			c = 1;
			d = len[1];
			j = nchanges;
		}
		Bprint(&stdout, "%s:", file1);
		range(a, b, ",");
		Bprint(&stdout, " - ");
		Bprint(&stdout, "%s:", file2);
		range(c, d, ",");
		Bputc(&stdout, '\n');
		at = a;
		for(; i<j; i++){
			fetch(ixold, at, changes[i].a-1, input[0], "  ");
			fetch(ixold, changes[i].a, changes[i].b, input[0], "- ");
			fetch(ixnew, changes[i].c, changes[i].d, input[1], "+ ");
			at = changes[i].b+1;
		}
		fetch(ixold, at, b, input[0], "  ");
	}
	nchanges = 0;
}
