#include "sam.h"

Rune	genbuf[BLOCKSIZE];
int	io;
int	panicking;
int	rescuing;
String	genstr;
String	rhs;
String	curwd;
String	cmdstr;
Rune	empty[] = { 0 };
char	*genc;
File	*curfile;
File	*flist;
File	*cmd;
jmp_buf	mainloop;
List	tempfile = { 'p' };
int	quitok = TRUE;
int	downloaded;
int	dflag;
int	Rflag;
char	*machine;
char	*home;
int	bpipeok;
int	termlocked;
char	*samterm = SAMTERM;
char	*rsamname = RSAM;
File	*lastfile;
Disk	*disk;
long	seq;

char *winsize;

Rune	baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};

void	usage(void);

extern int notify(void(*)(void*,char*));

void
main(int _argc, char **_argv)
{
	volatile int i, argc;
	char **volatile argv;
	String *t;
	char *termargs[10], **ap;
	
	argc = _argc;
	argv = _argv;
	ap = termargs;
	*ap++ = "samterm";
	ARGBEGIN{
	case 'd':
		dflag++;
		break;
	case 'r':
		machine = EARGF(usage());
		break;
	case 'R':
		Rflag++;
		break;
	case 't':
		samterm = EARGF(usage());
		break;
	case 's':
		rsamname = EARGF(usage());
		break;
	default:
		dprint("sam: unknown flag %c\n", ARGC());
		usage();
	/* options for samterm */
	case 'a':
		*ap++ = "-a";
		break;
	case 'W':
		*ap++ = "-W";
		*ap++ = EARGF(usage());
		break;
	}ARGEND
	*ap = nil;

	Strinit(&cmdstr);
	Strinit0(&lastpat);
	Strinit0(&lastregexp);
	Strinit0(&genstr);
	Strinit0(&rhs);
	Strinit0(&curwd);
	Strinit0(&plan9cmd);
	home = getenv(HOME);
	disk = diskinit();
	if(home == 0)
		home = "/";
	if(!dflag)
		startup(machine, Rflag, termargs, (char**)argv);
	notify(notifyf);
	getcurwd();
	if(argc>0){
		for(i=0; i<argc; i++){
			if(!setjmp(mainloop)){
				t = tmpcstr(argv[i]);
				Straddc(t, '\0');
				Strduplstr(&genstr, t);
				freetmpstr(t);
				fixname(&genstr);
				logsetname(newfile(), &genstr);
			}
		}
	}else if(!downloaded)
		newfile();
	seq++;
	if(file.nused)
		current(file.filepptr[0]);
	setjmp(mainloop);
	cmdloop();
	trytoquit();	/* if we already q'ed, quitok will be TRUE */
	exits(0);
}

void
usage(void)
{
	dprint("usage: sam [-d] [-t samterm] [-s sam name] [-r machine] [file ...]\n");
	exits("usage");
}

void
rescue(void)
{
	int i, nblank = 0;
	File *f;
	char *c;
	char buf[256];
	char *root;

	if(rescuing++)
		return;
	io = -1;
	for(i=0; i<file.nused; i++){
		f = file.filepptr[i];
		if(f==cmd || f->b.nc==0 || !fileisdirty(f))
			continue;
		if(io == -1){
			sprint(buf, "%s/sam.save", home);
			io = create(buf, 1, 0777);
			if(io<0)
				return;
		}
		if(f->name.s[0]){
			c = Strtoc(&f->name);
			strncpy(buf, c, sizeof buf-1);
			buf[sizeof buf-1] = 0;
			free(c);
		}else
			sprint(buf, "nameless.%d", nblank++);
		root = getenv("PLAN9");
		if(root == nil)
			root = "/usr/local/plan9";
		fprint(io, "#!/bin/sh\n%s/bin/samsave '%s' $* <<'---%s'\n", root, buf, buf);
		addr.r.p1 = 0, addr.r.p2 = f->b.nc;
		writeio(f);
		fprint(io, "\n---%s\n", (char *)buf);
	}
}

void
panic(char *s)
{
	int wasd;

	if(!panicking++ && !setjmp(mainloop)){
		wasd = downloaded;
		downloaded = 0;
		dprint("sam: panic: %s: %r\n", s);
		if(wasd)
			fprint(2, "sam: panic: %s: %r\n", s);
		rescue();
		abort();
	}
}

void
hiccough(char *s)
{
	File *f;
	int i;

	if(rescuing)
		exits("rescue");
	if(s)
		dprint("%s\n", s);
	resetcmd();
	resetxec();
	resetsys();
	if(io > 0)
		close(io);

	/*
	 * back out any logged changes & restore old sequences
	 */
	for(i=0; i<file.nused; i++){
		f = file.filepptr[i];
		if(f==cmd)
			continue;
		if(f->seq==seq){
			bufdelete(&f->epsilon, 0, f->epsilon.nc);
			f->seq = f->prevseq;
			f->dot.r = f->prevdot;
			f->mark = f->prevmark;
			state(f, f->prevmod ? Dirty: Clean);
		}
	}

	update();
	if (curfile) {
		if (curfile->unread)
			curfile->unread = FALSE;
		else if (downloaded)
			outTs(Hcurrent, curfile->tag);
	}
	longjmp(mainloop, 1);
}

void
intr(void)
{
	error(Eintr);
}

void
trytoclose(File *f)
{
	char *t;
	char buf[256];

	if(f == cmd)	/* possible? */
		return;
	if(f->deleted)
		return;
	if(fileisdirty(f) && !f->closeok){
		f->closeok = TRUE;
		if(f->name.s[0]){
			t = Strtoc(&f->name);
			strncpy(buf, t, sizeof buf-1);
			free(t);
		}else
			strcpy(buf, "nameless file");
		error_s(Emodified, buf);
	}
	f->deleted = TRUE;
}

void
trytoquit(void)
{
	int c;
	File *f;

	if(!quitok){
		for(c = 0; c<file.nused; c++){
			f = file.filepptr[c];
			if(f!=cmd && fileisdirty(f)){
				quitok = TRUE;
				eof = FALSE;
				error(Echanges);
			}
		}
	}
}

void
load(File *f)
{
	Address saveaddr;

	Strduplstr(&genstr, &f->name);
	filename(f);
	if(f->name.s[0]){
		saveaddr = addr;
		edit(f, 'I');
		addr = saveaddr;
	}else{
		f->unread = 0;
		f->cleanseq = f->seq;
	}

	fileupdate(f, TRUE, TRUE);
}

void
cmdupdate(void)
{
	if(cmd && cmd->seq!=0){
		fileupdate(cmd, FALSE, downloaded);
		cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->b.nc;
		telldot(cmd);
	}
}

void
delete(File *f)
{
	if(downloaded && f->rasp)
		outTs(Hclose, f->tag);
	delfile(f);
	if(f == curfile)
		current(0);
}

void
update(void)
{
	int i, anymod;
	File *f;

	settempfile();
	for(anymod = i=0; i<tempfile.nused; i++){
		f = tempfile.filepptr[i];
		if(f==cmd)	/* cmd gets done in main() */
			continue;
		if(f->deleted) {
			delete(f);
			continue;
		}
		if(f->seq==seq && fileupdate(f, FALSE, downloaded))
			anymod++;
		if(f->rasp)
			telldot(f);
	}
	if(anymod)
		seq++;
}

File *
current(File *f)
{
	return curfile = f;
}

void
edit(File *f, int cmd)
{
	int empty = TRUE;
	Posn p;
	int nulls;

	if(cmd == 'r')
		logdelete(f, addr.r.p1, addr.r.p2);
	if(cmd=='e' || cmd=='I'){
		logdelete(f, (Posn)0, f->b.nc);
		addr.r.p2 = f->b.nc;
	}else if(f->b.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
		empty = FALSE;
	if((io = open(genc, OREAD))<0) {
		if (curfile && curfile->unread)
			curfile->unread = FALSE;
		error_r(Eopen, genc);
	}
	p = readio(f, &nulls, empty, TRUE);
	closeio((cmd=='e' || cmd=='I')? -1 : p);
	if(cmd == 'r')
		f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
	else
		f->ndot.r.p1 = f->ndot.r.p2 = 0;
	f->closeok = empty;
	if (quitok)
		quitok = empty;
	else
		quitok = FALSE;
	state(f, empty && !nulls? Clean : Dirty);
	if(empty && !nulls)
		f->cleanseq = f->seq;
	if(cmd == 'e')
		filename(f);
}

int
getname(File *f, String *s, int save)
{
	int c, i;

	Strzero(&genstr);
	if(genc){
		free(genc);
		genc = 0;
	}
	if(s==0 || (c = s->s[0])==0){		/* no name provided */
		if(f)
			Strduplstr(&genstr, &f->name);
		goto Return;
	}
	if(c!=' ' && c!='\t')
		error(Eblank);
	for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
		;
	while(s->s[i] > ' ')
		Straddc(&genstr, s->s[i++]);
	if(s->s[i])
		error(Enewline);
	fixname(&genstr);
	if(f && (save || f->name.s[0]==0)){
		logsetname(f, &genstr);
		if(Strcmp(&f->name, &genstr)){
			quitok = f->closeok = FALSE;
			f->qidpath = 0;
			f->mtime = 0;
			state(f, Dirty); /* if it's 'e', fix later */
		}
	}
    Return:
	genc = Strtoc(&genstr);
	i = genstr.n;
	if(i && genstr.s[i-1]==0)
		i--;
	return i;	/* strlen(name) */
}

void
filename(File *f)
{
	if(genc)
		free(genc);
	genc = Strtoc(&genstr);
	dprint("%c%c%c %s\n", " '"[f->mod],
		"-+"[f->rasp!=0], " ."[f==curfile], genc);
}

void
undostep(File *f, int isundo)
{
	uint p1, p2;
	int mod;

	mod = f->mod;
	fileundo(f, isundo, 1, &p1, &p2, TRUE);
	f->ndot = f->dot;
	if(f->mod){
		f->closeok = 0;
		quitok = 0;
	}else
		f->closeok = 1;

	if(f->mod != mod){
		f->mod = mod;
		if(mod)
			mod = Clean;
		else
			mod = Dirty;
		state(f, mod);
	}
}

int
undo(int isundo)
{
	File *f;
	int i;
	Mod max;

	max = undoseq(curfile, isundo);
	if(max == 0)
		return 0;
	settempfile();
	for(i = 0; i<tempfile.nused; i++){
		f = tempfile.filepptr[i];
		if(f!=cmd && undoseq(f, isundo)==max)
			undostep(f, isundo);
	}
	return 1;
}

int
readcmd(String *s)
{
	int retcode;

	if(flist != 0)
		fileclose(flist);
	flist = fileopen();

	addr.r.p1 = 0, addr.r.p2 = flist->b.nc;
	retcode = plan9(flist, '<', s, FALSE);
	fileupdate(flist, FALSE, FALSE);
	flist->seq = 0;
	if (flist->b.nc > BLOCKSIZE)
		error(Etoolong);
	Strzero(&genstr);
	Strinsure(&genstr, flist->b.nc);
	bufread(&flist->b, (Posn)0, genbuf, flist->b.nc);
	memmove(genstr.s, genbuf, flist->b.nc*RUNESIZE);
	genstr.n = flist->b.nc;
	Straddc(&genstr, '\0');
	return retcode;
}

void
getcurwd(void)
{
	String *t;
	char buf[256];

	buf[0] = 0;
	getwd(buf, sizeof(buf));
	t = tmpcstr(buf);
	Strduplstr(&curwd, t);
	freetmpstr(t);
	if(curwd.n == 0)
		warn(Wpwd);
	else if(curwd.s[curwd.n-1] != '/')
		Straddc(&curwd, '/');
}

void
cd(String *str)
{
	int i, fd;
	char *s;
	File *f;
	String owd;

	getcurwd();
	if(getname((File *)0, str, FALSE))
		s = genc;
	else
		s = home;
	if(chdir(s))
		syserror("chdir");
	fd = open("/dev/wdir", OWRITE);
	if(fd > 0)
		write(fd, s, strlen(s));
	dprint("!\n");
	Strinit(&owd);
	Strduplstr(&owd, &curwd);
	getcurwd();
	settempfile();
	/*
	 * Two passes so that if we have open
	 * /a/foo.c and /b/foo.c and cd from /b to /a,
	 * we don't ever have two foo.c simultaneously.
	 */
	for(i=0; i<tempfile.nused; i++){
		f = tempfile.filepptr[i];
		if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
			Strinsert(&f->name, &owd, (Posn)0);
			fixname(&f->name);
			sortname(f);
		}
	}
	for(i=0; i<tempfile.nused; i++){
		f = tempfile.filepptr[i];
		if(f != cmd && Strispre(&curwd, &f->name)){
			fixname(&f->name);
			sortname(f);
		}
	}
	Strclose(&owd);
}

int
loadflist(String *s)
{
	int c, i;

	c = s->s[0];
	for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
		;
	if((c==' ' || c=='\t') && s->s[i]!='\n'){
		if(s->s[i]=='<'){
			Strdelete(s, 0L, (long)i+1);
			readcmd(s);
		}else{
			Strzero(&genstr);
			while((c = s->s[i++]) && c!='\n')
				Straddc(&genstr, c);
			Straddc(&genstr, '\0');
		}
	}else{
		if(c != '\n')
			error(Eblank);
		Strdupl(&genstr, empty);
	}
	if(genc)
		free(genc);
	genc = Strtoc(&genstr);
	return genstr.s[0];
}

File *
readflist(int readall, int delete)
{
	Posn i;
	int c;
	File *f;
	String t;

	Strinit(&t);
	for(i=0,f=0; f==0 || readall || delete; i++){	/* ++ skips blank */
		Strdelete(&genstr, (Posn)0, i);
		for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
			;
		if(i >= genstr.n)
			break;
		Strdelete(&genstr, (Posn)0, i);
		for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
			;

		if(i == 0)
			break;
		genstr.s[i] = 0;
		Strduplstr(&t, tmprstr(genstr.s, i+1));
		fixname(&t);
		f = lookfile(&t);
		if(delete){
			if(f == 0)
				warn_S(Wfile, &t);
			else
				trytoclose(f);
		}else if(f==0 && readall)
			logsetname(f = newfile(), &t);
	}
	Strclose(&t);
	return f;
}

File *
tofile(String *s)
{
	File *f;

	if(s->s[0] != ' ')
		error(Eblank);
	if(loadflist(s) == 0){
		f = lookfile(&genstr);	/* empty string ==> nameless file */
		if(f == 0)
			error_s(Emenu, genc);
	}else if((f=readflist(FALSE, FALSE)) == 0)
		error_s(Emenu, genc);
	return current(f);
}

File *
getfile(String *s)
{
	File *f;

	if(loadflist(s) == 0)
		logsetname(f = newfile(), &genstr);
	else if((f=readflist(TRUE, FALSE)) == 0)
		error(Eblank);
	return current(f);
}

void
closefiles(File *f, String *s)
{
	if(s->s[0] == 0){
		if(f == 0)
			error(Enofile);
		trytoclose(f);
		return;
	}
	if(s->s[0] != ' ')
		error(Eblank);
	if(loadflist(s) == 0)
		error(Enewline);
	readflist(FALSE, TRUE);
}

void
copy(File *f, Address addr2)
{
	Posn p;
	int ni;
	for(p=addr.r.p1; p<addr.r.p2; p+=ni){
		ni = addr.r.p2-p;
		if(ni > BLOCKSIZE)
			ni = BLOCKSIZE;
		bufread(&f->b, p, genbuf, ni);
		loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
	}
	addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
	addr2.f->ndot.r.p1 = addr2.r.p2;
}

void
move(File *f, Address addr2)
{
	if(addr.r.p2 <= addr2.r.p2){
		logdelete(f, addr.r.p1, addr.r.p2);
		copy(f, addr2);
	}else if(addr.r.p1 >= addr2.r.p2){
		copy(f, addr2);
		logdelete(f, addr.r.p1, addr.r.p2);
	}else
		error(Eoverlap);
}

Posn
nlcount(File *f, Posn p0, Posn p1)
{
	Posn nl = 0;

	while(p0 < p1)
		if(filereadc(f, p0++)=='\n')
			nl++;
	return nl;
}

void
printposn(File *f, int charsonly)
{
	Posn l1, l2;

	if(!charsonly){
		l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
		l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
		/* check if addr ends with '\n' */
		if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
			--l2;
		dprint("%lud", l1);
		if(l2 != l1)
			dprint(",%lud", l2);
		dprint("; ");
	}
	dprint("#%lud", addr.r.p1);
	if(addr.r.p2 != addr.r.p1)
		dprint(",#%lud", addr.r.p2);
	dprint("\n");
}

void
settempfile(void)
{
	if(tempfile.nalloc < file.nused){
		if(tempfile.filepptr)
			free(tempfile.filepptr);
		tempfile.filepptr = emalloc(sizeof(File*)*file.nused);
		tempfile.nalloc = file.nused;
	}
	memmove(tempfile.filepptr, file.filepptr, sizeof(File*)*file.nused);
	tempfile.nused = file.nused;
}
