/*
 *                     RCS revision number handling
 */
/*********************************************************************************
 *********************************************************************************
 *
 * Copyright (C) 1982 by Walter F. Tichy
 *                       Purdue University
 *                       Computer Science Department
 *                       West Lafayette, IN 47907
 *
 * All rights reserved. No part of this software may be sold or distributed
 * in any form or by any means without the prior written permission of the
 * author.
 * Report problems and direct all inquiries to Tichy@purdue (ARPA net).
 */


/* $Log:	C.rcsrev $
 * Revision 1.1  90/05/22  21:53:47  pmoore
 * Initial revision
 * 
 * Revision 4.4  87/12/18  11:45:22  narten
 * more lint cleanups. Also, the NOTREACHED comment is no longer necessary,
 * since there's now a return value there with a value. (Guy Harris)
 *
 * Revision 4.3  87/10/18  10:38:42  narten
 * Updating version numbers. Changes relative to version 1.1 actually
 * relative to 4.1
 *
 * Revision 1.3  87/09/24  14:00:37  narten
 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
 * warnings)
 *
 * Revision 1.2  87/03/27  14:22:37  jenkins
 * Port to suns
 *
 * Revision 1.1  84/01/23  14:50:37  kcs
 * Initial revision
 *
 * Revision 4.1  83/03/25  21:10:45  wft
 * Only changed $Header to $Id.
 *
 * Revision 3.4  82/12/04  13:24:08  wft
 * Replaced getdelta() with gettree().
 *
 * Revision 3.3  82/11/28  21:33:15  wft
 * fixed compartial() and compnum() for nil-parameters; fixed nils
 * in error messages. Testprogram output shortenend.
 *
 * Revision 3.2  82/10/18  21:19:47  wft
 * renamed compnum->cmpnum, compnumfld->cmpnumfld,
 * numericrevno->numricrevno.
 *
 * Revision 3.1  82/10/11  19:46:09  wft
 * changed expandsym() to check for source==nil; returns zero length string
 * in that case.
 */

#undef REVTEST
/* version REVTEST is for testing the routines that generate a sequence
 * of delta numbers needed to regenerate a given delta.
 */

#include "rcsbase.h"

extern FILE *finptr;		/* RCS input file */


/* Given a pointer s to a dotted number (date or revision number),
 * countnumflds returns the number of digitfields in s.
 */
int countnumflds(char *s)
{
	register char *sp;
	register int count;
	if ((sp = s) == nil)
		return (0);
	if (*sp == '\0')
		return (0);
	count = 1;
	while (*sp)
	{
		if (*sp++ == '.')
			count++;
	}
	if (*(--sp) == '.')
		count--;	/* trailing periods don't count */
	return (count);
}

/* Given a non-nil revision number revno, getbranchno copies the number of the branch
 * on which revno is into branchnumber. If revno itself is a branch number,
 * it is copied unchanged.
 */
void getbranchno(char *revno, char *branchno)
{
	register int i, numflds;
	register char *tp, *sp;

	numflds = countnumflds(revno);
	if (numflds % 2 == 1)
		VOID strcpy(branchno, revno);
	else
	{
		sp = revno;
		tp = branchno;
		for (i = 1; i < numflds; i++)
		{
			while (*sp != '.')
				*tp++ = *sp++;
			*tp++ = *sp++;
		}
		*(tp - 1) = '\0';
	}
}

/* Compares the two dotted numbers num1 and num2 lexicographically
 * by field. Individual fields are compared numerically.
 * returns <0, 0, >0 if num1<num2, num1==num2, and num1>num2, resp.
 * omitted fields are assumed to be higher than the existing ones.
 */
int cmpnum(char *num1, char *num2)
{
	register char *s1, *s2;
	register int n1, n2;

	s1 = num1 == nil ? "" : num1;
	s2 = num2 == nil ? "" : num2;

	do
	{
		n1 = 0;
		while (('0' <= *s1) && (*s1 <= '9'))
		{
			n1 = n1 * 10 + (*s1 - '0');
			s1++;
		}
		/* skip '.' */
		if (*s1 == '.')
			s1++;

		n2 = 0;
		while (('0' <= *s2) && (*s2 <= '9'))
		{
			n2 = n2 * 10 + (*s2 - '0');
			s2++;
		}
		/* skip '.' */
		if (*s2 == '.')
			s2++;

	} while ((n1 == n2) && (*s1 != '\0') && (*s2 != '\0'));

	if (((*s1 == '\0') && (*s2 == '\0')) || (n1 != n2))
		return (n1 - n2);
	/* now n1==n2 and one of s1 or s2 is shorter */
	/* give precedence to shorter one */
	if (*s1 == '\0')
		return 1;
	else
		return -1;

}

/* compares the two dotted numbers at field fld and returns
 * num1[fld]-num2[fld]. Assumes that num1 and num2 have at least fld fields.
 */
int cmpnumfld(char *num1, char *num2, int fld)
{
	register char *s1, *s2;
	register int n1, n2;

	s1 = num1;
	n1 = fld - 1;
	/* skip fld-1 fields */
	while (n1)
	{
		while (*s1 != '.')
			s1++;
		n1--;
		s1++;
	}
	s2 = num2;
	n2 = fld - 1;
	while (n2)
	{
		while (*s2 != '.')
			s2++;
		n2--;
		s2++;
	}
	/* Don't put the above into a single loop! */
	/* Now s1 and s2 point to the beginning of the respective fields */
	/* compute numerical value and compare */
	n1 = 0;
	while (('0' <= *s1) && (*s1 <= '9'))
	{
		n1 = n1 * 10 + (*s1 - '0');
		s1++;
	}
	n2 = 0;
	while (('0' <= *s2) && (*s2 <= '9'))
	{
		n2 = n2 * 10 + (*s2 - '0');
		s2++;
	}
	return (n1 - n2);
}

/* Compare the first "length" fields of two dot numbers;
 * the omitted field is considered to be larger than any number
 * restriction:  at least one number has length or more fields
 */
int compartial(char *num1, char *num2, int length)
{
	register char *s1, *s2;
	register int n1, n2;


	s1 = num1;
	s2 = num2;
	if (s1 == nil || *s1 == '\0')
		return 1;
	if (s2 == nil || *s2 == '\0')
		return -1;

	do
	{
		n1 = 0;
		while (('0' <= *s1) && (*s1 <= '9'))
		{
			n1 = n1 * 10 + (*s1 - '0');
			s1++;
		}
		if (*s1 == '.')
			s1++;	/* skip .   */

		n2 = 0;
		while (('0' <= *s2) && (*s2 <= '9'))
		{
			n2 = n2 * 10 + (*s2 - '0');
			s2++;
		}
		if (*s2 == '.')
			s2++;
	} while ((n1 == n2) && ((--length) != 0) &&
		 (*s1 != '\0') && (*s2 != '\0'));

	if ((n1 != n2) || (length == 0))
	{
		return (n1 - n2);
	}

	if (*s1 == '\0')
		return 1;
	if (*s2 == '\0')
		return -1;
	VOID fprintf(stderr, "RCS Internal error, routine: compartial\n");
	return (0);
}

/* Increments the last field of revision number onum by one and
 * places the result into nnum
 */
void incnum(char *onum, char *nnum)
{
	register char *sp, *tp;
	register int i;

	sp = onum;
	tp = nnum;
	for (i = countnumflds(onum) - 1; i > 0; i--)
	{
		while (*sp != '.')
			*tp++ = *sp++;
		*tp++ = *sp++;	/* copy dot also */
	}
	VOID sprintf(tp, "%d", atoi(sp) + 1);
}

/* Function: Copies length fields of revision number rev2 into rev1.
 * returns rev1.
 */
char *partialno(char *rev1, char *rev2, int length)
{
	register char *r1, *r2;

	r1 = rev1;
	r2 = rev2;
	while (length)
	{
		while (*r2 != '.' && *r2 != '\0')
			*r1++ = *r2++;
		*r1++ = *r2++;
		length--;
	}
	/* eliminate last '.' */
	*(r1 - 1) = '\0';
	return rev1;
}

/* Function: finds the common ancestor of r1 and r2 and places it into r3.
 * Returns r3 if successful, false otherwise.
 * Works reliably only if r1 and r2 are not branch numbers.
 */
char *getancestor(char *r1, char *r2, char *r3)
{
	int l1, l2, l3;
	char t1[revlength], t2[revlength];

	l1 = countnumflds(r1);
	l2 = countnumflds(r2);
	if ((l1 <= 2 && l2 <= 2) || (cmpnum(r1, r2) == 0))
	{
		/* on main trunk or identical */
		error("Common ancestor of %s and %s undefined.", r1, r2);
		return false;
	}

	l3 = 0;
	while ((cmpnumfld(r1, r2, l3 + 1) == 0) && (cmpnumfld(r1, r2, l3 + 2) == 0))
	{
		l3 = l3 + 2;
	}
	/* This will terminate since r1 and r2 are not the same; see above */
	if (l3 == 0)
	{
		/* no common prefix. Common ancestor on main trunk. */
		VOID partialno(t1, r1, l1 > 2 ? 2 : l1);
		VOID partialno(t2, r2, l2 > 2 ? 2 : l2);
		if (cmpnum(t1, t2) < 0)
			VOID strcpy(r3, t1);
		else
			VOID strcpy(r3, t2);
		if ((cmpnum(r3, r1) == 0) || (cmpnum(r3, r2) == 0))
		{
			error("Ancestor for %s and %s undefined.", r1, r2);
			return false;
		}
		return r3;
	}
	else
	{
		if (cmpnumfld(r1, r2, l3 + 1) == 0)
		{
			error("Ancestor for %s and %s undefined.", r1, r2);
			return false;
		}
		return (partialno(r3, r1, l3));
	}
}

/* Function: finds the deltas needed for reconstructing the
 * revision given by revno, date, author, and state, and stores pointers
 * to these deltas into an array whose starting address is given by store.
 * The last pointer stored is nil. The last delta (target delta) is returned.
 * If the proper delta could not be found, nil is returned.
 */
struct hshentry *genrevs(char *revno, char *date, char *author,
			     char *state, struct hshentry ** store)
{
	int length;
	register struct hshentry *next;
	int result;
	char *branchnum;
	char t[revlength];

	if (Head == nil)
	{
		error("RCSfile empty.");
		return nil;
	}

	length = countnumflds(revno);
	next = Head;

	if (length >= 1)
	{
		/* at least one field; find branch exactly */
		while ((next != nil) &&
		       ((result = cmpnumfld(revno, next->num, 1)) < 0))
		{
			/* puts(next->num); */
			*store++ = next;
			next = next->next;
		}

		if (next == nil)
		{
			error("Branch number %s too low.", partialno(t, revno, 1));
			return nil;
		}
		if (result > 0)
		{
			error("Branch number %s not present.", partialno(t, revno, 1));
			return nil;
		}
	}
	if (length <= 1)
	{
		/* pick latest one on given branch */
		branchnum = next->num;	/* works even for empty revno */
		while ((next != nil) &&
		       (cmpnumfld(branchnum, next->num, 1) == 0) &&
		       !(
		      (date == nil ? 1 : (cmpnum(date, next->date) >= 0)) &&
		(author == nil ? 1 : (strcmp(author, next->author) == 0)) &&
		      (state == nil ? 1 : (strcmp(state, next->state) == 0))
			 )
			)
		{		/* puts(next->num); */
			*store++ = next;
			next = next->next;
		}
		if ((next == nil) ||
		    (cmpnumfld(branchnum, next->num, 1) != 0))	/* overshot */
		{
			error("Cannot find revision on branch %s with a date before %s, author %s, and state %s.",
			      length == 0 ? partialno(t, branchnum, 1) : revno, date == nil ? "<now>" : date,
			      author == nil ? "<any>" : author, state == nil ? "<any>" : state);
			return nil;
		}
		else
		{
			/* puts(next->num); */
			*store++ = next;
		}
		*store = nil;
		return next;
	}

	/* length >=2 */
	/* find revision; may go low if length==2 */
	while ((next != nil) &&
	       ((result = cmpnumfld(revno, next->num, 2)) < 0) &&
	       (cmpnumfld(revno, next->num, 1) == 0))
	{
		/* puts(next->num); */
		*store++ = next;
		next = next->next;
	}

	if ((next == nil) || (cmpnumfld(revno, next->num, 1) != 0))
	{
		error("Revision number %s too low.", partialno(t, revno, 2));
		return nil;
	}
	if ((length > 2) && (result != 0))
	{
		error("Revision %s not present.", partialno(t, revno, 2));
		return nil;
	}

	/* print last one */
	/* puts(next->num); */
	*store++ = next;

	if (length > 2)
		return genbranch(next, revno, length, date, author, state, store);
	else
	{			/* length == 2 */
		if ((date != nil) && (cmpnum(date, next->date) < 0))
		{
			error("Revision %s has date %s.", next->num, next->date);
			return nil;
		}
		if ((author != nil) && (strcmp(author, next->author) != 0))
		{
			error("Revision %s has author %s.", next->num, next->author);
			return nil;
		}
		if ((state != nil) && (strcmp(state, next->state) != 0))
		{
			error("Revision %s has state %s.", next->num,
			      next->state == nil ? "<empty>" : next->state);
			return nil;
		}
		*store = nil;
		return next;
	}
}

/* Function: given a branchpoint, a revision number, date, author, and state,
 * genbranch finds the deltas necessary to reconstruct the given revision
 * from the branch point on.
 * Pointers to the found deltas are stored in an array beginning with store.
 * revno must be on a side branch.
 * return nil on error
 */
struct hshentry *genbranch(struct hshentry * bpoint, char *revno,
			       int length, char *date, char *author,
			       char *state, struct hshentry ** store)
{
	int field;
	register struct hshentry *next, *trail;
	register struct branchhead *bhead;
	int result;
	char t[revlength];

	bhead = bpoint->branches;

	for (field = 3; field <= length; field = field + 2)
	{

		if (bhead == nil)
		{
			error("No side branches present for %s.", partialno(t, revno, field - 1));
			return nil;
		}

		/* find branch head */
		/* branches are arranged in increasing order */
		while ((bhead != nil) &&
		  ((result = cmpnumfld(revno, bhead->hsh->num, field)) > 0))
		{
			bhead = bhead->nextbranch;
		}

		if (bhead == nil)
		{
			error("Branch number %s too high.", partialno(t, revno, field));
			return nil;
		}
		if (result < 0)
		{
			error("Branch number %s not present.", partialno(t, revno, field));
			return nil;
		}

		next = bhead->hsh;
		if (length == field)
		{
			/* pick latest one on that branch */
			trail = nil;
			do
			{
				if ((date == nil ? 1 : (cmpnum(date, next->date) >= 0)) &&
				    (author == nil ? 1 : (strcmp(author, next->author) == 0)) &&
				    (state == nil ? 1 : (strcmp(state, next->state) == 0))
					)
					trail = next;
				next = next->next;
			} while (next != nil);

			if (trail == nil)
			{
				error("Cannot find revision on branch %s with a date before %s, author %s, and state %s.",
				      revno, date == nil ? "<now>" : date,
				      author == nil ? "<any>" : author, state == nil ? "<any>" : state);
				return nil;
			}
			else
			{	/* print up to last one suitable */
				next = bhead->hsh;
				while (next != trail)
				{
					/* puts(next->num); */
					*store++ = next;
					next = next->next;
				}
				/* puts(next->num); */
				*store++ = next;
			}
			*store = nil;
			return next;
		}

		/* length > field */
		/* find revision */
		/* check low */
		if (cmpnumfld(revno, next->num, field + 1) < 0)
		{
			error("Revision number %s too low.", partialno(t, revno, field + 1));
			return (nil);
		}
		do
		{		/* puts(next->num); */
			*store++ = next;
			trail = next;
			next = next->next;
		} while ((next != nil) &&
			 (cmpnumfld(revno, next->num, field + 1) >= 0));

		if ((length > field + 1) &&	/* need exact hit */
		    (cmpnumfld(revno, trail->num, field + 1) != 0))
		{
			error("Revision %s not present.", partialno(t, revno, field + 1));
			return (nil);
		}
		if (length == field + 1)
		{
			if ((date != nil) && (cmpnum(date, trail->date) < 0))
			{
				error("Revision %s has date %s.", trail->num, trail->date);
				return nil;
			}
			if ((author != nil) && (strcmp(author, trail->author) != 0))
			{
				error("Revision %s has author %s.", trail->num, trail->author);
				return nil;
			}
			if ((state != nil) && (strcmp(state, trail->state) != 0))
			{
				error("Revision %s has state %s.", trail->num,
				      trail->state == nil ? "<empty>" : trail->state);
				return nil;
			}
		}
		bhead = trail->branches;

	}
	*store = nil;
	return trail;
}


/* Function: looks up id in the list of symbolic names starting
 * with pointer SYMBOLS, and returns a pointer to the corresponding
 * revision number. Returns nil if not present.
 */
char *lookupsym(char *id)
{
	register struct assoc *next;
	next = Symbols;
	while (next != nil)
	{
		if (strcmp(id, next->symbol) == 0)
			return (next->delta->num);
		else
			next = next->nextassoc;
	}
	return nil;
}

/* Function: Source points to a revision number. Expandsym copies
 * the number to target, but replaces all symbolic fields in the
 * source number with their numeric values.
 * A trailing '.' is omitted; leading zeroes are compressed.
 * returns false on error;
 */
int expandsym(char *source, char *target)
{
	register char *sp, *tp, *bp;
	char symbuf[30];
	register enum tokens d;

	sp = source;
	tp = target;
	if (sp == nil)
	{			/* accept nil pointer as a legal value */
		*tp = '\0';
		return true;
	}

	while (*sp != '\0')
	{
		if (ctab[*sp] == DIGIT)
		{
			if (*sp == '0')
			{
				/* skip leading zeroes */
				sp++;
				while (*sp == '0')
					sp++;
				if (*sp == '\0' || *sp == '.')
					*tp++ = '0';	/* single zero */
			}
			while (ctab[*sp] == DIGIT)
				*tp++ = *sp++;
			if ((*sp == '\0') || ((*sp == '.') && (*(sp + 1) == '\0')))
			{
				*tp = '\0';
				return true;
			}
			if (*sp == '.')
				*tp++ = *sp++;
			else
			{
				error("Improper revision number: %s", source);
				*tp = '\0';
				return false;
			}
		}
		else if (ctab[*sp] == LETTER)
		{
			bp = symbuf;
			do
			{
				*bp++ = *sp++;
			} while (((d = ctab[*sp]) == LETTER) || (d == DIGIT) ||
				 (d == IDCHAR));
			*bp = '\0';
			bp = lookupsym(symbuf);
			if (bp == nil)
			{
				error("Symbolic number %s is undefined.", symbuf);
				*tp = '\0';
				return false;
			}
			else
			{
				/* copy number */
				/* copies the trailing \0 */
				while ((*tp++ = *bp++) != '\0')
					;
			}
			if ((*sp == '\0') || ((*sp == '.') && (*(sp + 1) == '\0')))
				return true;
			if (*sp == '.')
			{
				*(tp - 1) = *sp++;
			}
			else
			{
				error("Improper revision number: %s", source);
				return false;
			}
		}
		else
		{
			error("Improper revision number: %s", source);
			*tp = '\0';
			return false;
		}
	}
	*tp = '\0';
	return true;
}



#ifdef REVTEST

int main(int argc, char *argv[])
{
	char symrevno[revlength];	/* used for input of revision numbers */
	char numricrevno[revlength];
	char author[20];
	char state[20];
	char date[20];
	struct hshentry *gendeltas[hshsize / 2];
	struct hshentry *target;
	int i;

	cmdid = "revtest";
	if (argc < 2)
	{
		VOID fputs("No input file\n", stderr);
		exit(-1);
	}
	if ((finptr = fopen(argv[1], "r")) == NULL)
	{
		faterror("Can't open input file %s\n", argv[1]);
	}
	Lexinit();
	getadmin();

	gettree();

	getdesc(false);

	do
	{
		/* all output goes to stderr, to have diagnostics and       */
		/* errors in sequence.                                      */
		VOID fprintf(stderr, "\nEnter revision number or <return> or '.': ");
		if (gets(symrevno) == NULL)
			break;
		if (*symrevno == '.')
			break;
		VOID fprintf(stderr, "%s;\n", symrevno);
		expandsym(symrevno, numricrevno);
		VOID fprintf(stderr, "expanded number: %s; ", numricrevno);
		VOID fprintf(stderr, "Date: ");
		gets(date);
		VOID fprintf(stderr, "%s; ", date);
		VOID fprintf(stderr, "Author: ");
		gets(author);
		VOID fprintf(stderr, "%s; ", author);
		VOID fprintf(stderr, "State: ");
		gets(state);
		VOID fprintf(stderr, "%s;\n", state);
		target = genrevs(numricrevno, *date == '\0' ? (char *) nil : date, *author == '\0' ? (char *) nil : author,
			  *state == '\0' ? (char *) nil : state, gendeltas);
		if (target != nil)
		{
			i = 0;
			while (gendeltas[i] != nil)
			{
				VOID fprintf(stderr, "%s\n", gendeltas[i++]->num);
			}
		}
	} while (true);
	VOID fprintf(stderr, "done\n");

	return 0;
}

/* dummy cleanup function */
cleanup(void)
{
}

#endif
