#include "../sccshead/sfile.h"
#include "../sccshead/statbuf.h"
#include "../sccshead/sint.h"
# include "../sccshead/had.h"

# define debug if(DEBUG) printf
int	DEBUG	0;

char delta____[] "@(#)delta.c	3.13";


int	num_files;
char	had[26];
long	cutoff	0X7FFFFFFFL;	/* max positive long */
int	verbosity;
int	heur	3;
int	lookahd 50;
char	*why;
struct Deltab ndt;

main(argc,argv)
int argc;
char *argv[];
{
	register int i;
	register char *p;
	char c;
	int testmore;
	int needy;
	int intrp;
	extern delta();
	extern char *acklist;

	needy = 0;
	for(i=1; i<argc; i++)
		if(argv[i][0] == '-' && (c=argv[i][1])) {
			p = &argv[i][2];
			testmore = 0;
			switch (c) {

			case 'a':
				acklist = p;
				break;
			case 'h':
				if ((heur=patoi(p)) < 0 || heur > 3)
					fatal("bad heuristic level (7)");
				break;
			case 'y':
				why = p;
				break;
			case 'l':
				if ((lookahd=patoi(p)) < 1)
					fatal("bad lookahead (8)");
				break;
			case 'n':
			case 's':
				testmore++;
				break;
			default:
				fatal("unknown key letter (69)");
			}

			if (testmore) {
				testmore = 0;
				if (*p)
					fatal(stringf(
					  "value after %c arg (232)",c));
			}
			if (had[c - 'a']++)
				fatal("key letter twice (464)");
			argv[i] = 0;
		}
		else {
			if (argv[i][0] == '-')
				needy++;
			num_files++;
		}

	if(num_files == 0) fatal("missing file arg (49)");
	if (needy && !HADY)
		fatal("missing history (326)");
	if (!HADS)
		verbosity = WARNING;
	dohist(&ndt,why);
	if ((intrp=signal(2,1))==1)
		signal(1,1);
	else
		signal(2,intrp);
	setsig();
	for (i=1; i<argc; i++)
		if (p=argv[i])
			do_file(p,delta);
	exit(0);
}


struct Ln {
	char *Ltext;
	int Llnno;
	int Llen;
};

struct Info {
	char Iwhich;
	char Ieof;
	int Ilookahead;
	int Ilast;
	char *Iinput;
	int Itop;
	struct Ln Ilines[1];

};

int inserted, deleted, unchanged;
struct Packet gpkt;
struct Ibufr bufb;
struct Statbuf sb;


/*	`Main' routine.
*	Sets everything up for `diff()' which does the real work.
*/

char	*oldrec;
int	oldlnno;
int	curlnno;
int	noteach;
int	did_id;
struct Packet wpacket;
int	wopen, weof;

delta(dfile)
char *dfile;
{
	char gfile[SIZEOFPfile];
	register char *pfile;
	extern int had_dir, had_standinp;

	sinit(&gpkt,dfile,1);
	dohead(&gpkt);
	copy(auxf(&gpkt,'g'),gfile);
	did_id = oldrec = oldlnno = curlnno = noteach = wopen = weof = 0;
	inserted = deleted = unchanged = 0;

	if (verbosity && (num_files > 1 || had_dir || had_standinp))
		printf("\n%s:\n",gpkt.Pfile);
	if (diff(gpkt.Pfile,gfile,lookahd)) {
		rename(auxf(&gpkt,'x'),gpkt.Pfile);
		xrm(&gpkt);
		remove(auxf(&gpkt,'p'));
		if (!HADN) {
			setuid(getuid()&0377);
			remove(gfile);
		}
	}
	if (!did_id && verbosity)
		msg2("No id keywords (305)\n");
	xrm(&gpkt);
	freeall();
}


/*	Init sets up the `Info' structures for the two files,
*	calls opena() or openb(), and reads in the first window
*	for the given file.
*/

init(f,file,lookahead,which)
struct Info **f;
char file[], which;
int lookahead;
{
	register struct Info *p;
	register struct Ln *lp;
	register int i;

	i = (sizeof(*p)-sizeof(*lp)) + lookahead*sizeof(*lp);
	debug("(sizeof(*p)-sizeof(*lp))=%d  sizeof(*lp)=%d\n",
		(sizeof(*p)-sizeof(*lp)),sizeof(*lp));
	p = alloc(i,0);
	*f = p;

	p->Iwhich = which;
	p->Ilookahead = lookahead;
	p->Itop = 0;

	if (which == 'A') opena(p,file);
	else openb(p,file);

	for(i= -1; ++i<lookahead;) {
		lp = &p->Ilines[i];
		if(rdline(p,&lp->Ltext,&lp->Llen,&lp->Llnno) == -1) break;
	}

	p->Ilast = i;
}


/*	Opena() does the initial SCCS file stuff.
*/

opena(p,mod)
register struct Info *p;
char mod[];
{
	register int f;
	char user[8];

	p->Iinput = &gpkt;
	rdpfile(auxf(&gpkt,'p'),&gpkt.Prel,user);
	if (!equal(user,getlnam()))
		fatal("you are not `",user,"' (322)");
	gpkt.Plev = -1;
	gpkt.Pcutoff = cutoff;
	doreltab(&gpkt);
	permiss(&gpkt);
	dodeltab(&gpkt);
}


/*	Openb() does the initial text file stuff.
*/

openb(p,file)
struct Info *p;
char file[];
{
	p->Iinput = &bufb;
	opnl(p->Iinput,file);
}


/*	Rdline() reads a line.
*	It determines if it should read the SCCS file or the text file.
*	It returns -1 on error or (equivalently) EOF,
*	and 0 on success.
*/

rdline(p,text,len,lnno)
register struct Info *p;
char **text;
int *len, *lnno;
{
	register struct Packet *pkt;
	register struct Ibufr *buf;

	if (p->Ieof)
		return(-1);

	if(p->Iwhich == 'A') {
		pkt = p->Iinput;
		if(readmod(pkt)) {
			p->Ieof = 1;
			close(pkt->Pibuf.Ifildes);
			return(-1);
		}
		*text = alloc((*len=pkt->Pibuf.Ilen)+1);
		move(pkt->Pibuf.Irecptr,*text,*len);
		(*text)[*len] = '\0';
		*lnno = pkt->Precno;
	}
	else {
		buf = p->Iinput;
		if(getl(buf)==1) {
			p->Ieof = 1;
			close(buf->Ifildes);
			return(-1);
		}
		*text = alloc((*len=buf->Ilen-1)+1);
		move(buf->Irecptr,*text,*len);
		(*text)[*len] = '\0';
		*lnno = ++curlnno;
		checkid(*text);
	}
	return(0);
}


/*
 *	checkid() checks for "%.%" strings
 */

checkid(line)
{
	extern did_id;
	register char *p;

	if (!did_id)
		for (p = line; *p; p++)
			if (p[0] == '%' && p[1] != 0 && p[2] == '%') {
				did_id = 1;
				break;
			}
}


/*	Wrtctl() writes a control record.
*/


wrtctl(p,ctl)
register struct Info *p;
int ctl;
{
	struct Control ctlrec;

	if (!wopen) wrtopn(p);
	ctlrec.Cctl = ctl;
	ctlrec.Crel = (p->Iinput)->Prel;
	ctlrec.Clev = (p->Iinput)->Plev + 1;

	wrtrec(&wpacket,&ctlrec,SIZEOFCONTROL);
}


/*	Wrtopn() opens the SCCS file with the update (Pupd)
*	flag set. The x-file is generated by this instance
*	of the open SCCS file.
*/

wrtopn(p)
struct Info *p;
{
	register struct Packet *p1, *p2;
	extern int verbosity;

	p1 = &gpkt;
	p2 = wopen = &wpacket;
	zero(p2,sizeof(*p2));
	copy(p1->Pfile,p2->Pfile);
	p2->Prel = p1->Prel;
	p2->Plev = p1->Plev;
	p2->Pupd = 1;
	p2->Pcutoff = cutoff;
	p2->Pwrttn = 1;
	p2->Pverbose = verbosity;
	opnr(&p2->Pibuf,p2->Pfile);
	dohead(p2);
	doreltab(p2);
	dodeltab(p2);
}


/*	Wrtend() flushes the SCCS file (and the x-file).
*/

wrtend(p)
struct Info *p;
{
	if (!wopen) wrtopn(p);
	if (weof==0) while(readmod(&wpacket)==0);
	close(wpacket.Pibuf.Ifildes);
	wrtrec(&wpacket,0,-1);
}


/*	Before() rolls the wpacket instance of the SCCS file to 
*	the record before the record at the top of the window.
*/

before(p)
register struct Info *p;
{
	register int n;

	if(weof) return;
	if (!wopen) wrtopn(p);
	if (p->Ilast==0) n = 0;
	else n = (&p->Ilines[(p->Itop)%(p->Ilookahead)])->Llnno;
	if(wpacket.Precno == n) return;
	while(wpacket.Precno != n)
		if (weof=readmod(&wpacket)) break;
}


/*	After() rolls the wpacket instance of the SCCS file to
*	the record after the record at the top of the window.
*/

after(p)
struct Info *p;
{
	before(p);
	wrtrec(&wpacket,0,0);
}


status()
{
	printf("%d unchanged\n%d inserted\n%d deleted\n",
	  unchanged,inserted,deleted);
}


/*	Enter() is called by `dolist()' (which see),
*	and is used in processing the a_ argument.
*/

enter(fill1,fill2,r,l)
{
	extern int ackrel, acklev, acknowl;

	if (ackrel==r && acklev==l) acknowl = 1;
}


/*	Clean_up() is called by fatal(), which might be called
*	because of a signal being caught.
*/

clean_up()
{
	xrm(&gpkt);
}


/*	Mkdeltab() builds a new delta table entry.
*/

mkdeltab(pkt)
struct Packet *pkt;
{
	int level;
	extern long timenow;

	if (pkt->Pnoprop) {
		ndt.Dtype = 'U';
		if (pkt->Pverbose&WARNING)
			msg2("Delta will not propagate (324)\n");
	}
	else ndt.Dtype = 'D';

	ndt.Drel = pkt->Prel;
	ndt.Dlev = level = (pkt->Plev+1);
	ndt.Dfill = ' ';
	ndt.Ddatetime = timenow;
	copy(getlnam(),ndt.Dpgmr);

	wrtrec(pkt,&ndt,size(ndt.Dhist)+sizeof(ndt)-sizeof(ndt.Dhist));
	if (pkt->Pverbose&WARNING)
		printf("%d.%d\n",pkt->Prel,level);
}


/*	Now comes the hard stuff!
*	Read this code very slowly and very carefully.
*/

diff(filea,fileb,lookahead)
char filea[], fileb[];
int lookahead;
{
	struct Info *ainfo, *binfo;
	int a[2], b[2], deg[2], len;
	register int pick;

	init(&ainfo,filea,lookahead,'A');
	init(&binfo,fileb,lookahead,'B');

	while(ainfo->Ilast && binfo->Ilast) {

		match(ainfo,binfo,ainfo->Ilast,1,a,b,deg,0);
		match(binfo,ainfo,b[0]-1,a[0]+1,b,a,deg,1);

		pick = choose(ainfo,binfo,a,b,deg);

		insert(ainfo,binfo,b[pick]-1);
		delete(ainfo,binfo,a[pick]-1);

		unchanged =+ deg[pick];
		while(deg[pick]--) {
			take(ainfo,&len);
			take(binfo,&len);
		}
                while(ainfo->Ilast && binfo->Ilast) {
                        if (equal(
                        ainfo->Ilines[ainfo->Itop%ainfo->Ilookahead].Ltext,
                        binfo->Ilines[binfo->Itop%binfo->Ilookahead].Ltext)) {
                                take(ainfo,&len);
                                take(binfo,&len);
                                unchanged++;
                        }
                        else break;
                }
	}

	while(binfo->Ilast) insert(ainfo,binfo,binfo->Ilast);
	while(ainfo->Ilast) delete(ainfo,binfo,ainfo->Ilast);

	wrtend(ainfo);
	if (!HADS)
		status();
	if (!HADH) return(1);
	return(update()=='y');
}


match(f1,f2,stop1,start2,mark1,mark2,deg,sub)
register struct Info *f1, *f2;
int stop1, start2, mark1[2], mark2[2], deg[2], sub;
{
	register int i1;
	int i2, found;

	found = 0;
	for(i1=0; ++i1<=stop1;) {
		for(i2=start2-1; ++i2<=f2->Ilast;) if (equal(
			f1->Ilines[(f1->Itop+i1-1)%(f1->Ilookahead)].Ltext,
			f2->Ilines[(f2->Itop+i2-1)%(f2->Ilookahead)].Ltext)){
				found = 1;
				break;
			}
		if(found) break;
	}
	if(!found) {
		mark1[sub] = 0;
		mark2[sub] = f2->Ilast+1;
		deg[sub] = 0;
		return;
	}

	mark1[sub] = i1;
	mark2[sub] = i2;
	deg[sub] = 1;
	while(i1++ < f1->Ilast && i2++ < f2->Ilast &&
	  equal(f1->Ilines[(f1->Itop+i1-1)%(f1->Ilookahead)].Ltext,
		f2->Ilines[(f2->Itop+i2-1)%(f2->Ilookahead)].Ltext))
			deg[sub]++;

	return;
}


insert(f1,f2,mark2)
struct Info *f1, *f2;
register int mark2;
{
	register struct Control *rec;
	int len;

	if(mark2 < 1) return;
	inserted =+ mark2;

	before(f1);
	wrtctl(f1,INS);
	while(mark2--) {
		rec = take(f2,&len);
		if (ctlrec(rec,len))
			fatal(stringf("illegal data on line %d (312)",
				oldlnno));
		wrtrec(&wpacket,rec,len);
	}
	wrtctl(f1,END);
}


delete(f1,f2,mark1)
struct Info *f1, *f2;
register int mark1;
{
	int len;

	if(mark1 < 1) return;
	deleted =+ mark1;

	before(f1);
	wrtctl(f1,DEL);
	while(--mark1) take(f1,&len);
	after(f1);
	take(f1,&len);
	wrtctl(f1,END);
}



take(p,len)
register struct Info *p;
int *len;
{
	register struct Ln *lp;

	if(oldrec) free(oldrec);

	lp = &p->Ilines[p->Itop];
	oldrec = lp->Ltext;
	oldlnno = lp->Llnno;
	*len = lp->Llen;

	if(rdline(p,&lp->Ltext,&lp->Llen,&lp->Llnno) == -1) p->Ilast--;

	if(++p->Itop == p->Ilookahead) p->Itop = 0;
	return(oldrec);
}


numsame(f1,f2,start1,start2)
register struct Info *f1, *f2;
int start1, start2;
{
	register int i1;
	int i2, num;

	num = 0;
	for(i1=start1+1; i1<=f1->Ilast; i1++)
		for(i2=start2+1; i2<=f2->Ilast; i2++) if (equal(
			f1->Ilines[(f1->Itop+i1-1)%(f1->Ilookahead)].Ltext,
			f2->Ilines[(f2->Itop+i2-1)%(f2->Ilookahead)].Ltext)) {
				num =+ f1->Ilines[(f1->Itop+i1-1)%
					(f1->Ilookahead)].Llen;
				break;
			}

	return(num);
}


double reldiff(a,b)
double a, b;
{
	double x;

	if((1e-37 > -b) && (b < 1e-37)) return(1e37);
	x = (a - b)/b;
	if(x < 0) return(-x);
	return(x);
}


double W 1.;
double X 2.;
double Y 4.;
double Z 8.;

choose(ainfo,binfo,a,b,deg)
register struct Info *ainfo, *binfo;
int a[2], b[2], deg[2];
{
	register int i;
	int k, chars, avgline, nsame;
	double val[2];

	if(heur == 0) return(ask(ainfo,binfo,a,b));

	for(k=0; k<=1; k++) {
		chars = 0;
		for(i=a[k]; i>0 && i<a[k]+deg[k]; i++)
			chars =+ ainfo->Ilines[(ainfo->Itop+i-1)%
					(ainfo->Ilookahead)].Llen;
		avgline = (a[k]+b[k])/2;
		nsame=numsame(ainfo,binfo,a[k],b[k]);
		val[k] = -W*avgline + X*deg[k] + Y*chars + Z*nsame;
	}

	if(reldiff(val[0],val[1]) < .2) return(ask(ainfo,binfo,a,b));
	if(heur==1 && b[0]!=1 && ((a[1]-a[0])>3 || (b[0]-b[1])>3))
		return(ask(ainfo,binfo,a,b));
	if(val[0] > val[1]) return(0);
	if(val[0] < val[1]) return(1);
}


/*	If you've made it to here, congratulations!
*	The rest is easy.
*
*	Ask() returns zero if lines were inserted,
*	non-zero if lines were deleted.
*/

ask(f1,f2,a,b)
struct Info *f1, *f2;
int a[2], b[2];
{

	if(heur == 3) return(0);

	while(1) {
		printf("%d inserted:\n",b[0]-(b[1]?b[1]:1));
		if(answer(f2,b[1]) == 'y') return(0);
		printf("%d deleted:\n",a[1]-(a[0]?a[0]:1));
		if(answer(f1,a[0]) == 'y') return(1);
		printf("try again...\n");
	}
}


/*	Answer() solicits a response from the user
*	and prints the next line in the buffer if the response
*	was only a new-line.
*	It returns the response (which must be either `y' or `n').
*/

answer(p,ln)
struct Info *p;
int ln;
{
	char resp;

	if(!ln) ln++;
	printf("  %s\n",
		p->Ilines[(p->Itop+(ln++)-1)%(p->Ilookahead)].Ltext);
	if(!(noteach++)) printf("(y,n,s,CR)");
	printf("? ");
	while((resp=getresp("(y,n,s,CR)?")) == '\n') {
		if(ln > p->Ilast) printf("end of buffer\n?");
		else printf("  %s\n?",
			p->Ilines[(p->Itop+(ln++)-1)%(p->Ilookahead)].Ltext);
	}

	return(resp);
}


/*	Getresp() gets a response from the user about `lesson'.
*	It processes all responses other than `y', `n', and
*	new-line.
*/

getresp(lesson)
char lesson[];
{
	char resp, nextc;

	while(1) {
		resp = getchr();
		if(resp == '\n') return('\n');
		if((nextc=getchr()) != '\n') while(getchr() != '\n');
		if(resp == 's' && nextc == '\n') {
			status();
			printf("? ");
			continue;
		}
		if((resp != 'y' && resp != 'n') || nextc != '\n') {
			printf(lesson);
			continue;
		}
		return(resp);
	}
}


/*	Update() solicits a `y'/`n' response from the user.
*/

update()
{
	char resp;

	printf("update");
	if(!(noteach++)) printf("(y,n)");
	do {
		printf("? ");
		resp=getresp("(y,n)?");
	} while(resp == '\n');

	return(resp);
}
