/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program 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.

**********************************************************************/


#define STREAM_LIB
#define LIBRARY

#include	<fcntl.h>
#include	<stdlib.h>
#include	<errno.h>

#include	"memory_debug.h"
#include	"stream.h"
#include	"task.h"
#include	"xl.h"
#include	"lock_level.h"
#include	"pri_level.h"

extern SEM stream_lock;
SEM chain_lock;


int s_close_chain();
int s_write_chain();
int s_read_chain();
int s_flush_chain();
int s_vscanf_chain();
int s_error();
STREAM * s_error_stream();

S_TABLE s_chain_table = {
	'c',
	0,
	s_error_stream,
	s_close_chain,
	s_write_chain,
	s_read_chain,
	s_flush_chain,
	s_error,
	s_error_stream,
	s_error
};

void
init_chain_stream()
{
	chain_lock = new_lock(LL_CHAIN);
	insert_s_table(&s_chain_table);
}

void
chain_thread(TKEY d)
{
STREAM * s;
int ret;
XL_SEXP * chain, * raw_data,* ret_s;
XLISP_ENV * env;
XL_INTERPRETER * xli;
	s = (STREAM*)GET_TKEY(d);

	xli = new_xl_interpreter();
	xli->a_type = XLA_SELF;
	setup_i(xli);


	for ( ; ; ) {
		lock_task(chain_lock);
		for ( ; s->chain.buf_len == 0 &&
				s->chain.iid >= 0 ; ) {
			s->chain.process = 0;
			wakeup_task((int)&s->chain.iid);
			sleep_task((int)&s->chain.iid,chain_lock);
			lock_task(chain_lock);
		}
		if ( s->chain.iid < 0 ) {
			s->chain.process = -1;
			wakeup_task((int)&s->chain.iid);
			unlock_task(chain_lock,"chain_thread");
			break;
		}

		gc_push(0,0,"s_write_chain");

		raw_data = get_raw(s->chain.buf,s->chain.buf_len);
		s->chain.buf_len = 0;

		wakeup_task((int)&s->chain.iid);

		s->chain.process = 1;

		unlock_task(chain_lock,"chain_thread");

		env = new_env(0);

		chain = List(
			n_get_symbol("PrintChain"),
			n_get_string(s->chain.type),
			raw_data,
			-1);

		ret_s = remote_query(s->chain.iid,env,0,chain);
		if ( get_type(ret_s) == XLT_ERROR ) {
			lock_task(chain_lock);
			s->chain.process = -1;
			wakeup_task((int)&s->chain.iid);
			unlock_task(chain_lock,"process");
			s_close(s);
			gc_pop(0,0);
			break;
		}
		gc_pop(0,0);
	}
}


STREAM *
s_open_chain(int iid,char type)
{
STREAM * ret;


	if ( type != 'o' && type != 'e' )
		return 0;

	errno = 0;
	ret = d_alloc(sizeof(S_CHAIN),134);
	ret->h.tbl = &s_chain_table;
	ret->h.thread = 0;

	ret->chain.type[0] = type;
	ret->chain.type[1] = 0;
	ret->chain.iid = iid;
	ret->chain.buf_len = 0;
	ret->chain.process = 0;
	ret->chain.write_process = 0;

	lock_task(stream_lock);
	_s_open(ret,O_RDWR);
	(*ret->h.cm->close)(0,ret->h.cm_work);
	ret->h.cm = &int_cm;
	(*ret->h.cm->open)();
	unlock_task(stream_lock,"s_open_file");

	create_task(chain_thread,(int)ret,PRI_IPC_TASK);

	return ret;
}


void
_s_flush_chain(STREAM * s)
{
	for ( ; s->chain.process ||
			s->chain.write_process ||
			s->chain.buf_len ; ) {
		if ( s->chain.process == -1 )
			break;
		sleep_task((int)&s->chain.iid,chain_lock);
		lock_task(chain_lock);
	}
}

int
s_close_chain(STREAM * s)
{
	lock_task(chain_lock);
	_s_flush_chain(s);
	s->chain.iid = -1;
	wakeup_task((int)&s->chain.iid);
	unlock_task(chain_lock,"s_close_chain");
	return 0;
}

int
s_write_chain(STREAM * s,void * data,int len)
{
char * ptr;
int rem,size;
int ret;

	lock_task(chain_lock);
	s->chain.write_process ++;
	unlock_task(chain_lock,"s_write_chain");
	rem = len;
	ptr = data;
	ret = len;
	for ( ; rem ; ) {
		lock_task(chain_lock);
		for ( ; s->chain.buf_len == CHAIN_BUF_SIZE &&
				s->chain.iid >= 0 ; ) {
			sleep_task((int)&s->chain.iid,chain_lock);
			lock_task(chain_lock);
		}
		if ( s->chain.iid < 0 ) {
			unlock_task(chain_lock,"s_write_chain");
			ret = -1;
			break;
		}
		if ( rem > CHAIN_BUF_SIZE - s->chain.buf_len )
			size = CHAIN_BUF_SIZE - s->chain.buf_len;
		else	size = rem;
		memcpy(&s->chain.buf[s->chain.buf_len],
			ptr,
			size);
		ptr += size;
		rem -= size;
		s->chain.buf_len += size;
		wakeup_task((int)&s->chain.iid);
		unlock_task(chain_lock,"s_write_chain");
	}
	lock_task(chain_lock);
	s->chain.write_process --;
	wakeup_task((int)&s->chain.iid);
	unlock_task(chain_lock,"s_write_chain");
	s->h.last_size = len - rem;
	s->h.last_err = errno;
	return ret;
}

int
s_read_chain(STREAM * s,void * data,int len)
{
int ret;

retry:
	errno = 0;
	ret = 0;
	return ret;
}

int
s_flush_chain(STREAM * s)
{
	lock_task(chain_lock);
	_s_flush_chain(s);
	unlock_task(chain_lock,"s_flush_chain");
	return 0;
}

STREAM *
s_get_chain_from_iid(int iid)
{
STREAM * s;
extern STREAM * stream_list;
	lock_task(stream_lock);
	for ( s = stream_list; s ; s = s->h.next ) {
		if ( s->h.tbl != &s_chain_table )
			continue;
		if ( s->chain.iid == iid )
			break;
	}
	unlock_task(stream_lock,"s_get_chain_from_iid");
	return s;
}
