// $Id: ChunkedIO.cc,v 1.2 2003/09/01 23:42:22 vern Exp $

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <netinet/in.h>

#include "config.h"
#include "ChunkedIO.h"

ChunkedIOFd::ChunkedIOFd(int arg_fd)
	{
	fd = arg_fd;
	eof = 0;

	if ( fcntl(fd, F_SETFL, O_NONBLOCK) < 0 )
		{
		error(fmt("Can't set fd to non-blocking: %s", strerror(errno)));
		exit(1);
		}

	readpos = writepos = 0;
	lchunk = dchunk = 0;
	}

ChunkedIOFd::~ChunkedIOFd()
	{
	close(fd);
	}

bool ChunkedIOFd::Read(Chunk** chunk, bool mayblock)
	{
	bool all;

	if ( mayblock )
		{
		// Wait for something to read.
		fd_set fd_read, fd_write, fd_except;

		FD_ZERO(&fd_read);
		FD_ZERO(&fd_write);
		FD_ZERO(&fd_except);
		FD_SET(fd, &fd_read);

		select(fd + 1, &fd_read, &fd_write, &fd_except, 0);
		}

	*chunk = 0;

	if ( ! dchunk )
		{
		if ( ! lchunk )
			{
			readpos = 0;
			lchunk = new Chunk;
			lchunk->len = sizeof(uint32);
			lchunk->data = new char[sizeof(uint32)];
			}

		if ( ! ReadChunk(lchunk, &all) )
			return false;

		if ( ! all )
			return true;

		unsigned long l = ntohl(*((uint32*) lchunk->data));
		readpos = 0;
		dchunk = new Chunk;
		dchunk->len = l;
		dchunk->data = new char[l];

		delete [] lchunk->data;
		delete lchunk;

		lchunk = 0;
		}

	if ( ! ReadChunk(dchunk, &all) )
		return false;

	if ( ! all )
		return true;

	readpos = 0;
	*chunk = dchunk;
	dchunk = 0;

	return true;
	}

bool ChunkedIOFd::ReadChunk(Chunk* c, bool* all)
	{
	// Try writing buffered data.
	Flush();

	*all = false;

	while ( true )
		{
		int len = c->len - readpos;

		int read =::read(fd, c->data + readpos, len);
		if ( read < 0 )
			{
			// These are the same on POSIX.
			if ( errno == EWOULDBLOCK || errno == EAGAIN )
				return true;
			return false;
			}

		if ( read == 0 )
			{
			eof = true;
			return false;
			}

		if ( read == len )
			{
			*all = true;
			return true;
			}

		// Short read.
		readpos += read;
		}
	}

bool ChunkedIOFd::Write(Chunk* chunk)
	{
	// For each real chunk we first write another chunk
	// containing its length.
	if ( ! IsPure() )
		{
		uint32 len = htonl(chunk->len);

		Chunk* lchunk = new Chunk;
		lchunk->data = new char[sizeof(len)];
		lchunk->len = sizeof(len);
		memcpy(lchunk->data, &len, sizeof(len));

		towrite.append(lchunk);
		}

	towrite.append(chunk);
	Flush();

	return true;
	}

bool ChunkedIOFd::Flush()
	{
	while ( towrite.length() )
		{
		Chunk* c = towrite[0];
		int len = c->len - writepos;
		int written = write(fd, c->data + writepos, len);

		if ( written < 0 )
			{
			// These are the same on POSIX.
			if ( errno == EWOULDBLOCK || errno == EAGAIN )
				return true;
			}

		if ( written == 0 )
			internal_error("written==0");

		if ( written == len )
			{
			delete [] c->data;
			delete c;
			towrite.remove_nth(0);
			writepos = 0;

			continue;
			}

		// Short write.
		writepos += written;
		}

	return true;
	}

bool ChunkedIOFd::CanRead()
	{
	// Build FDSets for select.
	fd_set fd_read;
	FD_ZERO(&fd_read);
	FD_SET(fd, &fd_read);

	struct timeval notimeout;
	notimeout.tv_sec = 0;
	notimeout.tv_usec = 0;

	return select(fd + 1, &fd_read, 0, 0, &notimeout) != 0;
	}

const char* ChunkedIOFd::Error()
	{
	static char buffer[1024];
	snprintf(buffer, sizeof(buffer), "%s [%d]", strerror(errno), errno);
	buffer[sizeof(buffer) - 1] = '\0';
	return buffer;
	}
