/*
** 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
*/

/*
 * gimpwire.c from the GIMP modified for the cvsgui project by :
 * Alexandre Parenteau <aubonbeurre@hotmail.com> --- November 1999
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

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

#include <map>

#ifdef WIN32
static int read(pipe_t fd, void *buf, int count)
{
	DWORD dread;
	if(!ReadFile(fd,buf,count,&dread,NULL))
	{
		DWORD err = GetLastError();
		errno = EINVAL;
		return -1;
	}
	return dread;
}

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;

struct _WireHandler
{
	guint32 type;
	WireReadFunc read_func;
	WireWriteFunc write_func;
	WireDestroyFunc destroy_func;
};

static WireIOFunc wire_read_func = NULL;
static WireIOFunc wire_write_func = NULL;
static WireFlushFunc wire_flush_func = NULL;
static int wire_error_val = FALSE;

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

	std::map<guint32, WireHandler *> wire_ht;
} sHandlers;

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

	std::map<guint32, WireHandler *>::iterator i = sHandlers.wire_ht.find(type);
	if(i == sHandlers.wire_ht.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.wire_ht.insert(std::map<guint32, WireHandler *>::value_type(type, handler));
}

void
wire_set_reader (WireIOFunc read_func)
{
	wire_read_func = read_func;
}

void
wire_set_writer (WireIOFunc write_func)
{
	wire_write_func = write_func;
}

void
wire_set_flusher (WireFlushFunc flush_func)
{
	wire_flush_func = flush_func;
}

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;
}

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;
}

int
wire_flush (pipe_t fd)
{
	if (wire_flush_func)
		return (* wire_flush_func) (fd);
	return FALSE;
}

int
wire_error ()
{
	return wire_error_val;
}

void
wire_clear_error ()
{
	wire_error_val = FALSE;
}

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.wire_ht.find(msg->type);
	if(i == sHandlers.wire_ht.end())
		return FALSE;

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

	return !wire_error_val;
}

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.wire_ht.find(msg->type);
	if(i == sHandlers.wire_ht.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;
}

void
wire_destroy (WireMessage *msg)
{
	WireHandler *handler;

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

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

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;
}

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;
}

int
wire_read_int8 (pipe_t fd,
				guint8 *data,
				gint    count)
{
	return wire_read (fd, data, count);
}

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;
}

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;
}

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;
}

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;
}

int
wire_write_int8 (pipe_t fd,
				 guint8 *data,
				 gint    count)
{
	return wire_write (fd, data, count);
}

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))
			return FALSE;
    }

	return TRUE;
}

int
wire_write_string (pipe_t fd,
				   gchar **data,
				   gint    count)
{
	guint32 tmp;
	int i;

	for (i = 0; i < count; i++)
    {
		if (data[i])
			tmp = strlen (data[i]) + 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;
}
