/*
** The cvsgui protocol used by WinCvs
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
** 
** This library is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Lesser General Public License for more details.
** 
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*!
	\file cvsgui_wire.cpp
	\brief CvsGui wire code implementation
	\author Alexandre Parenteau <aubonbeurre@hotmail.com> --- November 1999
	\note To be used by GUI and CVS client
	\note Derived from gimpwire.c in GIMP
*/

#ifdef HAVE_CONFIG_H
extern "C" {
#include "config.h"
}
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h> // hpux requirement for c++ apps

#include <sys/types.h>
#include "cvsgui_wire.h"

#ifndef WIN32
#	include <unistd.h>
#	include <sys/param.h>
#	include <netinet/in.h>
#	include <arpa/inet.h>
#else
#	pragma warning (disable : 4786)
#endif

#include <map>

#ifdef WIN32
/*!
	Read the data from pipe
	\param fd Pipe
	\param buf Buffer for data read
	\param count Number of bytes to read
	\return The number of bytes read, -1 on error
*/
static int read(pipe_t fd, void* buf, int count)
{
	DWORD read;
	if( !ReadFile(fd, buf, count, &read, NULL) )
	{
		DWORD err = GetLastError();
		errno = EINVAL;
	
		return -1;
	}

	return read;
}

/*!
	Write the data to the pipe
	\param fd Pipe
	\param buf Buffer for data to write
	\param count Number of bytes to write
	\return The number of bytes written, -1 on error
*/
static int write(pipe_t fd, const void* buf, int count)
{
	DWORD dwrite;
	if( !WriteFile(fd, buf, count, &dwrite, NULL) )
	{
		errno = EINVAL;
		return -1;
	}

	return dwrite;
}
#endif

typedef struct _WireHandler  WireHandler;

/// Wire handler structure
struct _WireHandler
{
	guint32 type;					/*!< Type */
	WireReadFunc read_func;			/*!< Read function */
	WireWriteFunc write_func;		/*!< Write function */
	WireDestroyFunc destroy_func;	/*!< Destroy function */
};

static WireIOFunc wire_read_func = NULL;	  /*!< Read function */
static WireIOFunc wire_write_func = NULL;	  /*!< Write function */
static WireFlushFunc wire_flush_func = NULL;  /*!< Flush function */
static int wire_error_val = FALSE;			  /*!< Error value */

/// Handlers container wrapper
struct CAllHandlers
{
public:
	// Construction
	CAllHandlers() 
	{
	}
	
	~CAllHandlers()
	{
		std::map<guint32, WireHandler*>::iterator i;
		for(i = m_wireHandlers.begin(); i != m_wireHandlers.end(); ++i)
		{
			WireHandler* handler = (*i).second;
			free(handler);
		}
	}

private:
	// Data members
	std::map<guint32, WireHandler*> m_wireHandlers; /*!< Handlers container */

public:
	// Interface
	std::map<guint32, WireHandler*>& GetWireHandlers()
	{
		return m_wireHandlers;
	}
} sHandlers;

void wire_register(guint32 type, WireReadFunc read_func, WireWriteFunc write_func, WireDestroyFunc destroy_func)
{
	WireHandler* handler;

	std::map<guint32, WireHandler*>::iterator i = sHandlers.GetWireHandlers().find(type);
	if( i == sHandlers.GetWireHandlers().end() )
		handler = (WireHandler*)malloc(sizeof(WireHandler));
	else
		handler = (*i).second;

	handler->type = type;
	handler->read_func = read_func;
	handler->write_func = write_func;
	handler->destroy_func = destroy_func;

	sHandlers.GetWireHandlers().insert(std::map<guint32, WireHandler*>::value_type(type, handler));
}

/*!
	Set the read function
	\param read_func Read function
*/
void wire_set_reader(WireIOFunc read_func)
{
	wire_read_func = read_func;
}

/*!
	Set the write function
	\param write_func Write function
*/
void wire_set_writer(WireIOFunc write_func)
{
	wire_write_func = write_func;
}

/*!
	Set the flush function
	\param flush_func Flush function
*/
void wire_set_flusher(WireFlushFunc flush_func)
{
	wire_flush_func = flush_func;
}

/*!
	Read from the pipe
	\param fd Pipe
	\param buf Buffer for data read
	\param count Count of data read
	\return TRUE on success, FALSE otherwise
*/
int wire_read(pipe_t fd, guint8* buf, gulong count)
{
	if( wire_read_func )
	{
		if( !(*wire_read_func)(fd, buf, count) )
		{
			//g_print("wire_read: error\n");
			wire_error_val = TRUE;
			return FALSE;
		}
	}
	else
	{
		int bytes;
		
		while( count > 0 )
		{
			do
			{
				bytes = read(fd, (char*)buf, count);
			}while( (bytes == -1) && ((errno == EAGAIN) || (errno == EINTR))) ;
			
			if( bytes == -1 )
			{
				//g_print("wire_read: error\n");
				wire_error_val = TRUE;
				return FALSE;
			}
			
			if( bytes == 0 )
			{
				//g_print("wire_read: unexpected EOF (plug-in crashed?)\n");
				wire_error_val = TRUE;
				return FALSE;
			}
			
			count -= bytes;
			buf += bytes;
		}
	}
	
	return TRUE;
}

/*!
	Write to the pipe
	\param fd Pipe
	\param buf Buffer of data to be written
	\param count Count of data to be written
	\return TRUE on success, FALSE otherwise
*/
int wire_write(pipe_t fd, guint8* buf, gulong count)
{
	if( wire_write_func )
	{
		if( !(*wire_write_func)(fd, buf, count) )
		{
			//g_print("wire_write: error\n");
			wire_error_val = TRUE;
			return FALSE;
		}
	}
	else
	{
		int bytes;

		while( count > 0 )
		{
			do 
			{
				bytes = write(fd, (char*)buf, count);
			}while( (bytes == -1) && ((errno == EAGAIN) || (errno == EINTR)) );

			if( bytes == -1 )
			{
				//g_print("wire_write: error\n");
				wire_error_val = TRUE;
				return FALSE;
			}

			count -= bytes;
			buf += bytes;
		}
	}

	return TRUE;
}

/*!
	Flush the pipe
	\param fd Pipe
	\return TRUE on success, FALSE otherwise
	\note 
*/
int wire_flush(pipe_t fd)
{
	if( wire_flush_func )
		return (*wire_flush_func)(fd);

	return FALSE;
}

/*!
	Get the wire error
	\return The wire error value
*/
int wire_error()
{
	return wire_error_val;
}

/*!
	Clear the wire error
	\note Set the wire error value to FALSE
*/
void wire_clear_error()
{
	wire_error_val = FALSE;
}

/*!
	Read the message from the pipe
	\param fd Pipe
	\param msg Message read from the pipe
	\return TRUE on success, FALSE otherwise
*/
int wire_read_msg(pipe_t fd, WireMessage* msg)
{
	WireHandler* handler;

	if( wire_error_val )
		return !wire_error_val;

	if( !wire_read_int32(fd, &msg->type, 1) )
		return FALSE;

	std::map<guint32, WireHandler*>::iterator i = sHandlers.GetWireHandlers().find(msg->type);
	if( i == sHandlers.GetWireHandlers().end() )
		return FALSE;

	handler = (*i).second;
	(*handler->read_func)(fd, msg);

	return !wire_error_val;
}

/*!
	Write the message to the pipe
	\param fd Pipe
	\param msg Message to write to the pipe
	\return TRUE on success, FALSE otherwise
*/
int wire_write_msg(pipe_t fd, WireMessage* msg)
{
	WireHandler* handler;

	if( wire_error_val )
		return !wire_error_val;

	std::map<guint32, WireHandler*>::iterator i = sHandlers.GetWireHandlers().find(msg->type);
	if( i == sHandlers.GetWireHandlers().end() )
		return FALSE;

	handler = (*i).second;

	if( !wire_write_int32(fd, &msg->type, 1) )
		return FALSE;

	(*handler->write_func)(fd, msg);

	return !wire_error_val;
}

/*!
	Destroy wire message
	\param msg Message to be destroyed
*/
void wire_destroy(WireMessage* msg)
{
	WireHandler* handler;

	std::map<guint32, WireHandler*>::iterator i = sHandlers.GetWireHandlers().find(msg->type);
	if( i == sHandlers.GetWireHandlers().end() )
		return;

	handler = (*i).second;
	(*handler->destroy_func)(msg);
}

/*!
	Read 32 bit integers from the pipe
	\param fd Pipe
	\param data Data to be read
	\param count Count of integers to be read
	\return TRUE on success, FALSE otherwise
*/
int wire_read_int32(pipe_t fd, guint32* data, gint count)
{
	if( count > 0 )
    {
		if( !wire_read_int8(fd, (guint8*)data, count * 4) )
			return FALSE;

		while( count-- )
		{
			*data = ntohl(*data);
			data++;
		}
    }

	return TRUE;
}

/*!
	Read 16 bit integers from the pipe
	\param fd Pipe
	\param data Data to be read
	\param count Count of integers to be read
	\return TRUE on success, FALSE otherwise
*/
int wire_read_int16(pipe_t fd, guint16* data, gint count)
{
	if( count > 0 )
    {
		if( !wire_read_int8(fd, (guint8*)data, count * 2) )
			return FALSE;

		while( count-- )
		{
			*data = ntohs(*data);
			data++;
		}
    }

	return TRUE;
}

/*!
	Read 8 bit integers from the pipe
	\param fd Pipe
	\param data Data to be read
	\param count Count of integers to be read
	\return TRUE on success, FALSE otherwise
*/
int wire_read_int8(pipe_t fd, guint8* data, gint count)
{
	return wire_read(fd, data, count);
}

/*!
	Read double values from the pipe
	\param fd Pipe
	\param data Data to be read
	\param count Count of double values to be read
	\return TRUE on success, FALSE otherwise
*/
int wire_read_double(pipe_t fd, gdouble* data, gint count)
{
	char* str;
	int i;

	for(i = 0; i < count; i++)
    {
		if( !wire_read_string(fd, &str, 1) )
			return FALSE;

		sscanf(str, "%le", &data[i]);
		free(str);
    }

	return TRUE;
}

/*!
	Read strings from the pipe
	\param fd Pipe
	\param data Data to be read
	\param count Count of strings to be read
	\return TRUE on success, FALSE otherwise
*/
int wire_read_string(pipe_t fd, gchar** data, gint count)
{
	guint32 tmp;
	int i;

	for(i = 0; i < count; i++)
    {
		if( !wire_read_int32(fd, &tmp, 1) )
			return FALSE;

		if( tmp > 0 )
		{
			data[i] = (gchar*)malloc(tmp * sizeof(gchar));
			if( !wire_read_int8(fd, (guint8*)data[i], tmp) )
			{
				free(data[i]);
				return FALSE;
			}
		}
		else
		{
			data[i] = NULL;
		}
    }

	return TRUE;
}

/*!
	Write 32 bit integers to the pipe
	\param fd Pipe
	\param data Data to be written
	\param count Count of integers to be written
	\return TRUE on success, FALSE otherwise
*/
int wire_write_int32(pipe_t fd, guint32* data, gint count)
{
	guint32 tmp;
	int i;

	if( count > 0 )
    {
		for(i = 0; i < count; i++)
		{
			tmp = htonl(data[i]);
			if( !wire_write_int8(fd, (guint8*)&tmp, 4) )
				return FALSE;
		}
    }

	return TRUE;
}

/*!
	Write 16 bit integers to the pipe
	\param fd Pipe
	\param data Data to be written
	\param count Count of integers to be written
	\return TRUE on success, FALSE otherwise
*/
int wire_write_int16(pipe_t fd, guint16* data, gint count)
{
	guint16 tmp;
	int i;

	if( count > 0 )
    {
		for(i = 0; i < count; i++)
		{
			tmp = htons(data[i]);
			if( !wire_write_int8(fd, (guint8*)&tmp, 2) )
				return FALSE;
		}
    }

	return TRUE;
}

/*!
	Write 8 bit integers to the pipe
	\param fd Pipe
	\param data Data to be written
	\param count Count of integers to be written
	\return TRUE on success, FALSE otherwise
*/
int wire_write_int8(pipe_t fd, guint8* data, gint count)
{
	return wire_write(fd, data, count);
}

/*!
	Write double values to the pipe
	\param fd Pipe
	\param data Data to be written
	\param count Count of double values to be written
	\return TRUE on success, FALSE otherwise
*/
int wire_write_double(pipe_t fd, gdouble* data, gint count)
{
	gchar* t, buf[128];
	int i;

	t = buf;
	for(i = 0; i < count; i++)
    {
		sprintf(buf, "%0.50e", data[i]);
		if( !wire_write_string(fd, &t, 1, -1) )
			return FALSE;
    }

	return TRUE;
}

/*!
	Write strings from the pipe
	\param fd Pipe
	\param data Data to be written
	\param count Count of strings to be written
	\param len Strings length, if -1 then it's NULL-terminated string, otherwise it's a binary data
	\return TRUE on success, FALSE otherwise
*/
int wire_write_string(pipe_t fd, gchar** data, gint count, int len)
{
	guint32 tmp;
	int i;

	for(i = 0; i < count; i++)
    {
		if( data[i] )
			tmp = (guint32)(len == -1 ? strlen(data[i]) + 1 : len + 1);
		else
			tmp = 0;

		if( !wire_write_int32(fd, &tmp, 1) )
			return FALSE;

		if( tmp > 0 )
			if( !wire_write_int8(fd, (guint8*)data[i], tmp) )
				return FALSE;
    }

	return TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1