/*****
 NAME
 	casyncio.m - source code for CAsyncIO class
 VERSION
 	$Id$
 CHANGELOG
 	$Log$
 */

#include <coconut/casyncio.h>
#include <coconut/csystem.h>
#include <coconut/cerror.h>
#include <errno.h>

static boolean asyncIOFunc(GIOChannel * channel, GIOCondition cond, gpointer dat);

@implementation CAsyncIO

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

- retain
{
	if(io_channel)
		g_io_channel_ref(io_channel) ;
	return [super retain] ;
}

- (void) dealloc
{
	id <PError>	err ;
	err = [self closeChannel] ;
	g_assert(err == nil) ;
	[output_stream release] ;
	[input_stream release] ;
	[super dealloc] ;
}

- (id <PError>) openChannel: (GIOChannel *) channel input: (id <PStream>) input
    output: (id <PStream>) output
{
	id <PError>	err ;
	io_condition_t	mask ;

	io_channel = channel ;
	output_stream = output ; input_stream = input ;
	g_assert(io_channel != NULL) ;
	g_assert(input != nil || output != nil) ;

	/* one of them will be nil */
	[input_stream retain] ; [output_stream retain] ;

	/* start watching */
	if(io_channel){
		mask = G_IO_ERR | G_IO_HUP ;
		if(input)
			mask |= G_IO_OUT ; /* prepare output for input stream */
		if(output)
			mask |= G_IO_IN ; /* prepare input for output stream */

		/* add count of main loop */
		[CSystem retainMainLoop] ;

		g_io_add_watch(io_channel, mask, &asyncIOFunc, self) ;
		err = nil ;
	} else
		err = [CError not_exist] ;
	return err ;

}

- (id <PError>) closeChannel
{
	/* WARNING: this method is copied from "closeChannel" method for
	     CIOStream class (in "ciostrem.m"). You have to update
	     both methods when you edit this.
	 */

	/* dispose the channel */
	if(io_channel){
		/* flush the buffer */
		[self flush] ;

		g_io_channel_unref(io_channel) ;
		io_channel = NULL ;
		/* the io_channel will be disposed by above function
		  g_free(io_channel) ; io_channel = NULL ;
		 */

		/* decrement count of main loop */
		[CSystem releaseMainLoop: nil] ;
	} 
	[input_stream release] ; input_stream = nil ;
	[output_stream release] ; output_stream = nil ;
	return nil ;
}

- (io_status_t) flush
{
	io_status_t	stat = io_status_normal ;
	if(io_channel){
		stat = g_io_channel_flush(io_channel, NULL) ;
		g_assert(stat == io_status_normal) ;
	} 
	return stat ;
}

- (boolean) asyncIOEvent: (io_condition_t) cond
{
	boolean		result = FALSE ; /* NG */

	if(IS_IO_ERROR_CONDITION(cond)){
		g_error("error or hungup");
	} else if(IS_IO_INPUT_CONDITION(cond)){
		result = [self asyncInputEvent] ;
	} else if(IS_IO_OUTPUT_CONDITION(cond)){
		result = [self asyncOutputEvent] ;
	} else {
		g_error("can not happen") ;
	}
	return result ;
}

- (boolean) asyncInputEvent
{
	id <PError>	err ;
	io_status_t	stat ;
	gchar *		ptr ;
	gsize		length ;
	gsize		terminator ;
	boolean		result = TRUE ; /* OK */

	if(output_stream == nil){
		/* output stream has bee lost, can not continue */
		return FALSE ; 
	}
	do {
		stat = g_io_channel_read_line(io_channel, &ptr, &length, 
		  &terminator, NULL);
		switch(stat){
		  case io_status_error:
		  	g_error("async input error") ;
			result = FALSE ;
		  break ;
		  case io_status_normal:
		  	[output_stream putPtr: (const utf8_char *) ptr
			  length: length] ;
	 	  break ;
		  case io_status_eof:
		  	err = [self closeChannel] ;
			g_assert(err == nil) ;
		  break ;
		  case io_status_again:
		  	/* nothing have to do, continue this loop */
		  break ;
		}
	} while(stat == io_status_again) ;
	return result ;
}

- (boolean) asyncOutputEvent
{
	io_status_t	stat ;
	id <PError>	err ;
	id <PConstStr>	line ;
	gsize		ressize ;
	boolean		result = TRUE ; /* no error */

	if(input_stream == nil){
		/* input stream has bee lost, can not continue */
		return FALSE ; 
	}
	if((line = [input_stream getLine]) != nil){
		do {
			stat = g_io_channel_write_chars(io_channel, [line ptr],
			  [line length], &ressize, NULL) ;
			switch(stat){
			  case io_status_error:
				g_error("async output error") ;
				result = FALSE ;
			  break ;
			  case io_status_normal:
				/* nothing have to do */
			  break ;
			  case io_status_eof:
				err = [self closeChannel] ;
				g_assert(err == nil) ;
			  break ;
			  case io_status_again:
				/* nothing have to do, continue this loop */
			  break ;
			}
		} while(stat == io_status_again) ;
	} else {
		err = [self closeChannel] ;
		g_assert(err == nil) ;
	}
	return result ;
}

@end

static boolean asyncIOFunc(GIOChannel * chan, GIOCondition cond, gpointer dat)
{
	CAsyncIO *	asyncio = (CAsyncIO *) dat ;
	boolean		result ;
	g_assert(dat != NULL) ;
	result = [asyncio asyncIOEvent: cond] ;
	return result ;
}

