/**********************************************************************
 
	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	<stdlib.h>
#include	<fcntl.h>
#include	"memory_debug.h"
#include	"stream.h"
#include	"task.h"
#include	"utils.h"
#include	"s_buf.h"
#include	"pri_level.h"

#define WBF_TIMEOUT	30
#define ZONBIE_LIFE_TIME	(5*60)

typedef struct close_queue {
	struct close_queue *	next;
	STREAM * 		st;
} CLOSE_QUEUE;

extern SEM stream_lock,cq_lock;
/*
int zonbie_ptr;
STREAM * close_zonbie[CLOSE_ZONBIE];
*/
extern STREAM * stream_list;
STREAM * close_zonbie_list;
int zonbie_life_time = ZONBIE_LIFE_TIME;
int zonbie_count;
CLOSE_QUEUE *_cq_head,* _cq_tail;
int close_queue_task_nos;

void zonbie_tick();
void s_close_write_tick(STREAM * s);
void close_queue_task(TKEY d);


void
_delete_stream_list(STREAM * s,char * file,int line)
{
STREAM ** sp;

	for ( sp = &stream_list ; *sp ; sp = &(*sp)->h.next ) {
		if ( *sp == s ) {
			*sp = s->h.next;
			return;
		}
	}
	fprintf(stderr,"FILE %s / %i\n",file,line);
	er_panic("_delete_stream_list");
}

void
zonbie_tick()
{
STREAM ** sp, * ss;
unsigned int now;
int life,_life;
	lock_task(stream_lock);
	now = get_xltime();
	life = 0;
	_life = 0;
	for ( sp = &close_zonbie_list ; *sp ; ) {
		ss = *sp;
		life =  ss->h.zonbie_access - ss->h.close_time;
		if ( zonbie_life_time < 2 * life )
			zonbie_life_time = 2 * life;
		if ( _life < life )
			_life = life;
		if ( now - ss->h.close_time < zonbie_life_time ) {
			sp = &ss->h.next;
			continue;
		}
		*sp = ss->h.next;
		d_f_ree(ss);
		zonbie_count --;
	}
	unlock_task(stream_lock,"zonbie_tick");
/*
printf("ZONBIE LIFE TIME c=%i lt=%i(%i)\n",zonbie_count,zonbie_life_time,_life);
*/
}


void
s_close_write_tick(STREAM * s)
{
	lock_task(stream_lock);
	s->h.wb_flags |= WBF_C_TIMEOUT;
	wakeup_task((int)&s->h.write_buf);
	unlock_task(stream_lock,"s_close_write_tick");
}

int
__d_s_close(STREAM * s,char * file,int line)
{
int ret;
char * buf;
int rbyte;
S_TABLE * tbl;
int i;

	ret = -1;
	if ( s == 0 )
		goto end;
	if ( s->h.tbl == 0 ) {
		s->h.zonbie_access = get_xltime();
/*
printf("CLOSE ZOMBIE ACCESS %i %i / %x\n",s->h.close_time,s->h.zonbie_access,s);
*/
		goto end;
	}

	for ( i = 0 ; i < 3 ; i ++ )
		if ( s == (STREAM*)&__s_std[i] )
			goto end;
	tbl = s->h.tbl;
	if ( (tbl->flags & STF_BUF_OUT) ||
			(s->h.wb_flags & STF_BUF_OUT) ) {
		for ( ; ; ) {
			if ( s->h.write_buf.head == 0 &&
					(s->h.wb_flags & WBF_WRITING) == 0 )
				break;
			wakeup_task((int)s_write);
			new_tick((void(*)())s_close_write_tick,WBF_TIMEOUT,
					(int)s);
			sleep_task((int)&s->h.write_buf,stream_lock);
			lock_task(stream_lock);
			del_tick_with_data(s_close_write_tick,(int)s);
			if ( s->h.wb_flags & WBF_C_TIMEOUT )
				break;
		}
		if ( s->h.tbl == 0 ) {
			s->h.zonbie_access = get_xltime();
			goto end;
		}
	}
	s->h.tbl = 0;
	if ( s->h.mode == O_WRONLY || s->h.mode == O_RDWR ) {
		buf = d_alloc(s->h.cm->max_ret);
		rbyte = (*s->h.cm->close)((unsigned char*)buf,s->h.cm_work); 
		unlock_task(stream_lock,"s_close");
		(*tbl->write)(s,buf,rbyte);
		lock_task(stream_lock);
		d_f_ree(buf);
	}
	else {
		buf = d_alloc(s->h.cm->max_ret);
		rbyte = (*s->h.cm->close)((unsigned char*)buf,s->h.cm_work); 
		d_f_ree(buf);
	}
	free_sbuf(&s->h.write_buf,SEM_NULL);
	wakeup_task((int)&s->h.write_buf);

	ret = (*tbl->close)(s);

	_delete_stream_list(s,file,line);
/*
	if ( close_zonbie[zonbie_ptr] )
		d_f_ree(close_zonbie[zonbie_ptr]);

	close_zonbie[zonbie_ptr] = s;
	zonbie_ptr ++;
	if ( zonbie_ptr >= CLOSE_ZONBIE )
		zonbie_ptr = 0;
*/

	zonbie_count ++;
	s->h.zonbie_access = s->h.close_time = get_xltime();
	s->h.next = close_zonbie_list;
	close_zonbie_list = s;
/*
printf("CLOSE %i %i - %i / %x\n",zonbie_count,get_tid(),get_xltime(),s);
*/
end:
	return ret;
}


int
_d_s_close(STREAM * s,char * file,int line)
{
int ret;
int pri;
	pri = push_pri(PRI_NETWORK);

	lock_task(stream_lock);
	ret = __d_s_close(s,file,line);
	unlock_task(stream_lock,"s_close");

	change_pri(0,pri);
	return ret;
}



void
s_close_queue(STREAM * st)
{
CLOSE_QUEUE * cq;
int pri;
	if ( st == 0 )
		return;
	cq = d_alloc(sizeof(*cq));
	cq->st = st;

	pri = push_pri(PRI_NETWORK);
	lock_task(cq_lock);
	cq->next = 0;
	if ( _cq_head ) {
		_cq_tail->next = cq;
		_cq_tail = cq;
	}
	else 	_cq_head = _cq_tail = cq;

	if ( close_queue_task_nos == 0 )
		create_task(close_queue_task,0,PRI_TICK);
	wakeup_task((int)&_cq_head);
	unlock_task(cq_lock,"s_close_queue");

	change_pri(0,pri);

}


void
close_queue_task(TKEY d)
{
CLOSE_QUEUE * cq;
int t;
	lock_task(cq_lock);
	close_queue_task_nos ++;
	unlock_task(cq_lock,"s_close_queue");
	for ( ; ; ) {
		lock_task(cq_lock);
		if ( close_queue_task_nos > 2 )
			break;
		for ( ; _cq_head == 0 ; ) {
			t = get_xltime();
			for ( ; _cq_head == 0 && get_xltime() - t < 5 ; ) {
				new_timeout((int)&_cq_head,5);
				sleep_task((int)&_cq_head,cq_lock);
				lock_task(cq_lock);
			}
			if ( _cq_head == 0 )
				goto end;
		}
		cq = _cq_head;
		_cq_head = cq->next;
		if ( _cq_head == 0 )
			_cq_tail = 0;
		close_queue_task_nos --;
		unlock_task(cq_lock,"s_close_queue");

		s_close(cq->st);
		d_f_ree(cq);

		lock_task(cq_lock);
		close_queue_task_nos ++;
		unlock_task(cq_lock,"s_close_queue");
	}
end:
	close_queue_task_nos --;
	unlock_task(cq_lock,"s_close_queue");
}


