#include <../arch/arch.h>
#include <sys/poll.h>
#include <signal.h>

#ifdef	ARCH_IPC_SUPPORTED

#define	READ_WAIT_TIMESLICE		100
#define	WRITE_WAIT_TIMESLICE	1000
#define	WRITE_OPEN_TRIALS		10

/*
 * Server-side functions
 */

V_LOCAL char *kern_prefix = "/tmp/";

vboolean V_CALL
archInboundPipe(char *name,arch_pipe_p pipe_ret)
{
	ARCH_CALLER_NAME(archInboundPipe)
	int result;
	mode_t old_mask;
	struct stat st;
	char pipe_name[_MAX_PATH], full_name[_MAX_PATH];
	pipe_ret->pipe_name[0] = 0;
	pipe_ret->full_name[0] = 0;
	pipe_ret->fd = -1;
	if(strlen(name) >= ((_MAX_PATH >> 1) - strlen(kern_prefix)))
	{
		ARCH_ERROR("name too long");
		return false;
	}
	str2HexCodes(pipe_name,name);
	strCopy(full_name,kern_prefix,_MAX_PATH);
	strCat2(full_name,pipe_name,_MAX_PATH);
	/*
	 * Check if given pipe already exists
	 */
	if(stat(full_name,&st) != -1)
	{
		/* inode with this name already exists */
		if((st.st_mode & S_IFMT) == S_IFIFO)
		{
			/* check if pipe is dead */
			int fd = open(full_name,O_WRONLY | O_NONBLOCK,0222);
			if(fd != -1)
			{
				/*
				 * At least, read side is opened.
				 * Note: the next close() call breaks the poll() on
				 * another side, so we must be prepared for reconnection.
				 */
				close(fd);
				ARCH_ERROR_1STR("pipe already in use: ",full_name);
				return false;
			}
			if(errno != ENODEV && errno != ENXIO)
			{
				ARCH_ERROR_1STR("pipe already exists, "
					"and its state can't be determined: ",full_name);
				return false;
			}
			ARCH_WARNING_1STR("pipe already exists, but seems to be dead;"
				" removing: ",full_name);
			if(unlink(full_name))
			{
				SYSCALL_FAILED_1STR("unlink",full_name);
				return false;
			}
		}
		else
		{
			ARCH_ERROR_1STR("inode already exists and isn't a pipe: ",full_name);
			return false;
		}
	}
	/*
	 * Create FIFO
	 */
	old_mask = umask(0);
	result = mknod(full_name,S_IFIFO | 0666,0);
	umask(old_mask);
	if(result)
	{
		SYSCALL_FAILED_1STR("mknod",full_name);
		return false;
	}
	/*
	 * Open it
	 */
	pipe_ret->fd = open(full_name,O_RDONLY | O_NONBLOCK,0444);
	if(pipe_ret->fd == -1)
	{
		SYSCALL_FAILED_1STR("open",full_name);
		return false;
	}
	strCopy(pipe_ret->pipe_name,pipe_name,_MAX_PATH);
	strCopy(pipe_ret->full_name,full_name,_MAX_PATH);
	pipe_ret->inbound = true;
	return true;
}

vboolean V_CALL
archInboundPipeRecv(arch_pipe_p pipe,void_p msg)
{
	ARCH_CALLER_NAME(archInboundPipeRecv)
	ssize_t b_read = 0, remainder = IPC_MSG_LEN;
	for(;;)
	{
		if(!archInboundPipeWait(pipe,0))
			if(!archInboundPipeWait(pipe,READ_WAIT_TIMESLICE))
				return false;
		b_read = read(pipe->fd,msg,remainder);
		switch(b_read)
		{
			case -1:
				SYSCALL_FAILED("read");
				continue;
			case 0:
				continue;
			default:
				break;
		}
		remainder -= b_read;
		if(!remainder)
			return true;
	}
	return false;
}

V_LOCAL vboolean
inbound_pipe_reconnect(arch_pipe_p pipe)
{
	ARCH_CALLER_NAME(inbound_pipe_reconnect)
	int result;
	struct stat st;
	ARCH_WARNING_1STR("connection lost, reconnecting: ",pipe->full_name);
	close(pipe->fd);
	/* check if inode is still exist in filesystem */
	result = stat(pipe->full_name,&st);
	if(!result)
	{
		if((st.st_mode & S_IFMT) != S_IFIFO)
		{
			ARCH_WARNING_1STR("not a fifo, removing: ",pipe->full_name);
			unlink(pipe->full_name);
			result = -1;
		}
	}
	if(result)
	{
		/* re-create FIFO */
		int old_mask = umask(0);
		result = mknod(pipe->full_name,S_IFIFO | 0666,0);
		umask(old_mask);
		if(result)
		{
			SYSCALL_FAILED_1STR("mknod",pipe->full_name);
			return false;
		}
	}
	/* re-open pipe */
	pipe->fd = open(pipe->full_name,O_RDONLY | O_NONBLOCK,0444);
	if(pipe->fd == -1)
	{
		SYSCALL_FAILED_1STR("open",pipe->full_name);
		return false;
	}
	return true;
}

vboolean V_CALL
archInboundPipeWait(arch_pipe_p pipe,vsize_t timeout_ms)
{
	ARCH_CALLER_NAME(archInboundPipeWait)
	vboolean connection_lost = false;
	u32_t tick, elapsed = 0;
	int result;
	struct pollfd p;
	p.fd = pipe->fd;
	p.events = POLLIN;
	p.revents = 0;
	for(;;)
	{
		tick = archTickCount();
		if(connection_lost)
			if(!inbound_pipe_reconnect(pipe))
			{
				ARCH_ERROR("unable to reconnect pipe");
				return false;
			}
		result = poll(&p,1,timeout_ms);
		if(result != -1 && (p.revents & POLLIN))
			return true;
		if(result == -1 || (p.revents & POLLERR) ||
			(p.revents & POLLNVAL))
		{
			SYSCALL_FAILED("poll");
			connection_lost = true;
		}
		if(p.revents & POLLHUP)
		{
			connection_lost = true;
		}
		elapsed = archTickCount() - tick;
		if(elapsed >= timeout_ms)
			break;
		timeout_ms -= elapsed;
	}
	return false;
}

vboolean V_CALL
archInboundPipeHasData(arch_pipe_p pipe)
{
	return archInboundPipeWait(pipe,0);
}

/*
 * Client-side functions
 */
vboolean V_CALL
archOutboundPipe(char *name,arch_pipe_p pipe_ret)
{
	ARCH_CALLER_NAME(archOutboundPipe)
	int count;
	sigset_t ss;
	struct stat st;
	char pipe_name[_MAX_PATH], full_name[_MAX_PATH];
	pipe_ret->pipe_name[0] = 0;
	pipe_ret->full_name[0] = 0;
	pipe_ret->fd = -1;
	if(strlen(name) >= ((_MAX_PATH >> 1) - strlen(kern_prefix)))
	{
		ARCH_ERROR("name too long");
		return false;
	}
	str2HexCodes(pipe_name,name);
	strCopy(full_name,kern_prefix,_MAX_PATH);
	strCat2(full_name,pipe_name,_MAX_PATH);
	/*
	 * Check if given pipe exists
	 */
	if(stat(full_name,&st) == -1)
	{
		SYSCALL_FAILED_1STR("stat",full_name);
		return false;
	}
	if((st.st_mode & S_IFMT) != S_IFIFO)
	{
		ARCH_ERROR_1STR("inode already exists and isn't a pipe: ",full_name);
		return false;
	}
	/*
	 * Open FIFO and check if it has a reader
	 */
	for(count=0;count < WRITE_OPEN_TRIALS;count++)
	{
		pipe_ret->fd = open(full_name,O_WRONLY | O_NONBLOCK,0222);
		if(pipe_ret->fd != -1)
			break;
		if(errno != ENODEV && errno != ENXIO)
		{
			SYSCALL_FAILED_1STR("open",full_name);
			return false;
		}
		ARCH_WARNING_1STR("pipe exists, but have no reader: ",full_name);
		archDelayExecution(WRITE_WAIT_TIMESLICE);
	}
	if(pipe_ret->fd == -1)
	{
		ARCH_ERROR_1STR("pipe exists, but have no reader: ",full_name);
		return false;
	}
	strCopy(pipe_ret->pipe_name,pipe_name,_MAX_PATH);
	strCopy(pipe_ret->full_name,full_name,_MAX_PATH);
	pipe_ret->inbound = false;
	/*
	 * Block the SIGPIPE signal; we will later check write() for EPIPE
	 */
	sigemptyset(&ss);
	sigaddset(&ss,SIGPIPE);
	sigprocmask(SIG_BLOCK,&ss,NULL);
	return true;
}

V_LOCAL vboolean
outbound_pipe_reconnect(arch_pipe_p pipe)
{
	ARCH_CALLER_NAME(outbound_pipe_reconnect)
	int count;
	struct stat st;
	if(pipe->fd != -1)
	{
		close(pipe->fd);
		pipe->fd = -1;
	}
	for(count=0;count < WRITE_OPEN_TRIALS;count++)
	{
		if(stat(pipe->full_name,&st) == -1)
		{
			ARCH_WARNING_1STR("still no inode: ",pipe->full_name);
			archDelayExecution(WRITE_WAIT_TIMESLICE);
			continue;
		}
		if((st.st_mode & S_IFMT) != S_IFIFO)
		{
			ARCH_WARNING_1STR("wrong type of inode: ",pipe->full_name);
			archDelayExecution(WRITE_WAIT_TIMESLICE);
			continue;
		}
		/* re-open pipe */
		pipe->fd = open(pipe->full_name,O_WRONLY | O_NONBLOCK,0222);
		if(pipe->fd == -1)
		{
			SYSCALL_FAILED_1STR("open",pipe->full_name);
			archDelayExecution(WRITE_WAIT_TIMESLICE);
			continue;
		}
		return true;
	}
	ARCH_ERROR_1STR("unable to reconnect: ",pipe->full_name);
	return false;
}

vboolean V_CALL
archOutboundPipeSend(arch_pipe_p pipe,void_p msg)
{
	ARCH_CALLER_NAME(archOutboundPipeSend)
	struct pollfd p;
	ssize_t wrote = 0, remainder = IPC_MSG_LEN;
	for(;;)
	{
		if(pipe->fd != -1)
			wrote = write(pipe->fd,msg,remainder);
		else
		{
			ARCH_WARNING("invalid pipe handle; waiting for connection...");
			if(outbound_pipe_reconnect(pipe))
				continue;
			break;
		}
		if(wrote == -1)
		{
			switch(errno)
			{
				case EAGAIN:
					ARCH_WARNING("pipe busy; waiting...");
					p.fd = pipe->fd;
					p.events = POLLOUT;
					if(poll(&p,1,WRITE_WAIT_TIMESLICE) != -1 && 
						(p.revents & POLLOUT))
						continue;
					break;
				case EPIPE:
					ARCH_WARNING("pipe broken; waiting for connection...");
					if(outbound_pipe_reconnect(pipe))
						continue;
					break;
				default:
					SYSCALL_FAILED("write");
					break;
			}
			break;
		}
		remainder -= wrote;
		if(!remainder)
			return true;
	}
	return false;
}

void V_CALL
archPipeClose(arch_pipe_p pipe)
{
	ARCH_CALLER_NAME(archPipeClose)
	if(pipe->fd != -1 && close(pipe->fd))
		SYSCALL_FAILED_1STR("close",pipe->full_name);
	if(pipe->inbound)
	{
		/* remove inode from filesystem */
		if(unlink(pipe->full_name))
			SYSCALL_FAILED_1STR("unlink",pipe->full_name);
	}
	pipe->fd = -1;
	pipe->full_name[0] = pipe->pipe_name[0] = 0;
	return;
}

#endif	/* ARCH_IPC_SUPPORTED */

