#define VIRTUAL_PIPE_FLAG_WRITE		1
#define VIRTUAL_PIPE_FLAG_NONBLOCK	2

struct virtual_pipe {
	unsigned char content[VIRTUAL_PIPE_SIZE];
	unsigned used;
	unsigned n_rd;
	unsigned n_wr;
};

static struct virtual_pipe *pipe_desc[FD_SETSIZE] = { NULL };
static unsigned pipe_flags[FD_SETSIZE] = { 0 };

static struct virtual_pipe *get_virtual_pipe(int fd)
{
	struct virtual_pipe *desc;
	if (fd >= FD_SETSIZE || !pipe_desc[fd]) return NULL;
	pipe_lock();
	desc = pipe_desc[fd];
	if (!desc) {
		pipe_unlock();
		return NULL;
	}
	return desc;
}

static int vpipe_read(int fd, void *buf, size_t size)
{
	int should_wake;
	struct virtual_pipe *desc;
test_again:
	if (!(desc = get_virtual_pipe(fd)))
		return -2;
	if (pipe_flags[fd] & VIRTUAL_PIPE_FLAG_WRITE) internal_error("vpipe_read: reading from write pipe descriptor");
	if (!desc->used) {
		if (!desc->n_wr) {
#ifdef TRACE_PIPES
			fprintf(stderr, "read(%d) -> zero.", fd);
#endif
			pipe_unlock();
			return 0;
		}
		if (pipe_flags[fd] & VIRTUAL_PIPE_FLAG_NONBLOCK) {
#ifdef TRACE_PIPES
			fprintf(stderr, "read(%d) -> wouldblock.", fd);
#endif
			pipe_unlock();
			errno = EWOULDBLOCK;
			return -1;
		}
#ifdef TRACE_PIPES
		fprintf(stderr, "read(%d) -> sleep.", fd);
#endif
		pipe_unlock_wait();
		pipe_unlock();
		goto test_again;
	}
	should_wake = desc->used == VIRTUAL_PIPE_SIZE;
	if (size > desc->used)
		size = desc->used;
	memcpy(buf, desc->content, size);
	memmove(desc->content, desc->content + size, desc->used -= size);
#ifdef TRACE_PIPES
	fprintf(stderr, "read(%d) -> %d. (%d)", fd, size, should_wake);
#endif
	pipe_unlock();
	if (should_wake) pipe_wake();
	return size;
}

static int vpipe_write(int fd, const void *buf, size_t size)
{
	int should_wake;
	struct virtual_pipe *desc;
test_again:
	if (!(desc = get_virtual_pipe(fd)))
		return -2;
	if (!(pipe_flags[fd] & VIRTUAL_PIPE_FLAG_WRITE)) internal_error("vpipe_write: writing to read pipe descriptor");
	if (!desc->n_rd) {
#ifdef TRACE_PIPES
		fprintf(stderr, "write(%d) -> epipe.", fd);
#endif
		pipe_unlock();
		errno = EPIPE;
		return -1;
	}
	if (desc->used == VIRTUAL_PIPE_SIZE) {
		if (pipe_flags[fd] & VIRTUAL_PIPE_FLAG_NONBLOCK) {
#ifdef TRACE_PIPES
			fprintf(stderr, "write(%d) -> wouldblock.", fd);
#endif
			pipe_unlock();
			errno = EWOULDBLOCK;
			return -1;
		}
#ifdef TRACE_PIPES
		fprintf(stderr, "write(%d) -> sleep.", fd);
#endif
		pipe_unlock_wait();
		pipe_unlock();
		goto test_again;
	}
	should_wake = !desc->used;
	if (size > (VIRTUAL_PIPE_SIZE - desc->used))
		size = VIRTUAL_PIPE_SIZE - desc->used;
	memcpy(desc->content + desc->used, buf, size);
	desc->used += size;
#ifdef TRACE_PIPES
	fprintf(stderr, "write(%d) -> %d.", fd, size);
#endif
	pipe_unlock();
	if (should_wake) pipe_wake();
	return size;
}

static int vpipe_close(int fd)
{
	struct virtual_pipe *desc;
	if (!(desc = get_virtual_pipe(fd)))
		return -2;
	if (!(pipe_flags[fd] & VIRTUAL_PIPE_FLAG_WRITE)) {
		if (!desc->n_rd) internal_error("vpipe_close: read counter underflow");
		desc->n_rd--;
	} else {
		if (!desc->n_wr) internal_error("vpipe_close: write counter underflow");
		desc->n_wr--;
	}
	pipe_desc[fd] = NULL;
	pipe_flags[fd] = 0;
	pipe_unlock();
	if (!desc->n_rd && !desc->n_wr)
		free(desc);
	return close(fd);
}

static int vpipe_create(int fd[2])
{
	int rs;
	struct virtual_pipe *desc;
	EINTRLOOP(fd[0], open("/dev/null", O_RDONLY));
	if (fd[0] == -1)
		goto err0;
	EINTRLOOP(fd[1], open("/dev/null", O_WRONLY));
	if (fd[1] == -1)
		goto err1;
	if (fd[0] >= FD_SETSIZE || fd[1] >= FD_SETSIZE) {
		errno = EMFILE;
		goto err2;
	}
	desc = malloc(sizeof(struct virtual_pipe));
	if (!desc) goto err2;
	desc->used = 0;
	desc->n_rd = 1;
	desc->n_wr = 1;
	pipe_lock();
	if (pipe_desc[fd[0]] || pipe_flags[fd[0]] || pipe_desc[fd[1]] || pipe_flags[fd[0]])
		internal_error("c_pipe: pipe handles %d, %d already used: %p, %d, %p, %d", fd[0], fd[1], pipe_desc[fd[0]], pipe_flags[fd[0]], pipe_desc[fd[1]], pipe_flags[fd[0]]);
	pipe_desc[fd[0]] = desc;
	pipe_flags[fd[0]] = 0;
	pipe_desc[fd[1]] = desc;
	pipe_flags[fd[1]] = VIRTUAL_PIPE_FLAG_WRITE;
#ifdef DOS
	pipe_flags[fd[0]] |= VIRTUAL_PIPE_FLAG_NONBLOCK;
	pipe_flags[fd[1]] |= VIRTUAL_PIPE_FLAG_NONBLOCK;
#endif
	pipe_unlock();
	return 0;
err2:
	EINTRLOOP(rs, close(fd[1]));
err1:
	EINTRLOOP(rs, close(fd[0]));
err0:
	return -1;
}

static int vpipe_may_read(int fd)
{
	if (pipe_flags[fd] & VIRTUAL_PIPE_FLAG_WRITE) internal_error("vpipe_may_read: selecting write descriptor %d for read", fd);
	return pipe_desc[fd]->used || !pipe_desc[fd]->n_wr;
}

static int vpipe_may_write(int fd)
{
	if (!(pipe_flags[fd] & VIRTUAL_PIPE_FLAG_WRITE)) internal_error("vpipe_may_write: selecting read descriptor %d for write", fd);
	return pipe_desc[fd]->used != VIRTUAL_PIPE_SIZE || !pipe_desc[fd]->n_rd;
}

static void clear_inactive(fd_set *fs, int i)
{
	if (!fs) return;
	while (--i >= 0) FD_CLR(i, fs);
}

