#include	<u.h>
#include	<libc.h>

#define	MINUTE(x)	((long)(x)*60L)
#define	HOUR(x)		(MINUTE(x)*60L)
#define	YEAR(x)		(HOUR(x)*24L*360L)

int	verb;
int	uflag;
int	force;
int	diff;
char*	sflag;
char*	dargv[20] = {
	"diff",
};
int	ndargv = 1;

void	usage(void);
void	ysearch(char*, char*);
long	starttime(char*);
void	lastbefore(ulong, char*, char*, char*);
char*	prtime(ulong);
void	darg(char*);

void
main(int argc, char *argv[])
{
	int i;
	char *ndump;

	ndump = nil;
	ARGBEGIN {
	default:
		usage();
	case 'a':
		darg("-a");
		break;
	case 'b':
		darg("-b");
		break;
	case 'c':
		darg("-c");
		break;
	case 'e':
		darg("-e");
		break;
	case 'm':
		darg("-m");
		break;
	case 'n':
		darg("-n");
		break;
	case 'w':
		darg("-w");
		break;
	case 'D':
		diff = 1;
		break;
	case 'd':
		ndump = ARGF();
		break;
	case 'f':
		force = 1;
		break;
	case 's':
		sflag = ARGF();
		break;
	case 'u':
		uflag = 1;
		break;
	case 'v':
		verb = 1;
		break;
	} ARGEND

	if(argc == 0)
		usage();

	for(i=0; i<argc; i++)
		ysearch(argv[i], ndump);
	exits(0);
}

void
darg(char* a)
{
	if(ndargv >= nelem(dargv)-3)
		return;
	dargv[ndargv++] = a;
}

void
usage(void)
{
	fprint(2, "usage: history [-bDfuv] [-d dumpfilesystem] [-s yyyymmdd] files\n");
	exits("usage");
}

void
ysearch(char *file, char *ndump)
{
	char fil[400], buf[500], nbuf[100], pair[2][500], *p;
	Tm *tm;
	Waitmsg *w;
	Dir *dir, *d;
	ulong otime, dt;
	int toggle, started, missing;

	fil[0] = 0;
	if(file[0] != '/') {
		getwd(strchr(fil, 0), 100);
		strcat(fil, "/");
	}
	strcat(fil, file);
	if(memcmp(fil, "/n/", 3) == 0){
		p = strchr(fil+3, '/');
		if(p == nil)
			p = fil+strlen(fil);
		if(ndump == nil){
			if(p-fil >= sizeof nbuf-10){
				fprint(2, "%s: dump name too long", fil);
				return;
			}
			memmove(nbuf, fil+3, p-(fil+3));
			nbuf[p-(fil+3)] = 0;
			strcat(nbuf, "dump");
			ndump = nbuf;
		}
		memmove(fil, p, strlen(p)+1);
	}
	if(ndump == nil)
		ndump = "dump";

	tm = localtime(time(0));
	snprint(buf, sizeof buf, "/n/%s/%.4d/", ndump, tm->year+1900);
	if(access(buf, AREAD) < 0) {
		if(verb)
			print("mounting dump %s\n", ndump);
		if(rfork(RFFDG|RFPROC) == 0) {
			execl("/bin/rc", "rc", "9fs", ndump, nil);
			exits(0);
		}
		w = wait();
		if(w == nil){
			fprint(2, "history: wait error: %r\n");
			exits("wait");
		}
		if(w->msg[0] != '\0'){
			fprint(2, "9fs failed: %s\n", w->msg);
			exits(w->msg);
		}
		free(w);
	}

	started = 0;
	dir = dirstat(file);
	if(dir == nil)
		fprint(2, "history: warning: %s does not exist\n", file);
	else{
		print("%s %s %lld [%s]\n", prtime(dir->mtime), file, dir->length, dir->muid);
		started = 1;
		strecpy(pair[1], pair[1]+sizeof pair[1], file);
	}
	free(dir);
	otime = starttime(sflag);
	toggle = 0;
	for(;;) {
		lastbefore(otime, fil, buf, ndump);
		dir = dirstat(buf);
		if(dir == nil) {
			if(!force)
				return;
			dir = malloc(sizeof(Dir));
			nulldir(dir);
			dir->mtime = otime + 1;
		}
		dt = HOUR(12);
		missing = 0;
		while(otime <= dir->mtime){
			if(verb)
				print("backup %ld, %ld\n", dir->mtime, otime-dt);
			lastbefore(otime-dt, fil, buf, ndump);
			d = dirstat(buf);
			if(d == nil){
				if(!force)
					return;
				if(!missing)
					print("removed %s\n", buf);
				missing = 1;
			}else{
				free(dir);
				dir = d;
			}
			dt += HOUR(12);
		}
		strcpy(pair[toggle], buf);
		if(diff && started){
			switch(rfork(RFFDG|RFPROC)){
			case 0:
				dargv[ndargv] = pair[toggle];
				dargv[ndargv+1] = pair[toggle ^ 1];
				exec("/bin/diff", dargv);
				fprint(2, "can't exec diff: %r\n");
				exits(0);
			case -1:
				fprint(2, "can't fork diff: %r\n");
				break;
			default:
				while(waitpid() != -1)
					;
				break;
			}
		}
		print("%s %s %lld [%s]\n", prtime(dir->mtime), buf, dir->length, dir->muid);
		toggle ^= 1;
		started = 1;
		otime = dir->mtime;
		free(dir);
	}
}

void
lastbefore(ulong t, char *f, char *b, char *ndump)
{
	Tm *tm;
	Dir *dir;
	int vers, try;
	ulong t0, mtime;
	int i, n, fd;

	t0 = t;
	if(verb)
		print("%ld lastbefore %s\n", t0, f);
	mtime = 0;
	for(try=0; try<30; try++) {
		tm = localtime(t);
		sprint(b, "/n/%s/%.4d/%.2d%.2d", ndump,
			tm->year+1900, tm->mon+1, tm->mday);
		dir = dirstat(b);
		if(dir){
			mtime = dir->mtime;
			free(dir);
		}
		if(dir==nil || mtime > t0) {
			if(verb)
				print("%ld earlier %s\n", mtime, b);
			t -= HOUR(24);
			continue;
		}
		if(strstr(ndump, "snap")){
			fd = open(b, OREAD);
			if(fd < 0)
				continue;
			n = dirreadall(fd, &dir);
			close(fd);
			if(n == 0)
				continue;
			for(i = n-1; i > 0; i--){
				if(dir[i].mtime > t0)
					break;
			}
			sprint(b, "/n/%s/%.4d/%.2d%.2d/%s%s", ndump,
				tm->year+1900, tm->mon+1, tm->mday, dir[i].name, f);
			free(dir);
		} else {
			for(vers=0;; vers++) {
				sprint(b, "/n/%s/%.4d/%.2d%.2d%d", ndump,
					tm->year+1900, tm->mon+1, tm->mday, vers+1);
				dir = dirstat(b);
				if(dir){
					mtime = dir->mtime;
					free(dir);
				}
				if(dir==nil || mtime > t0)
					break;
				if(verb)
					print("%ld later %s\n", mtime, b);
			}
			sprint(b, "/n/%s/%.4d/%.2d%.2d%s", ndump,
				tm->year+1900, tm->mon+1, tm->mday, f);
			if(vers)
				sprint(b, "/n/%s/%.4d/%.2d%.2d%d%s", ndump,
					tm->year+1900, tm->mon+1, tm->mday, vers, f);
		}
		return;
	}
	strcpy(b, "XXX");	/* error */
}

char*
prtime(ulong t)
{
	static char buf[100];
	char *b;
	Tm *tm;

	if(uflag)
		tm = gmtime(t);
	else
		tm = localtime(t);
	b = asctime(tm);
	memcpy(buf, b+4, 24);
	buf[24] = 0;
	return buf;
}

long
starttime(char *s)
{
	Tm *tm;
	long t, dt;
	int i, yr, mo, da;

	t = time(0);
	if(s == 0)
		return t;
	for(i=0; s[i]; i++)
		if(s[i] < '0' || s[i] > '9') {
			fprint(2, "bad start time: %s\n", s);
			return t;
		}
	if(strlen(s)==6){
		yr = (s[0]-'0')*10 + s[1]-'0';
		mo = (s[2]-'0')*10 + s[3]-'0' - 1;
		da = (s[4]-'0')*10 + s[5]-'0';
		if(yr < 70)
			yr += 100;
	}else if(strlen(s)==8){
		yr = (((s[0]-'0')*10 + s[1]-'0')*10 + s[2]-'0')*10 + s[3]-'0';
		yr -= 1900;
		mo = (s[4]-'0')*10 + s[5]-'0' - 1;
		da = (s[6]-'0')*10 + s[7]-'0';
	}else{
		fprint(2, "bad start time: %s\n", s);
		return t;
	}
	t = 0;
	dt = YEAR(10);
	for(i=0; i<50; i++) {
		tm = localtime(t+dt);
		if(yr > tm->year ||
		  (yr == tm->year && mo > tm->mon) ||
		  (yr == tm->year && mo == tm->mon) && da > tm->mday) {
			t += dt;
			continue;
		}
		dt /= 2;
		if(dt == 0)
			break;
	}
	t += HOUR(12);	/* .5 day to get to noon of argument */
	return t;
}
