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

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

#define BUFF_SIZE 1024

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

struct SpreadMessage {
	int service_type;
	char sender[MAX_GROUP_NAME];
	int num_groups;
	char groups[100][MAX_GROUP_NAME];
	int16 mess_type;
	int length;
	int endian;
	char *message;
};

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

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

static VALUE 
spread_connect(argc, argv, class)
	int argc;
	VALUE* argv;
	VALUE class;
{
	VALUE spread_name_arg, private_name_arg, retobj;
	struct SpreadConnection *spret;
	int n;
	retobj = Data_Make_Struct(class, struct SpreadConnection, 0, free, spret);

	rb_scan_args(argc, argv, "02", &spread_name_arg, &private_name_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, 0, &spret->mbox, 
		spret->private_group)) < 0)
	{
		rb_raise(rb_eSpread, 
			"failed with 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 = BUFF_SIZE;
	rb_obj_call_init(retobj, 0, NULL);
	return retobj;

}

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

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

	Data_Get_Struct(obj, struct SpreadConnection, sp);
	switch(TYPE(group)) {
		case T_STRING:
			if((n = SP_join(sp->mbox, RSTRING(group)->ptr)) < 0)
			{
				rb_raise(rb_eSpread, "returned %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, "returned %d joining group %s",
						n, RSTRING(tmp)->ptr);
				}
			}
			break;
		default:
			break;
	}
	return obj;
}

static VALUE
spread_leave(obj, argc, argv)
    int argc;
    VALUE* argv;
    VALUE obj;
{
		char *tmp;
		int i;
    VALUE group;
    struct SpreadConnection *sp;
    Data_Get_Struct(obj, struct 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(argc, argv, obj)
	int argc;
	VALUE* argv;
	VALUE obj;
{
	VALUE message, mtype, group, st;
	struct SpreadConnection *sp;
	int n;

	Data_Get_Struct(obj, struct SpreadConnection, sp);
	rb_scan_args(argc, argv, "04", &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, "Error (%d) during multicast", n);
					}
				break;
				case T_ARRAY: {
				char groupnames[100][MAX_GROUP_NAME];
				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 char (*)[MAX_GROUP_NAME])groupnames, NUM2INT(mtype),
					RSTRING(message)->len, RSTRING(message)->ptr)) < 0) 
				{
                  	rb_raise(rb_eSpread, "Error (%d) during multicast", n);
               	}
			}
            break;
			}
			break;
		default:
			rb_raise(rb_eSpread, "Invalid object type for multicast");
			break;	
	}
	return Qnil;
}

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

static VALUE
spread_receive(argc, argv, obj)
	int argc;
	VALUE *argv;
	VALUE obj;
{
	struct SpreadConnection *sp;
	struct SpreadMessage *sp_mess;
	VALUE message;
	char buffer[BUFF_SIZE];
	int n;
	bzero(buffer, BUFF_SIZE);
	message = Data_Make_Struct(rb_cSpreadMessage, struct SpreadMessage, 0, 
		free_spmess, sp_mess);
	Data_Get_Struct(obj, struct SpreadConnection, sp);
	if((n = SP_receive(sp->mbox, &sp_mess->service_type, sp_mess->sender, 100,
		&sp_mess->num_groups, sp_mess->groups, &sp_mess->mess_type, 
		&sp_mess->endian, BUFF_SIZE, buffer)) < 0)
	{
		rb_raise(rb_eSpread, "error %d during SP_recieve", n);
	}
	sp_mess->message = (char *)malloc(n);
	memcpy(sp_mess->message, buffer, n);
	sp_mess->length = n;
	return message;
}	

/* Message Functions */

static VALUE
sm_is_unreliable_mess(obj)
    VALUE obj;
{
	int n;
    struct SpreadMessage *sp_mess;
	
    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
	n = Is_unreliable_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_reliable_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_reliable_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_fifo_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_fifo_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_causal_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_causal_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_agreed_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_agreed_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_safe_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_safe_mess(sp_mess->mess_type);
    return INT2NUM(n);
}

static VALUE
sm_is_regular_mess(obj)
    VALUE obj;
{
    int n;
    struct SpreadMessage *sp_mess;

    Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
    n = Is_regular_mess(sp_mess->mess_type);
    return INT2NUM(n);
}



static VALUE
sm_message(obj)
	VALUE obj;
{
	struct SpreadMessage *sp_mess;
	Data_Get_Struct(obj, struct SpreadMessage, sp_mess);
	return rb_str_new(sp_mess->message, sp_mess->length);
}

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

void
Init_spread()
{
	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 */
    rb_define_method(rb_cSpread, "connect", spread_connect, -1);
	/* takes nothing */
    rb_define_method(rb_cSpread, "disconnect", spread_disconnect, 0);
	/* takes group to join */
    rb_define_method(rb_cSpread, "join", spread_join, 1);
	/* takes group 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, -1);

	/* 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, "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);
	
	/* 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));

}
