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

#include	"stream.h"
#include	"task.h"
#include	"s_buf.h"
#include	"pri_level.h"
#include	"memory_debug.h"
extern SEM stream_lock;
int s_write_wait,s_write_pos;
extern STREAM * stream_list;

int
s_write(STREAM * s,void * data,int len)
{
S_TABLE * tbl;
S_FILE_THREAD t;
int ret;
	lock_task(stream_lock);
	if ( s == 0 ) {
		unlock_task(stream_lock,"s_write");
		return -1;
	}
	if ( s->h.tbl == 0 ) {
		unlock_task(stream_lock,"s_write");
		return -1;
	}
	tbl = s->h.tbl;
	if ( tbl->flags & STF_BUF_OUT ) {
		copy_in_sbuf(&s->h.write_buf,data,len,0);
		ret = len;
		wakeup_task((int)s_write);
		unlock_task(stream_lock,"s_write");
		return ret;
	}
	_s_insert_thread(s,&t);
	unlock_task(stream_lock,"s_write");

	ret = (*tbl->write)(s,data,len);

	s_delete_thread(s,&t);

	return ret;
}



int
_s_write(STREAM * s)
{
S_TABLE * tbl;
S_FILE_THREAD t;
int ret;
SBUF_DATA * d;
char * ptr;
int len,_ret;
int er;
	if ( s->h.tbl == 0 ) {
		return -1;
	}
	tbl = s->h.tbl;
	d = get_sbuf_head(&s->h.write_buf,0);
	if ( d == 0 ) {
		return -1;
	}
	_s_insert_thread(s,&t);
	unlock_task(stream_lock,"s_write");

	ptr = (char*)(d+1);
	len = d->len;
	_ret = 0;
	er = 0;
	for ( ; len ; ) {
		ret = (*tbl->write)(s,ptr,len);
		if ( ret < 0 ) {
			er = -1;
			break;
		}
		len -= ret;
		ptr += ret;
		_ret += ret;
	}
	
	d_f_ree(d);

	lock_task(stream_lock);
	
	if ( er == -1 ) {
		free_sbuf(&s->h.write_buf,0);
		if ( _ret == 0 )
			_ret = -1;
	}

	_s_delete_thread(s,&t);


	return ret;
}

void
s_write_task()
{
STREAM * target;
int ret;
int c;
int nos;
	lock_task(stream_lock);
	for ( ; ; ) {
		nos = 0;
		for ( target = stream_list ; target ; target = target->h.next , nos ++ );
		c = 0;
		if ( nos == 0 )
			goto wait;
	retry:
		for ( target = stream_list ; target ; target = target->h.next , c ++ ) {
			if ( c < s_write_pos )
				continue;
			if ( target->h.tbl == 0 )
				continue;
			if ( target->h.wb_flags )
				continue;
			if ( target->h.write_buf.head )
				goto ok;
		}
		if ( c < s_write_pos + nos ) {
			goto retry;
		}
	wait:
		if ( s_write_wait ) {
			unlock_task(stream_lock,"s_write_task");
			break;
		}
		s_write_wait ++;
		sleep_task((int)s_write,stream_lock);

		lock_task(stream_lock);
		s_write_wait --;
		continue;
	ok:
		s_write_pos = (c+1) % nos;
		target->h.wb_flags = WBF_WRITING;
		ret = 0;
		for ( ; ret >= 0 ; ) {
			ret = _s_write(target);
		}
		target->h.wb_flags &= ~WBF_WRITING;
		wakeup_task((int)&target->h.write_buf);
	}
}

void
s_write_tick()
{
	lock_task(stream_lock);
	if ( s_write_wait == 0 )
		create_task(s_write_task,0,PRI_WRITE_BUF);
	unlock_task(stream_lock,"s_write_tick");
}
