/*****
 NAME
 	casyncnet.m - source code for CAsyncNet class
 VERSION
 	$Id$
 CHANGELOG
 	$Log$
 */

#include <coconut/gnet/casyncnet.h>
#include <coconut/ceventsystem.h>
#include <coconut/cmemalloc.h>

static gchar	EOFSTR[] = {EOF, '\n', '\0'} ;
static guint	EOFSIZE  = sizeof(gchar) * 3 ;

@implementation CAsyncNet

- init
{
	input_stream = output_stream = nil ;
	return [super init] ;
}

- (void) dealloc
{
	[self closeChannel] ;
	[super dealloc] ;
}

- (void) setStream: (id <PStream>) input and: (id <PStream>) output
{
	input_stream = input ; [input_stream retain] ;
	output_stream = output ; [output_stream retain] ;
}

- (id <PError>) closeChannel
{
	[input_stream release] ; input_stream = nil ;
	[output_stream release] ; output_stream = nil ;
	return nil ;
}

- (boolean) asyncIOEvent: (GIOChannel *) channel condition: (GIOCondition) cond
{
	boolean		result = FALSE ; /* NG */

	/* printf(stderr, "casyncnet.m: asyncIOEvent\n") ; */
	/* g_message("asyncIOEvent %d\n", (int) cond) ; */
	if(IS_IO_ERROR_CONDITION(cond)){
		g_error("error or hungup") ;
	} else if(IS_IO_INPUT_CONDITION(cond)){
		result = [self asyncInputEvent: channel] ;
	} else if(IS_IO_OUTPUT_CONDITION(cond)){
		result = [self asyncOutputEvent: channel] ;
	} else {
		g_error("can not happen") ;
	}
	/* g_message(stderr, "asyncIOEvent end\n") ; */
	return result ;
}

- (boolean) asyncInputEvent: (GIOChannel *) channel
{
	GIOError	err ;
	gchar *		line ;
	guint		len ;
	boolean		result = TRUE ; /* OK */
	boolean		iseof ;

	/* g_message("asyncInputEvent \n") ; */
	if(output_stream == nil)
		return FALSE ;
	do {
		err = gnet_io_channel_readline_strdup(channel, &line, &len) ;
		/* g_message("asyncInputEvent receive size %d, \"%s\"\n", len, 
		     line) ; */
		switch(err){
		  case G_IO_ERROR_INVAL:
		  case G_IO_ERROR_UNKNOWN:
			g_error("read error") ;
			result = FALSE ;
		  break ;
		  case G_IO_ERROR_NONE:
			if(len >= 2 && line[len-2] == EOF){
				iseof = TRUE ;
				/* remove EOF and newline */
				len -= 2 ; line[len] = '\0' ;
			} else {
				iseof = FALSE ;
			}

		  	if(len > 0){
				[output_stream putPtr: line length: len] ;
			}
			/* g_message("asyncInputEvent is eof: %d \n", iseof); */
			if(iseof){
				g_io_channel_unref(channel) ;
				/* do not close, this might be opened */
				[output_stream flush] ;
				[output_stream release] ;
				[CEventSystem releaseMainLoop: nil] ;
				output_stream = nil ;
				result = FALSE ;
				/* g_message("finish") ; */
			}
		  break ;
		  case G_IO_ERROR_AGAIN:
		  	/* do again */
		  break ;
		}
		[CMemAlloc free: line] ;
	} while(err == G_IO_ERROR_AGAIN) ;
	return result ;
}

- (boolean) asyncOutputEvent: (GIOChannel *) channel
{
	GIOError	err ;
	id <PConstStr>	line ;
	guint		ressize ;
	boolean		result = TRUE ; /* OK */
	gpointer	ptr ;
	guint		len ;

	/* g_message(stderr, "asyncOutputEvent\n") ; */
	if(input_stream == nil)
		return FALSE ;
	line = [input_stream getLine] ;
	if(line == nil){
		/* write end of file */
		gnet_io_channel_writen(channel, EOFSTR, EOFSIZE, &ressize) ;
		/* closing the output channel */
		g_io_channel_unref(channel) ; 
		/* close the input stream
		   do not close, this might be opened */
		[input_stream release] ;
		input_stream = nil ;
		/* g_message("asyncOutputEvent finish\n") ; */
		[CEventSystem releaseMainLoop: nil] ;
		return FALSE ;
	}
	/* g_message("asyncOutputEvent \"%s\"\n", [line ptr]) ; */
	ptr = (gpointer) [line ptr] ; len = [line length] ;
	do {
		err = gnet_io_channel_writen(channel,ptr,len,&ressize);
		/* g_message("asyncOutputEvent err %d\n", (int) err) ; */
		switch(err){
		  case G_IO_ERROR_NONE:
			if(len != ressize){
				g_error("illegal size %d and %d", len, 
				  ressize);
			}
		  break ;
		  case G_IO_ERROR_AGAIN:
			/* try again */
		  break ;
		  case G_IO_ERROR_INVAL:
		  case G_IO_ERROR_UNKNOWN:
			g_error("write error") ;
			result = FALSE ;
		  break ;
		}
	} while(err == G_IO_ERROR_AGAIN) ;
	[line release] ;
	/* g_message(stderr, "asyncOutputEvent continue\n") ; */
	return result ;
}

- (io_status_t) flush
{
	return io_status_normal ;
}

@end

gboolean gnet_async_io(GIOChannel * iochannel, GIOCondition cond, gpointer d)
{
	CAsyncNet * client = (CAsyncNet *) d ;
	return [client asyncIOEvent: iochannel condition: cond] ;
}

