/* Common buffered read/write interface */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "bufio.h"
#include "select.h"
#include "socket.h"


static void
kill_bufio(sock_t *sock) {
	void (*handler_d)(sock_t *sock, void *data) = sock->buf->handler_d;
	void *handler_d_data = sock->buf->handler_d_data;

	sock->handler_r = NULL;
	sock->handler_w = NULL;

	sock->handler_r_data = NULL;
	sock->handler_w_data = NULL;

	sock->buf = bufio_del(sock->buf);
	sock->flags &= ~SF_BUFFER;

	if (sock->set)
		sockset_notify(sock->set, sock);

	handler_d(sock, handler_d_data);
}

static void
try_handle_read(sock_t *sock) {
	buf_t *buf = sock->buf;
	void (*handler)(sock_t *, int, char *, void *);
	void *data;
	char *buffer;
	int buflen;

	if (! buf->handler_r || ! buf->winlen)
		return;

	if (buf->winlen > buf->inblen)
		return;

	buffer = malloc(buf->winlen);
	memcpy(buffer, buf->inbuf, buf->winlen);

	memmove(buf->inbuf, (char *) buf->inbuf + buf->winlen, buf->inblen -= buf->winlen);
	buf->inbuf = realloc(buf->inbuf, buf->inblen);

	buflen = buf->winlen; buf->winlen = 0;
	handler = buf->handler_r; buf->handler_r = NULL;
	data = buf->handler_r_data; buf->handler_r_data = NULL;

	sock->handler_r = NULL;
	if (sock->set)
		sockset_notify(sock->set, sock);

	handler(sock, buflen, buffer, data);

	free(buffer);

	if (buf->handler_d && ! buf->handler_w)
		kill_bufio(sock);
}


/* sock_t handlers */

static void
bufio_rh(sock_t *sock, void *data) {
	buf_t *buf = sock->buf;

	if (! buf->handler_r || ! buf->winlen) {
		/* no! no! no! missed once more.. */
		sock->handler_r = NULL;
		if (sock->set)
			sockset_notify(sock->set, sock);

		return; /* what shall we do with.. a drunken sailor? */
	}

	if (buf->winlen > buf->inblen) {
		int buflen = buf->winlen - buf->inblen;
		void *buffer = malloc(buflen);
		int len;

		/* TODO: trigger exception handler in error cases? */

		if (! buffer)
			return; /* maybe next time */

		len = read(sock->fd, buffer, buflen);
		if (len <= 0) {
			free(buffer);
			return; /* nothing for us */
		}

		buf->inblen += len;
		buf->inbuf = realloc(buf->inbuf, buf->inblen);
		if (! buf->inbuf) {
			buf->inblen -= len;
			free(buffer);
			return; /* eek! so we have to discard it :| */
		}

		memcpy(buf->inbuf + buf->inblen - len, buffer, len);

		free(buffer);
	}

	try_handle_read(sock);
}

static void
bufio_wh(sock_t *sock, void *data) {
	buf_t *buf = sock->buf;
	int len;

	if (! buf->outblen) {
		sock->handler_w = NULL;
		if (sock->set)
			sockset_notify(sock->set, sock);

		return; /* nothing to write */
	}

	/* TODO: trigger exception handler in error cases? */

	len = write(sock->fd, buf->outbuf, buf->outblen);
	if (len <= 0)
		return;

	buf->outblen -= len;
	memmove(buf->outbuf, buf->outbuf + len, buf->outblen);
	buf->outbuf = realloc(buf->outbuf, buf->outblen);

	/* (! buf->outbuf) is ok if we have nothing else to write */
	if (! buf->outblen) {
		void (*handler)(sock_t *, void *);
		void *data;

		handler = buf->handler_w; buf->handler_w = NULL;
		data = buf->handler_w_data; buf->handler_w_data = NULL;

		sock->handler_w = NULL;
		if (sock->set)
			sockset_notify(sock->set, sock);

		if (handler)
			handler(sock, data);

		if (buf->handler_d && ! buf->handler_r)
			kill_bufio(sock);

	} else {
		/* otherwise realloc() failed, so */
		if (! buf->outbuf) {
			buf->outblen = 0;
			return; /* we HAVE a problem, discard it */
		}
	}
}


/* public interface */

buf_t *
bufio_get() {
	buf_t *buf = malloc(sizeof(buf_t));

	if (! buf)
		return NULL;

	buf->handler_r = NULL;
	buf->handler_w = NULL;
	buf->handler_d = NULL;

	buf->handler_r_data = NULL;
	buf->handler_w_data = NULL;
	buf->handler_d_data = NULL;

	buf->winlen = 0;
	buf->inblen = buf->outblen = 0;
	buf->inbuf = buf->outbuf = NULL;

	return buf;
}

int
bufio_init(sock_t *sock) {
	buf_t *buf = bufio_get();

	if (! buf)
		return 0;

	if (sock->flags & SF_BUFFER || sock->buf)
		return 0;

	/* paranoia checks */
	if (! (sock->flags & SF_NBLK) || sock->state < SS_IDLE)
		return 0;

	sock->buf = buf;

	sock->handler_r = NULL;
	sock->handler_w = NULL;

	sock->handler_r_data = NULL;
	sock->handler_w_data = NULL;

	sock->flags |= SF_BUFFER;

	if (sock->set)
		sockset_notify(sock->set, sock);

	return 1;
}

int
bufio_read(sock_t *sock, int len, void (*handler)(sock_t *, int, char *, void *), void *data) {
	buf_t *buf = sock->buf;

	if (! handler || ! sock->flags & SF_BUFFER || ! buf)
		return 0;

	buf->handler_r = handler;
	buf->handler_r_data = data;

	buf->winlen = len;

	if (! sock->handler_r) {
		sock->handler_r = bufio_rh;
		if (sock->set)
			sockset_notify(sock->set, sock);
	}

	try_handle_read(sock);

	return 1;
}

int
bufio_write(sock_t *sock, int len, char *buffer, void (*handler)(sock_t *, void *), void *data) {
	buf_t *buf = sock->buf;

	if (! sock->flags & SF_BUFFER || ! buf)
		return 0;

	buf->handler_w = handler;
	buf->handler_w_data = data;

	buf->outblen += len;

	buf->outbuf = realloc(buf->outbuf, buf->outblen);
	if (! buf->outbuf) {
		buf->outblen -= len;

		return 0;
	}

	memcpy(buf->outbuf + buf->outblen - len, buffer, len);

	if (! sock->handler_w) {
		sock->handler_w = bufio_wh;
		if (sock->set)
			sockset_notify(sock->set, sock);
	}

	return 1;
}

int
bufio_done(sock_t *sock, void (*handler)(sock_t *, void *), void *data) {
	buf_t *buf = sock->buf;

	if (! handler || ! sock->flags & SF_BUFFER || ! buf)
		return 0;

	buf->handler_d = handler;
	buf->handler_d_data = data;

	if (! buf->inbuf && ! buf->outbuf)
		kill_bufio(sock);

	return 1;
}

buf_t *
bufio_del(buf_t *buf) {
	if (buf->inbuf)
		free(buf->inbuf);

	if (buf->outbuf)
		free(buf->outbuf);

	return NULL;
}
