#include <u.h>
#include <libc.h>
#include <thread.h>
#include "ioproc.h"

enum
{
	STACK = 32768,
};

void
iointerrupt(Ioproc *io)
{
	if(!io->inuse)
		return;
	fprint(2, "bug: cannot iointerrupt %p yet\n", io);
}

static void
xioproc(void *a)
{
	Ioproc *io, *x;

	threadsetname("ioproc");
	io = a;
	/*
	 * first recvp acquires the ioproc.
	 * second tells us that the data is ready.
	 */
	for(;;){
		while(recv(io->c, &x) == -1)
			;
		if(x == 0)	/* our cue to leave */
			break;
		assert(x == io);

		/* caller is now committed -- even if interrupted he'll return */
		while(recv(io->creply, &x) == -1)
			;
		if(x == 0)	/* caller backed out */
			continue;
		assert(x == io);

		io->ret = io->op(&io->arg);
		if(io->ret < 0)
			rerrstr(io->err, sizeof io->err);
		while(send(io->creply, &io) == -1)
			;
		while(recv(io->creply, &x) == -1)
			;
	}
}

Ioproc*
ioproc(void)
{
	Ioproc *io;

	io = mallocz(sizeof(*io), 1);
	if(io == nil)
		sysfatal("ioproc malloc: %r");
	io->c = chancreate(sizeof(void*), 0);
	chansetname(io->c, "ioc%p", io->c);
	io->creply = chancreate(sizeof(void*), 0);
	chansetname(io->creply, "ior%p", io->c);
	io->tid = proccreate(xioproc, io, STACK);
	return io;
}

void
closeioproc(Ioproc *io)
{
	if(io == nil)
		return;
	iointerrupt(io);
	while(send(io->c, 0) == -1)
		;
	chanfree(io->c);
	chanfree(io->creply);
	free(io);
}

long
iocall(Ioproc *io, long (*op)(va_list*), ...)
{
	char e[ERRMAX];
	int ret, inted;
	Ioproc *msg;

	if(send(io->c, &io) == -1){
		werrstr("interrupted");
		return -1;
	}
	assert(!io->inuse);
	io->inuse = 1;
	io->op = op;
	va_start(io->arg, op);
	msg = io;
	inted = 0;
	while(send(io->creply, &msg) == -1){
		msg = nil;
		inted = 1;
	}
	if(inted){
		werrstr("interrupted");
		return -1;
	}

	/*
	 * If we get interrupted, we have stick around so that
	 * the IO proc has someone to talk to.  Send it an interrupt
	 * and try again.
	 */
	inted = 0;
	while(recv(io->creply, nil) == -1){
		inted = 1;
		iointerrupt(io);
	}
	USED(inted);
	va_end(io->arg);
	ret = io->ret;
	if(ret < 0)
		strecpy(e, e+sizeof e, io->err);
	io->inuse = 0;

	/* release resources */
	while(send(io->creply, &io) == -1)
		;
	if(ret < 0)
		errstr(e, sizeof e);
	return ret;
}