#include <u.h>
#include <libc.h>
#include <ip.h>
#define NODEFINE
#include <9pm/u.h>
#include <9pm/libc.h>
#include <9pm/ns.h>
#undef NODEFINE
#include <9pm/devip.h>

static long
netread(Conv *c, void *data, long ndata)
{
	long n;

	if((n=read(c->sfd, data, ndata)) < 0)
		pm_oserror();
	return n;
}

static long
netwrite(Conv *c, void *data, long ndata)
{
	long n;

	if((n=write(c->sfd, data, ndata)) < 0){
		pm_oserror();
		pm_nexterror();
	}
	return n;
}

static void
netopen(Conv *c, int fd)
{
	int n;
	char buf[64], num[12];

	c->cfd = fd;
	c->sfd = -1;

	if((n=read(fd, num, sizeof(num)-1)) <= 0){
		pm_oserror();
		c->cfd = -1;
		close(fd);
		pm_nexterror();
	}

	num[n] = '\0';
	c->dirno = atoi(num);
	sprint(buf, "/net/%s/%d/data", c->p->name, c->dirno);
	if((fd = open(buf, ORDWR)) < 0){
		pm_oserror();
		close(c->cfd);
		c->cfd = -1;
		pm_nexterror();
	}
	c->sfd = fd;
}

static void
netclone(Conv *c)
{
	int fd;
	char buf[64];

	sprint(buf, "/net/%s/clone", c->p->name);
	if((fd = open(buf, ORDWR)) < 0){
		pm_oserror();
		pm_nexterror();
	}
	netopen(c, fd);
}

static void
netconnect(Conv *c)
{
	uchar ip[4];
	hnputl(ip, c->raddr);

	if(c->lport) {
		if(fprint(c->cfd, "connect %L %d", ip, c->rport, c->lport) < 0) {
			pm_oserror();
			pm_nexterror();
		}
	} else {
		if(fprint(c->cfd, "connect %L", ip, c->rport) < 0) {
			pm_oserror();
			pm_nexterror();
		}
	}
}

static void
netannounce(Conv *c)
{
	uchar ip[4];
	hnputl(ip, c->raddr);

	if(fprint(c->cfd, "announce %L", ip, c->rport) < 0) {
		pm_oserror();
		pm_nexterror();
	}
}

static void
netlisten(Conv *c, Conv *oc)
{
	char buf[64];
	int lfd;

	sprint(buf, "/net/%s/%d/listen", oc->p->name, oc->dirno);
	if((lfd = open(buf, OREAD)) < 0) {
		pm_oserror();
		pm_nexterror();
	}

	netopen(c, lfd);
}

static void
netclose(Conv *c)
{
	close(c->sfd);
	c->sfd = -1;
	close(c->cfd);
	c->cfd = -1;
}

/* takes two arguments (!) */
static int
Lconv(va_list *va, Fconv *fp)
{
	uchar* addr;
	ushort port;
	char buf[40];

	addr = va_arg(*va, uchar*);
	port = va_arg(*va, int);	/* not ushort */
	if(addr == 0 && port == 0)
		snprint(buf, sizeof buf, "*");
	else if(addr == 0)
		snprint(buf, sizeof buf, "%d", port);
	else if(port == 0)
		snprint(buf, sizeof buf, "%V!*", addr);
	else
		snprint(buf, sizeof buf, "%V!%d", addr, port);
	strconv(buf, fp);
	return 0;
}

static Proto ilproto = {
	"il",
	netread,
	netwrite,
	netclone,
	netconnect,
	netannounce,
	netlisten,
	netclose,
	40,
};

static Proto udpproto = {
	"udp",
	netread,
	netwrite,
	netclone,
	netconnect,
	netannounce,
	netlisten,
	netclose,
	30,
};

static Proto tcpproto = {
	"tcp",
	netread,
	netwrite,
	netclone,
	netconnect,
	netannounce,
	netlisten,
	netclose,
	40,
};

void
ipinit(void (*inst)(Proto*))
{
	fmtinstall('V', eipconv);
	fmtinstall('L', Lconv);
	(*inst)(&tcpproto);
	(*inst)(&udpproto);
	(*inst)(&ilproto);
}

