#include "stdinc.h"

#include "9.h"

static struct {
	QLock	lock;

	Con*	con;
	int	confd[2];
	ushort	tag;
} cbox;

static ulong
cmd9pStrtoul(char* s)
{
	if(strcmp(s, "~0") == 0)
		return ~0UL;
	return strtoul(s, 0, 0);
}

static uvlong
cmd9pStrtoull(char* s)
{
	if(strcmp(s, "~0") == 0)
		return ~0ULL;
	return strtoull(s, 0, 0);
}

static int
cmd9pTag(Fcall* f, int i, char **argv)
{
	USED(f);
	USED(i);
	cbox.tag = strtoul(argv[0], 0, 0)-1;

	return 1;
}

static int
cmd9pTwstat(Fcall* f, int i, char **argv)
{
	Dir d;
	static uchar buf[DIRMAX];

	USED(i);
	memset(&d, 0, sizeof d);
	nulldir(&d);
	d.name = argv[1];
	d.uid = argv[2];
	d.gid = argv[3];
	d.mode = cmd9pStrtoul(argv[4]);
	d.mtime = cmd9pStrtoul(argv[5]);
	d.length = cmd9pStrtoull(argv[6]);

	f->fid = strtol(argv[0], 0, 0);
	f->stat = buf;
	f->nstat = convD2M(&d, buf, sizeof buf);
	if(f->nstat < BIT16SZ){
		werrstr("Twstat: convD2M failed (internal error)");
		return 0;
	}

	return 1;
}

static int
cmd9pTstat(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);

	return 1;
}

static int
cmd9pTremove(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);

	return 1;
}

static int
cmd9pTclunk(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);

	return 1;
}

static int
cmd9pTwrite(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);
	f->offset = strtoll(argv[1], 0, 0);
	f->data = argv[2];
	f->count = strlen(argv[2]);

	return 1;
}

static int
cmd9pTread(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);
	f->offset = strtoll(argv[1], 0, 0);
	f->count = strtol(argv[2], 0, 0);

	return 1;
}

static int
cmd9pTcreate(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);
	f->name = argv[1];
	f->perm = strtol(argv[2], 0, 8);
	f->mode = strtol(argv[3], 0, 0);

	return 1;
}

static int
cmd9pTopen(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);
	f->mode = strtol(argv[1], 0, 0);

	return 1;
}

static int
cmd9pTwalk(Fcall* f, int argc, char** argv)
{
	int i;

	if(argc < 2){
		werrstr("usage: Twalk tag fid newfid [name...]");
		return 0;
	}
	f->fid = strtol(argv[0], 0, 0);
	f->newfid = strtol(argv[1], 0, 0);
	f->nwname = argc-2;
	if(f->nwname > MAXWELEM){
		werrstr("Twalk: too many names");
		return 0;
	}
	for(i = 0; i < argc-2; i++)
		f->wname[i] = argv[2+i];

	return 1;
}

static int
cmd9pTflush(Fcall* f, int i, char** argv)
{
	USED(i);
	f->oldtag = strtol(argv[0], 0, 0);

	return 1;
}

static int
cmd9pTattach(Fcall* f, int i, char** argv)
{
	USED(i);
	f->fid = strtol(argv[0], 0, 0);
	f->afid = strtol(argv[1], 0, 0);
	f->uname = argv[2];
	f->aname = argv[3];

	return 1;
}

static int
cmd9pTauth(Fcall* f, int i, char** argv)
{
	USED(i);
	f->afid = strtol(argv[0], 0, 0);
	f->uname = argv[1];
	f->aname = argv[2];

	return 1;
}

static int
cmd9pTversion(Fcall* f, int i, char** argv)
{
	USED(i);
	f->msize = strtoul(argv[0], 0, 0);
	if(f->msize > cbox.con->msize){
		werrstr("msize too big");
		return 0;
	}
	f->version = argv[1];

	return 1;
}

typedef struct Cmd9p Cmd9p;
struct Cmd9p {
	char*	name;
	int	type;
	int	argc;
	char*	usage;
	int	(*f)(Fcall*, int, char**);
};

static Cmd9p cmd9pTmsg[] = {
	"Tversion", Tversion, 2, "msize version", cmd9pTversion,
	"Tauth", Tauth, 3, "afid uname aname", cmd9pTauth,
	"Tflush", Tflush, 1, "oldtag", cmd9pTflush,
	"Tattach", Tattach, 4, "fid afid uname aname", cmd9pTattach,
	"Twalk", Twalk, 0, "fid newfid [name...]", cmd9pTwalk,
	"Topen", Topen, 2, "fid mode", cmd9pTopen,
	"Tcreate", Tcreate, 4, "fid name perm mode", cmd9pTcreate,
	"Tread", Tread, 3, "fid offset count", cmd9pTread,
	"Twrite", Twrite, 3, "fid offset data", cmd9pTwrite,
	"Tclunk", Tclunk, 1, "fid", cmd9pTclunk,
	"Tremove", Tremove, 1, "fid", cmd9pTremove,
	"Tstat", Tstat, 1, "fid", cmd9pTstat,
	"Twstat", Twstat, 7, "fid name uid gid mode mtime length", cmd9pTwstat,
	"nexttag", 0, 0, "", cmd9pTag,
};

static int
cmd9p(int argc, char* argv[])
{
	int i, n;
	Fcall f, t;
	uchar *buf;
	char *usage;
	u32int msize;

	usage = "usage: 9p T-message ...";

	ARGBEGIN{
	default:
		return cliError(usage);
	}ARGEND
	if(argc < 1)
		return cliError(usage);

	for(i = 0; i < nelem(cmd9pTmsg); i++){
		if(strcmp(cmd9pTmsg[i].name, argv[0]) == 0)
			break;
	}
	if(i == nelem(cmd9pTmsg))
		return cliError(usage);
	argc--;
	argv++;
	if(cmd9pTmsg[i].argc && argc != cmd9pTmsg[i].argc){
		werrstr("usage: %s %s",
			cmd9pTmsg[i].name, cmd9pTmsg[i].usage);
		return 0;
	}

	memset(&t, 0, sizeof(t));
	t.type = cmd9pTmsg[i].type;
	if(t.type == Tversion)
		t.tag = NOTAG;
	else
		t.tag = ++cbox.tag;
	msize = cbox.con->msize;
	if(!cmd9pTmsg[i].f(&t, argc, argv))
		return 0;
	buf = vtmalloc(msize);
	n = convS2M(&t, buf, msize);
	if(n <= BIT16SZ){
		werrstr("%s: convS2M error", cmd9pTmsg[i].name);
		vtfree(buf);
		return 0;
	}
	if(write(cbox.confd[0], buf, n) != n){
		werrstr("%s: write error: %r", cmd9pTmsg[i].name);
		vtfree(buf);
		return 0;
	}
	consPrint("\t-> %F\n", &t);

	if((n = read9pmsg(cbox.confd[0], buf, msize)) <= 0){
		werrstr("%s: read error: %r", cmd9pTmsg[i].name);
		vtfree(buf);
		return 0;
	}
	if(convM2S(buf, n, &f) == 0){
		werrstr("%s: convM2S error", cmd9pTmsg[i].name);
		vtfree(buf);
		return 0;
	}
	consPrint("\t<- %F\n", &f);

	vtfree(buf);
	return 1;
}

static int
cmdDot(int argc, char* argv[])
{
	long l;
	Dir *dir;
	int fd, r;
	vlong length;
	char *f, *p, *s, *usage;

	usage = "usage: . file";

	ARGBEGIN{
	default:
		return cliError(usage);
	}ARGEND
	if(argc != 1)
		return cliError(usage);

	if((dir = dirstat(argv[0])) == nil)
		return cliError(". dirstat %s: %r", argv[0]);
	length = dir->length;
	free(dir);

	r = 1;
	if(length != 0){
		/*
		 * Read the whole file in.
		 */
		if((fd = open(argv[0], OREAD)) < 0)
			return cliError(". open %s: %r", argv[0]);
		f = vtmalloc(dir->length+1);
		if((l = read(fd, f, length)) < 0){
			vtfree(f);
			close(fd);
			return cliError(". read %s: %r", argv[0]);
		}
		close(fd);
		f[l] = '\0';

		/*
		 * Call cliExec() for each line.
		 */
		for(p = s = f; *p != '\0'; p++){
			if(*p == '\n'){
				*p = '\0';
				if(cliExec(s) == 0){
					r = 0;
					consPrint("%s: %r\n", s);
				}
				s = p+1;
			}
		}
		vtfree(f);
	}

	if(r == 0)
		werrstr("errors in . %#q", argv[0]);
	return r;
}

static int
cmdDflag(int argc, char* argv[])
{
	char *usage;

	usage = "usage: dflag";

	ARGBEGIN{
	default:
		return cliError(usage);
	}ARGEND
	if(argc)
		return cliError(usage);

	Dflag ^= 1;
	consPrint("dflag %d\n", Dflag);

	return 1;
}

static int
cmdEcho(int argc, char* argv[])
{
	char *usage;
	int i, nflag;

	nflag = 0;
	usage = "usage: echo [-n] ...";

	ARGBEGIN{
	default:
		return cliError(usage);
	case 'n':
		nflag = 1;
		break;
	}ARGEND

	for(i = 0; i < argc; i++){
		if(i != 0)
			consPrint(" %s", argv[i]);
		else
			consPrint(argv[i]);
	}
	if(!nflag)
		consPrint("\n");

	return 1;
}

static int
cmdBind(int argc, char* argv[])
{
	ulong flag = 0;
	char *usage;

	usage = "usage: bind [-b|-a|-c|-bc|-ac] new old";

	ARGBEGIN{
	case 'a':
		flag |= MAFTER;
		break;
	case 'b':
		flag |= MBEFORE;
		break;
	case 'c':
		flag |= MCREATE;
		break;
	default:
		return cliError(usage);
	}ARGEND

	if(argc != 2 || (flag&MAFTER)&&(flag&MBEFORE))
		return cliError(usage);

#ifndef PLAN9PORT
	if(bind(argv[0], argv[1], flag) < 0){
		/* try to give a less confusing error than the default */
		if(access(argv[0], 0) < 0)
			return cliError("bind: %s: %r", argv[0]);
		else if(access(argv[1], 0) < 0)
			return cliError("bind: %s: %r", argv[1]);
		else
			return cliError("bind %s %s: %r", argv[0], argv[1]);
	}
#endif
	return 1;
}

int
cmdInit(void)
{
	cbox.confd[0] = cbox.confd[1] = -1;

	cliAddCmd(".", cmdDot);
	cliAddCmd("9p", cmd9p);
	cliAddCmd("dflag", cmdDflag);
	cliAddCmd("echo", cmdEcho);
	cliAddCmd("bind", cmdBind);

	if(pipe(cbox.confd) < 0)
		return 0;
	if((cbox.con = conAlloc(cbox.confd[1], "console", 0)) == nil){
		close(cbox.confd[0]);
		close(cbox.confd[1]);
		cbox.confd[0] = cbox.confd[1] = -1;
		return 0;

	}
	cbox.con->isconsole = 1;

	return 1;
}
