/********************************************
rb_Spread: 
A ruby interace to the Spread Group Communication Toolkit
Copyright(2001) George Schlossnagle, All Rights Reserved

This sotware is release under the Perl Artistic License and is 
free to use for any purpose.

Modified July 2002 by Ned Konz
for threading, buffer resizing, and membership messages
*********************************************/

#include "ruby.h"
#include "sp.h"
#include <string.h>

/* defaults for first try at SP_receive() */
#define DEFAULT_BUFF_SIZE 1024
#define DEFAULT_NUMBER_OF_GROUPS 10

typedef char GroupName[MAX_GROUP_NAME];

typedef struct SpreadConnection
{
	int			priority;
	int			group_membership;
	int			mbox;
	int			buffer;
	int			recvtimeout;
	GroupName	private_group;
	char		spread_name[MAX_PROC_NAME];
	char		private_name[MAX_PRIVATE_NAME];
} SpreadConnection;

typedef struct SpreadMessage
{
	int			service_type;
	int			num_groups;
	GroupName	*groups;	/* num_groups long */
	int			length;
	char		*message;	/* length long */
	int			endian;
	int16		mess_type;	/* at end for alignment */
	GroupName	sender;		/* at end for alignment */
} SpreadMessage;

static VALUE rb_eSpread;
static VALUE rb_cSpread;
static VALUE rb_cSpreadMessage;

void free_spmess(void *ptr)
{
	SpreadMessage *sp_mess = ptr;
	free(sp_mess->message);
	free(sp_mess->groups);
	free(sp_mess);
	return;
}

void free_spread_connection(void *ptr)
{
	SpreadConnection *sp_conn = ptr;
	if (sp_conn->mbox >= 0)
		SP_disconnect(sp_conn->mbox);
	free(sp_conn);
}

/* optional third arg is true if you want to hear about membership msgs
 * (default=no) */
static VALUE 
spread_connect(int argc, VALUE *argv, VALUE class)
{
	VALUE spread_name_arg, private_name_arg, membership_msgs_arg, retobj;
	SpreadConnection *spret;
	int n;
	retobj = Data_Make_Struct(class, SpreadConnection, 0, free_spread_connection,
			spret);
	spret->mbox = -1;

	rb_scan_args(argc, argv, "21", &spread_name_arg, &private_name_arg,
			&membership_msgs_arg); Check_Type(spread_name_arg, T_STRING);
	Check_Type(private_name_arg, T_STRING);
	if ((n = SP_connect( RSTRING(spread_name_arg)->ptr,
			RSTRING(private_name_arg)->ptr, 0, (membership_msgs_arg == Qtrue),
			&spret->mbox, spret->private_group)) < 0)
	{
		rb_raise(rb_eSpread, 
			"Spread error (%d) connecting to Spread daemon at %s", 
			n, RSTRING(spread_name_arg)->ptr);
	}
	snprintf(spret->spread_name,MAX_PROC_NAME, "%s", 
		RSTRING(spread_name_arg)->ptr);
	snprintf(spret->private_name, MAX_PRIVATE_NAME, "%s", 
		RSTRING(private_name_arg)->ptr);
	spret->buffer = DEFAULT_BUFF_SIZE;
	rb_obj_call_init(retobj, 0, NULL);
	return retobj;
}

static VALUE
spread_disconnect(VALUE obj)
{
	SpreadConnection *sp;
	Data_Get_Struct(obj, SpreadConnection, sp);
	SP_disconnect(sp->mbox);
	sp->mbox = -1;
	return Qnil;
}

static VALUE
spread_join(VALUE obj, VALUE group)
{
	char *tmp;
	int i, n;
	SpreadConnection *sp;

	Data_Get_Struct(obj, SpreadConnection, sp);
	switch(TYPE(group)) {
		case T_STRING:
			if((n = SP_join(sp->mbox, RSTRING(group)->ptr)) < 0)
			{
				rb_raise(rb_eSpread, "Spread error (%d) joining group %s",
					n, RSTRING(tmp)->ptr);
			}
			break;
		case T_ARRAY:
			if(RARRAY(group)->len == 0) {
				/* do something clever */
			}
			for(i=1; i<RARRAY(group)->len; i++) {
				tmp = (char *)RARRAY(group)->ptr[i];
				if((n = SP_join(sp->mbox, RSTRING(tmp)->ptr)) < 0)
				{
					rb_raise(rb_eSpread, "Spread error (%d) joining group %s",
						n, RSTRING(tmp)->ptr);
				}
			}
			break;
		default:
			break;
	}
	return obj;
}

static VALUE
spread_leave(VALUE obj, int argc, VALUE *argv)
{
	char *tmp;
	int i;
	VALUE group;
	SpreadConnection *sp;

	Data_Get_Struct(obj, SpreadConnection, sp);
	rb_scan_args(argc, argv, "1", &group);
	Check_Type(group, T_STRING);
	switch (TYPE(group)) {
		case T_STRING:
		SP_leave(sp->mbox, RSTRING(group)->ptr);
			break;
		case T_ARRAY:
			if(RARRAY(group)->len == 0) {
		/* do something clever */
			}
			for(i=1; i<RARRAY(group)->len; i++) {
			  tmp = (char *)RARRAY(group)->ptr[i];
			  SP_leave(sp->mbox, RSTRING(tmp)->ptr);
			}
			break;
		default:
			break;
	}
	return Qnil;
}

static VALUE
spread_multicast(int argc, VALUE *argv, VALUE obj)
{
	VALUE message, mtype, group, st;
	SpreadConnection *sp;
	int n;

	Data_Get_Struct(obj, SpreadConnection, sp);
	rb_scan_args(argc, argv, "31", &message, &group, &st, &mtype);
	if(mtype == Qnil) {
		mtype = INT2NUM(1);
	}
	switch (TYPE(message)) {
		case T_STRING:	
			switch(TYPE(group)) {
				case T_STRING:	
					if ((n = SP_multicast(sp->mbox, NUM2INT(st),  
						RSTRING(group)->ptr, NUM2INT(mtype), RSTRING(message)->len, 
						RSTRING(message)->ptr)) < 0) {
							rb_raise(rb_eSpread, "Spread error (%d) during multicast", n);
					}
				break;
				case T_ARRAY: {
					GroupName groupnames[100];
					int i;

					if(RARRAY(group)->len == 0) {
						/* do something */
					}
					for(i=0; i<RARRAY(group)->len; i++) {
						VALUE tmp;
						tmp =  RARRAY(group)->ptr[i];
						snprintf(groupnames[i], MAX_GROUP_NAME, "%s", RSTRING(tmp)->ptr);
								
					}
					if ((n = SP_multigroup_multicast(sp->mbox, NUM2INT(st),  
						RARRAY(group)->len, (const GroupName *)groupnames, NUM2INT(mtype),
						RSTRING(message)->len, RSTRING(message)->ptr)) < 0) 
					{
						rb_raise(rb_eSpread, "Spread error (%d) during multicast", n);
					}
				}
				break;
			}
			break;
		default:
			rb_raise(rb_eSpread, "Invalid object type for multicast");
			break;
	}
	return Qnil;
}

static VALUE
spread_poll(VALUE obj)
{
	int n;
	SpreadConnection *sp;
	Data_Get_Struct(obj, SpreadConnection, sp);
	n = SP_poll(sp->mbox);
	return INT2NUM(n);
}

static VALUE
spread_mbox(VALUE obj)
{
	SpreadConnection *sp;
	Data_Get_Struct(obj, SpreadConnection, sp);
	return INT2NUM(sp->mbox);
}

static VALUE
spread_receive(int argc, VALUE *argv, VALUE obj)
{
	SpreadConnection *sp;
	SpreadMessage *sp_mess;
	VALUE message;
	int buffer_size = DEFAULT_BUFF_SIZE;
	char *buffer = calloc(buffer_size, 1);
	int number_of_groups = DEFAULT_NUMBER_OF_GROUPS;
	GroupName *groups = calloc(number_of_groups, MAX_GROUP_NAME);
	int n;

	message = Data_Make_Struct(rb_cSpreadMessage, SpreadMessage, 0, 
		free_spmess, sp_mess);
	Data_Get_Struct(obj, SpreadConnection, sp);

	for (;;)		/* retry until buffers are big enough */
	{
		if ((n = SP_receive(sp->mbox, &sp_mess->service_type, sp_mess->sender,
			number_of_groups, &number_of_groups, groups,
			&sp_mess->mess_type, &sp_mess->endian, buffer_size, buffer)) >= 0)
		{
			sp_mess->num_groups = number_of_groups;
			sp_mess->groups = realloc(groups, MAX_GROUP_NAME * number_of_groups);
			sp_mess->message = realloc(buffer, n);	/* could be NULL for n==0 */
			sp_mess->length = n;
			return message;
		}
		if (n == GROUPS_TOO_SHORT)
		{
			number_of_groups *= -1;
			groups = realloc(groups, MAX_GROUP_NAME * number_of_groups);
			continue;
		}
		else if (n == BUFFER_TOO_SHORT)
		{
			buffer_size = -sp_mess->endian;
			buffer = realloc(buffer, buffer_size);
			continue;
		}
		else
		{
			free(buffer);
			free(groups);
			rb_raise(rb_eSpread, "Spread error (%d) during SP_receive", n);
		}
	}
}

/* Message Functions */


/* return the message as a string */
static VALUE
sm_message(VALUE obj)
{
	SpreadMessage *sp_mess;
	Data_Get_Struct(obj, SpreadMessage, sp_mess);
	return rb_str_new(sp_mess->message, sp_mess->length);
}

/* return the name of the sender */
static VALUE
sm_sender(VALUE obj)
{
	SpreadMessage *sp_mess;
	Data_Get_Struct(obj, SpreadMessage, sp_mess);
	return rb_str_new(sp_mess->sender, MAX_GROUP_NAME);
}

/* return the (application defined) message type */
static VALUE
sm_message_type(VALUE obj)
{
	SpreadMessage *sp_mess;

	Data_Get_Struct(obj, SpreadMessage, sp_mess);
	return INT2NUM(sp_mess->mess_type);
}

/* return the numeric service type */
static VALUE
sm_service_type(VALUE obj)
{
	SpreadMessage *sp_mess;

	Data_Get_Struct(obj, SpreadMessage, sp_mess);
	return INT2NUM(sp_mess->service_type);
}

/* return an Array of String with the group names */
static VALUE
sm_groups(VALUE obj)
{
	SpreadMessage *sp_mess;
	VALUE groups;
	int i;

	Data_Get_Struct(obj, SpreadMessage, sp_mess);
	groups = rb_ary_new2((long)sp_mess->num_groups);
	for (i = 0; i < sp_mess->num_groups; i++)
	{
		GroupName *group = sp_mess->groups + i;
		rb_ary_store(groups, (long)i, rb_str_new2((const char*)group));
	}
	return groups;
}

/* Service type tests */
#define DEFINE_MSGTEST(Is_whatever, sm_is_whatever) \
static VALUE sm_is_whatever(VALUE msg) \
{ SpreadMessage *sp_mess; \
Data_Get_Struct(msg, SpreadMessage, sp_mess); \
return Is_whatever(sp_mess->service_type) ? Qtrue : Qfalse; }

DEFINE_MSGTEST(Is_unreliable_mess, sm_is_unreliable_mess);
DEFINE_MSGTEST(Is_reliable_mess, sm_is_reliable_mess);
DEFINE_MSGTEST(Is_fifo_mess, sm_is_fifo_mess);
DEFINE_MSGTEST(Is_causal_mess, sm_is_causal_mess);
DEFINE_MSGTEST(Is_agreed_mess, sm_is_agreed_mess);
DEFINE_MSGTEST(Is_safe_mess, sm_is_safe_mess);
DEFINE_MSGTEST(Is_regular_mess, sm_is_regular_mess);
DEFINE_MSGTEST(Is_self_discard, sm_is_self_discard);
DEFINE_MSGTEST(Is_reg_memb_mess, sm_is_reg_memb_mess);
DEFINE_MSGTEST(Is_transition_mess, sm_is_transition_mess);
DEFINE_MSGTEST(Is_caused_join_mess, sm_is_caused_by_join_mess);
DEFINE_MSGTEST(Is_caused_leave_mess, sm_is_caused_by_leave_mess);
DEFINE_MSGTEST(Is_caused_disconnect_mess, sm_is_caused_by_disconnect_mess);
DEFINE_MSGTEST(Is_caused_network_mess, sm_is_caused_by_network_mess);
DEFINE_MSGTEST(Is_membership_mess, sm_is_membership_mess);
DEFINE_MSGTEST(Is_reject_mess, sm_is_reject_mess);
DEFINE_MSGTEST(Is_self_leave, sm_is_self_leave);

void
Init_spread(void)
{
	rb_eSpread = rb_define_class("SpreadError", rb_eStandardError);
	rb_cSpread = rb_define_class("Spread", rb_cObject);
	rb_cSpreadMessage = rb_define_class("SpreadMessage", rb_cObject);
	rb_define_singleton_method(rb_cSpread, "new", spread_connect, -1);
	/* takes spread_name and private_name, and optional wants_member_messages flag */
	rb_define_method(rb_cSpread, "connect", spread_connect, -1);
	/* takes nothing */
	rb_define_method(rb_cSpread, "disconnect", spread_disconnect, 0);
	/* takes group(s) to join */
	rb_define_method(rb_cSpread, "join", spread_join, 1);
	/* takes group(s) to leave */
	rb_define_method(rb_cSpread, "leave", spread_leave, 1);
	/* takes message, message_type, group and service-type */	
	rb_define_method(rb_cSpread, "multicast", spread_multicast, -1);
	/* Takes nothing returns number of bytes waiting to be read */
	rb_define_method(rb_cSpread, "poll", spread_poll, 0);
	/* Takes nothing, returns a SpreadMessage */
	rb_define_method(rb_cSpread, "receive", spread_receive, 0);
	/* Takes nothing, returns file descriptor (mbox) */
	rb_define_method(rb_cSpread, "mbox", spread_mbox, 0);

	/* Spread Message Methods */
	rb_define_method(rb_cSpreadMessage, "message", sm_message, 0);
	rb_define_method(rb_cSpreadMessage, "sender", sm_sender, 0);
	rb_define_method(rb_cSpreadMessage, "message_type", sm_message_type, 0);
	rb_define_method(rb_cSpreadMessage, "service_type", sm_service_type, 0);
	rb_define_method(rb_cSpreadMessage, "groups", sm_groups, 0);

	/* Service type tests */
	rb_define_method(rb_cSpreadMessage, "is_unreliable", sm_is_unreliable_mess,
	  0);
	rb_define_method(rb_cSpreadMessage, "is_reliable", sm_is_reliable_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_fifo", sm_is_fifo_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_causal", sm_is_causal_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_agreed", sm_is_agreed_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_safe", sm_is_safe_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_regular", sm_is_regular_mess, 0);

	rb_define_method(rb_cSpreadMessage, "is_self_discard", sm_is_self_discard, 0);
	rb_define_method(rb_cSpreadMessage, "is_regular_membership",
	  sm_is_reg_memb_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_membership", sm_is_membership_mess,
	  0);
	rb_define_method(rb_cSpreadMessage, "is_transition", sm_is_transition_mess,
	  0);
	rb_define_method(rb_cSpreadMessage, "is_caused_by_join",
	  sm_is_caused_by_join_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_caused_by_leave",
	  sm_is_caused_by_leave_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_caused_by_disconnect",
	  sm_is_caused_by_disconnect_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_caused_by_network",
	  sm_is_caused_by_network_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_reject", sm_is_reject_mess, 0);
	rb_define_method(rb_cSpreadMessage, "is_self_leave", sm_is_self_leave, 0);

	/* define constants */
	rb_define_const(rb_cSpread, "LOW_PRIORITY", INT2NUM(LOW_PRIORITY));
	rb_define_const(rb_cSpread, "MEDIUM_PRIORITY", INT2NUM(MEDIUM_PRIORITY));
	rb_define_const(rb_cSpread, "HIGH_PRIORITY", INT2NUM(HIGH_PRIORITY));
	
	rb_define_const(rb_cSpread, "UNRELIABLE_MESS", INT2NUM(UNRELIABLE_MESS));
	rb_define_const(rb_cSpread, "RELIABLE_MESS", INT2NUM(RELIABLE_MESS));
	rb_define_const(rb_cSpread, "FIFO_MESS", INT2NUM(FIFO_MESS));
	rb_define_const(rb_cSpread, "CAUSAL_MESS", INT2NUM(CAUSAL_MESS));
	rb_define_const(rb_cSpread, "AGREED_MESS", INT2NUM(AGREED_MESS));
	rb_define_const(rb_cSpread, "SAFE_MESS", INT2NUM(SAFE_MESS));
	rb_define_const(rb_cSpread, "REGULAR_MESS", INT2NUM(REGULAR_MESS));
	rb_define_const(rb_cSpread, "SELF_DISCARD", INT2NUM(SELF_DISCARD));
	rb_define_const(rb_cSpread, "DROP_RECV", INT2NUM(DROP_RECV));
	rb_define_const(rb_cSpread, "REG_MEMB_MESS", INT2NUM(REG_MEMB_MESS));
	rb_define_const(rb_cSpread, "TRANSITION_MESS", INT2NUM(TRANSITION_MESS));
	rb_define_const(rb_cSpread, "CAUSED_BY_JOIN", INT2NUM(CAUSED_BY_JOIN));
	rb_define_const(rb_cSpread, "CAUSED_BY_LEAVE", INT2NUM(CAUSED_BY_LEAVE));
	rb_define_const(rb_cSpread, "CAUSED_BY_DISCONNECT", 
		INT2NUM(CAUSED_BY_DISCONNECT));
	rb_define_const(rb_cSpread, "CAUSED_BY_NETWORK", 
		INT2NUM(CAUSED_BY_NETWORK));
	rb_define_const(rb_cSpread, "MEMBERSHIP_MESS", INT2NUM(MEMBERSHIP_MESS));
	rb_define_const(rb_cSpread, "ENDIAN_RESERVED", INT2NUM(ENDIAN_RESERVED));
	rb_define_const(rb_cSpread, "RESERVED", INT2NUM(RESERVED));

	/* define spread errors */
	rb_define_const(rb_eSpread, "ACCEPT_SESSION", INT2NUM(ACCEPT_SESSION));
	rb_define_const(rb_eSpread, "ILLEGAL_SPREAD", INT2NUM(ILLEGAL_SPREAD));
	rb_define_const(rb_eSpread, "COULD_NOT_CONNECT", 
		INT2NUM(COULD_NOT_CONNECT));
	rb_define_const(rb_eSpread, "REJECT_QUOTA", INT2NUM(REJECT_QUOTA));
	rb_define_const(rb_eSpread, "REJECT_NO_NAME", INT2NUM(REJECT_NO_NAME));
	rb_define_const(rb_eSpread, "REJECT_ILLEGAL_NAME", 
		INT2NUM(REJECT_ILLEGAL_NAME));
	rb_define_const(rb_eSpread, "REJECT_NOT_UNIQUE", 
		INT2NUM(REJECT_NOT_UNIQUE));
	rb_define_const(rb_eSpread, "REJECT_VERSION", INT2NUM(REJECT_VERSION));
	rb_define_const(rb_eSpread, "CONNECTION_CLOSED", 
		INT2NUM(CONNECTION_CLOSED));
	rb_define_const(rb_eSpread, "ILLEGAL_SESSION", INT2NUM(ILLEGAL_SESSION));
	rb_define_const(rb_eSpread, "ILLEGAL_SERVICE", INT2NUM(ILLEGAL_SERVICE));
	rb_define_const(rb_eSpread, "ILLEGAL_MESSAGE", INT2NUM(ILLEGAL_MESSAGE));
	rb_define_const(rb_eSpread, "ILLEGAL_GROUP", INT2NUM(ILLEGAL_GROUP));
	rb_define_const(rb_eSpread, "BUFFER_TOO_SHORT", 
		INT2NUM(BUFFER_TOO_SHORT));
	rb_define_const(rb_eSpread, "GROUPS_TOO_SHORT", 
		INT2NUM(GROUPS_TOO_SHORT));
}
