#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <venti.h>
#include <sunrpc.h>
#include <nfs3.h>
#include <diskfs.h>

uchar *buf;
uint bufsize;
Nfs3Handle cwd, root;
Biobuf bin, bout;
char pwd[1000];
Fsys *fsys;
SunAuthUnix *auth;
VtConn *z;
VtCache *c;
Disk *disk;

char *cmdhelp(int, char**);
char *cmdcd(int, char**);
char *cmdpwd(int, char**);
char *cmdls(int, char**);
char *cmdget(int, char**);
char *cmdblock(int, char**);
char *cmddisk(int, char**);

typedef struct Cmd Cmd;
struct Cmd
{
	char *s;
	char *(*fn)(int, char**);
	char *help;
};

Cmd cmdtab[] =
{
	"cd", cmdcd, "cd dir - change directory",
	"ls", cmdls, "ls [-d] path... - list file",
	"get", cmdget, "get path [lpath] - copy file to local directory",
	"pwd", cmdpwd, "pwd - print working directory",
	"help", cmdhelp, "help - print usage summaries",
	"block", cmdblock, "block path offset - print disk offset of path's byte offset",
	"disk", cmddisk, "disk offset count - dump disk contents"
};

char*
ebuf(void)
{
	static char buf[ERRMAX];

	rerrstr(buf, sizeof buf);
	return buf;
}

static char*
estrdup(char *s)
{
	char *t;

	t = emalloc(strlen(s)+1);
	strcpy(t, s);
	return t;
}

char*
walk(char *path, Nfs3Handle *ph)
{
	char *p, *q;
	Nfs3Handle h;
	Nfs3Status ok;

	path = estrdup(path); /* writable */
	if(path[0] == '/')
		h = root;
	else
		h = cwd;
	for(p=path; *p; p=q){
		q = strchr(p, '/');
		if(q == nil)
			q = p+strlen(p);
		else
			*q++ = 0;
		if(*p == 0)
			continue;
		if((ok = fsyslookup(fsys, auth, &h, p, &h)) != Nfs3Ok){
			nfs3errstr(ok);
			free(path);
			return ebuf();
		}
	}
	*ph = h;
	free(path);
	return nil;
}

char*
cmdhelp(int argc, char **argv)
{
	int i;

	for(i=0; i<nelem(cmdtab); i++)
		print("%s\n", cmdtab[i].help);
	return nil;
}

char*
cmdcd(int argc, char **argv)
{
	char *err;
	Nfs3Attr attr;
	Nfs3Status ok;
	Nfs3Handle h;

	if(argc != 2)
		return "usage: cd dir";

	if((err = walk(argv[1], &h)) != nil)
		return err;
	if((ok = fsysgetattr(fsys, auth, &h, &attr)) != Nfs3Ok){
		nfs3errstr(ok);
		fprint(2, "%s: %r\n", argv[1]);
		return nil;
	}
	if(attr.type != Nfs3FileDir)
		return "not a directory";
	if(argv[1][0] == '/')
		pwd[0] = 0;
	strcat(pwd, "/");
	strcat(pwd, argv[1]);
	cleanname(pwd);
	cwd = h;
	print("%s\n", pwd);
	return nil;
}

char*
cmdpwd(int argc, char **argv)
{
	if(argc != 1)
		return "usage: pwd";

	print("%s\n", pwd);
	return nil;
}

/*
 * XXX maybe make a list of these in memory and then print them nicer
 */
void
ls(char *dir, char *elem, Nfs3Attr *attr)
{
	char c;

	c = ' ';	/* use attr->type */
	Bprint(&bout, "%s%s%s", dir ? dir : "", dir && elem ? "/" : "", elem ? elem : "");
	Bprint(&bout, " %c%luo %1d %4d %4d", c, attr->mode, attr->nlink, attr->uid, attr->gid);
	Bprint(&bout, " %11,lld %11,lld %4d.%4d %#11,llux %#11,llux",
		attr->size, attr->used, attr->major, attr->minor, attr->fsid, attr->fileid);
	Bprint(&bout, "\n");
}

void
lsdir(char *dir, Nfs3Handle *h)
{
	uchar *data, *p, *ep;
	Nfs3Attr attr;
	Nfs3Entry e;
	Nfs3Handle eh;
	u32int count;
	u1int eof;
	Nfs3Status ok;
	u64int cookie;

	cookie = 0;
	for(;;){
		ok = fsysreaddir(fsys, auth, h, 8192, cookie, &data, &count, &eof);
		if(ok != Nfs3Ok){
			nfs3errstr(ok);
			fprint(2, "ls %s: %r\n", dir);
			return;
		}
fprint(2, "got %d\n", count);
		p = data;
		ep = data+count;
		while(p<ep){
			if(nfs3entryunpack(p, ep, &p, &e) < 0){
				fprint(2, "%s: unpacking directory: %r\n", dir);
				break;
			}
			cookie = e.cookie;
			if((ok = fsyslookup(fsys, auth, h, e.name, &eh)) != Nfs3Ok){
				nfs3errstr(ok);
				fprint(2, "%s/%s: %r\n", dir, e.name);
				continue;
			}
			if((ok = fsysgetattr(fsys, auth, &eh, &attr)) != Nfs3Ok){
				nfs3errstr(ok);
				fprint(2, "%s/%s: %r\n", dir, e.name);
				continue;
			}
			ls(dir, e.name, &attr);
		}
		free(data);
		if(eof)
			break;
	}
}

char*
cmdls(int argc, char **argv)
{
	int i;
	int dflag;
	char *e;
	Nfs3Handle h;
	Nfs3Attr attr;
	Nfs3Status ok;

	dflag = 0;
	ARGBEGIN{
	case 'd':
		dflag = 1;
		break;
	default:
		return "usage: ls [-d] [path...]";
	}ARGEND

	if(argc == 0){
		lsdir(nil, &cwd);
		Bflush(&bout);
		return nil;
	}

	for(i=0; i<argc; i++){
		if((e = walk(argv[i], &h)) != nil){
			fprint(2, "%s: %s\n", argv[i], e);
			continue;
		}
		if((ok = fsysgetattr(fsys, auth, &h, &attr)) != Nfs3Ok){
			nfs3errstr(ok);
			fprint(2, "%s: %r\n", argv[i]);
			continue;
		}
		if(attr.type != Nfs3FileDir || dflag)
			ls(argv[i], nil, &attr);
		else
			lsdir(argv[i], &h);
		Bflush(&bout);
	}
	return nil;
}

char*
cmdget(int argc, char **argv)
{
	uchar eof;
	u32int n;
	int dflag, fd;
	char *e, *local;
	uchar *buf;
	Nfs3Handle h;
	Nfs3Attr attr;
	Nfs3Status ok;
	vlong o;

	dflag = 0;
	ARGBEGIN{
	default:
	usage:
		return "usage: get path [lpath]]";
	}ARGEND

	if(argc != 1 && argc != 2)
		goto usage;

	if((e = walk(argv[0], &h)) != nil){
		fprint(2, "%s: %s\n", argv[0], e);
		return nil;
	}
	if((ok = fsysgetattr(fsys, auth, &h, &attr)) != Nfs3Ok){
		nfs3errstr(ok);
		fprint(2, "%s: %r\n", argv[0]);
		return nil;
	}
	local = argv[0];
	if(argc == 2)
		local = argv[1];
	if((fd = create(local, OWRITE, 0666)) < 0){
		fprint(2, "create %s: %r\n", local);
		return nil;
	}
	eof = 0;
	for(o=0; o<attr.size && !eof; o+=n){
		if((ok = fsysreadfile(fsys, nil, &h, fsys->blocksize, o, &buf, &n, &eof)) != Nfs3Ok){
			nfs3errstr(ok);
			fprint(2, "reading %s: %r\n", argv[0]);
			close(fd);
			return nil;
		}
		if(write(fd, buf, n) != n){
			fprint(2, "writing %s: %r\n", local);
			close(fd);
			free(buf);
			return nil;
		}
		free(buf);
	}
	close(fd);
	fprint(2, "copied %,lld bytes\n", o);
	return nil;
}


char*
cmdblock(int argc, char **argv)
{
	char *e;
	Nfs3Handle h;
	u64int bno;

	ARGBEGIN{
	default:
		return "usage: block path offset";
	}ARGEND

	if(argc != 2)
		return "usage: block path offset";

	if((e = walk(argv[0], &h)) != nil){
		fprint(2, "%s: %s\n", argv[0], e);
		return nil;
	}
	if((bno = fsys->fileblock(fsys, &h, strtoll(argv[1], 0, 0))) == 0){
		fprint(2, "%s: %r\n", argv[0]);
		return nil;
	}
	print("%#llux\n", bno);
	return nil;
}

char*
cmddisk(int argc, char **argv)
{
	Block *b;
	int delta, count, i;
	u64int offset;
	uchar *p;

	ARGBEGIN{
	default:
		return "usage: disk offset count";
	}ARGEND

	if(argc != 2)
		return "usage: disk offset count";

	offset = strtoull(argv[0], 0, 0);
	count = atoi(argv[1]);
	delta = offset%fsys->blocksize;

	b = diskread(disk, fsys->blocksize, offset-delta);
	if(b == nil){
		fprint(2, "diskread: %r\n");
		return nil;
	}
	p = b->data + delta;
	for(i=0; i<count; i++){
		Bprint(&bout, "%2.2ux ", p[i]);
		if(i%16 == 15)
			Bprint(&bout, "\n");
		else if(i%8 == 7)
			Bprint(&bout, " - ");
	}
	if(i%16 != 0)
		Bprint(&bout, "\n");
	Bflush(&bout);
	blockput(b);
	return nil;
}

void
usage(void)
{
	fprint(2, "usage: vftp score\n");
	threadexitsall("usage");
}

extern int allowall;

void
threadmain(int argc, char **argv)
{
	char *err, *f[10], *p;
	int i, nf;
	uchar score[VtScoreSize];
	Nfs3Status ok;

	allowall = 1;
	ARGBEGIN{
	case 'V':
		chattyventi++;
		break;
	default:
		usage();
	}ARGEND

	if(argc != 1)
		usage();

	fmtinstall('F', vtfcallfmt);
	fmtinstall('H', encodefmt);
	fmtinstall('V', vtscorefmt);

	if(access(argv[0], AEXIST) >= 0 || strchr(argv[0], '/')){
		if((disk = diskopenfile(argv[0])) == nil)
			sysfatal("diskopen: %r");
		if((disk = diskcache(disk, 32768, 16)) == nil)
			sysfatal("diskcache: %r");
	}else{
		if(vtparsescore(argv[0], nil, score) < 0)
			sysfatal("bad score '%s'", argv[0]);
		if((z = vtdial(nil)) == nil)
			sysfatal("vtdial: %r");
		if(vtconnect(z) < 0)
			sysfatal("vtconnect: %r");
		if((c = vtcachealloc(z, 32768, 32)) == nil)
			sysfatal("vtcache: %r");
		if((disk = diskopenventi(c, score)) == nil)
			sysfatal("diskopenventi: %r");
	}
	if((fsys = fsysopen(disk)) == nil)
		sysfatal("fsysopen: %r");

	fprint(2, "block size %d\n", fsys->blocksize);
	buf = emalloc(fsys->blocksize);
	if((ok = fsysroot(fsys, &root)) != Nfs3Ok){
		nfs3errstr(ok);
		sysfatal("accessing root: %r");
	}
	cwd = root;
	Binit(&bin, 0, OREAD);
	Binit(&bout, 1, OWRITE);

	while(fprint(2, "vftp> "), (p = Brdstr(&bin, '\n', 1)) != nil){
		if(p[0] == '#')
			continue;
		nf = tokenize(p, f, nelem(f));
		if(nf == 0)
			continue;
		for(i=0; i<nelem(cmdtab); i++){
			if(strcmp(f[0], cmdtab[i].s) == 0){
				if((err = cmdtab[i].fn(nf, f)) != nil)
					fprint(2, "%s\n", err);
				break;
			}
		}
		if(i == nelem(cmdtab))
			fprint(2, "unknown command '%s'\n", f[0]);
	}
	threadexitsall(nil);
}