#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(*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(*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(*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;
}