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

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


#include	"memory_debug.h"
#include	"lock_level.h"
#include	"task.h"
#include	"stream.h"
#include	"s_buf.h"
#include	"xl.h"
#include	"XLoHTTP.h"

#define DEL_TIMEOUT1	600
#define DEL_TIMEOUT2	6000
#define XoH_HANGING_LIMIT	600

#define XoH_HASH_SIZE	13
#define XoH_KEY(c)	(((c)[0]*3 + (c)[1])%XoH_HASH_SIZE)

typedef struct XoH_list {
	struct XoH_list *	next;
	STREAM *		con;
	XLoHTTP_HEADER		h;
	HTTP_INFO		info;
} XoH_LIST;

typedef struct XoH_entry {
	struct XoH_entry * 	next;
	int			flags;
#define XoH_E_F_ERR		0x00000001
#define XoH_E_EXIT_RECV		0x80000000
#define XoH_E_EXIT_SEND		0x40000000
#define XoH_E_EXIT		0xc0000000
#define XoH_E_KEEP_ALIVE	0x00001000
#define XoH_E_SEND_WAIT		0x00000010
	unsigned int		cockie[2];
	XoH_LIST *		list;
	STREAM *		pipe;
	STREAM_BUF		recv;
	short			recv_seq;
	short			send_seq;
	unsigned int		ip;
	XL_INTERPRETER * 	xli;
	PERMISSION_LIST *	pl;
	unsigned int		last_polling;
	STREAM *		recv_target;
} XoH_ENTRY;

typedef struct stream_stock {
	struct stream_stock *	next;
	ACCESS_KEY		key;
	XL_INTERPRETER *	xli;
	PERMISSION_LIST *	pl;
} STREAM_STOCK;

XoH_ENTRY *	XoH_hash[XoH_HASH_SIZE];
SEM		XoH_lock;
unsigned int	cockie_cnt;
unsigned int	cockie_start_time;
STREAM_STOCK *	ss_list;
int		ss_wait_tasks,ss_run_tasks;

void ss_task();

void
check_fid_tick()
{
	printf("fid nos = %i\n",s_check_resource2(0));
}

void
init_XLoHTTP_server()
{
void empty_reply_task();

	XoH_lock = new_lock(LL_XoH);
	cockie_cnt = 0x80000000;
	cockie_start_time = get_xltime();
/*
	new_tick(check_fid_tick,2,2);
*/
	create_task(empty_reply_task,0,1);

	ss_run_tasks = 0;
	create_task(ss_task,0,1);
}


XoH_ENTRY *
_search_XoH_entry(unsigned int * cockie)
{
unsigned int key;
XoH_ENTRY * ret;
	key = XoH_KEY(cockie);
	for ( ret = XoH_hash[key] ; ret ; ret = ret->next )
		if ( ret->cockie[0] == cockie[0] &&
			ret->cockie[1] == cockie[1] )
			return ret;
	return 0;
}

XoH_ENTRY *
_new_XoH_entry(unsigned int *cockie)
{
XoH_ENTRY * ret;
unsigned int key;
	ret = d_alloc(sizeof(*ret));
	memcpy(ret->cockie,cockie,sizeof(ret->cockie));
	ret->list = 0;
	ret->flags = 0;
	ret->pipe = 0;
	ret->recv_seq = ret->send_seq = 1;
	init_sbuf(&ret->recv,0);
	key = XoH_KEY(cockie);
	ret->next = XoH_hash[key];
	XoH_hash[key] = ret;
	ret->last_polling = get_xltime();
	ret->recv_target = 0;
	return ret;
}

int
output_server_header(STREAM * s,HTTP_INFO * info)
{
	s_printf(s,"HTTP/1.1 200 OK\r\n");
	s_printf(s,"Date: Fri, 20 Sep 2002 09:36:44 GMT\r\n");
	s_printf(s,"Server: LANDSCAPE GLOBALBASE SERVER\r\n");
	if ( info->flags & XoHINF_F_KEEP_ALIVE )
		s_printf(s,"Connection: keep-alive\r\n");
	else	s_printf(s,"Connection: close\r\n");
	s_printf(s,"Content-Type: application/xlohttp\r\n");
	s_printf(s,"Content-Length: %i\r\n\r\n",info->content_length);
	return 0;
}


void
_free_XoH_list(XoH_LIST * lst)
{
XoH_LIST * l;
int ret_len;
HTTP_INFO info;
	for ( ; lst ;  ) {
		l = lst;
		lst = lst->next;
		l->h.type = XoH_T_ERROR;
		l->h.sum = 0;
		change_endian_XLoHTTP_HEADER(&l->h);
		l->h.sum = XoH_checksum(&l->h);

		info.flags = 0;
		info.content_length = sizeof(l->h);

		output_server_header(l->con,&info);
		en_do(&ret_len,s_write,l->con,&l->h,sizeof(l->h));
		s_close(l->con);
		d_f_ree(l);
	}
}

void
_destroy_XoH_entry(XoH_ENTRY * target)
{
XoH_ENTRY ** ep, * e;
unsigned int key;


	key = XoH_KEY(target->cockie);
	for ( ep = &XoH_hash[key] ; *ep ; ep = &(*ep)->next )
		if ( (*ep) == target ) {
			e = *ep;
			*ep = e->next;
			_free_XoH_list(e->list);
			if ( e->recv_target )
				s_close(e->recv_target);
			d_f_ree(e);
			return;
		}
}


void
_new_cockie(unsigned int * cockie)
{
unsigned int now;
	now = get_xltime();
	cockie[0] = now;
	cockie[1] = cockie_cnt ++;
	if ( now - cockie_start_time <= 10 ) {
		if ( cockie_cnt < 0x80000000 )
			cockie_cnt = 0x80000000;
	}
	else {
		if ( cockie_cnt >= 0x80000000 )
			cockie_cnt = 0;
	}
}

int
_insert_list(XoH_ENTRY * e,XoH_LIST * lst)
{
XoH_LIST ** lp;

	for ( lp = &e->list ; *lp ; lp = &(*lp)->next ) {
		if ( (*lp)->h.seq == lst->h.seq )
			return -1;
		if ( (*lp)->h.seq - lst->h.seq > 0 ) {
			lst->next = *lp;
			*lp = lst;
			wakeup_task((int)e);
			return 0;
		}
	}
	*lp = lst;
	lst->next = 0;
	e->last_polling = get_xltime();
	wakeup_task((int)e);
	return 0;
}

void
_insert_ss(STREAM *s,unsigned int ip,XL_INTERPRETER * xli,PERMISSION_LIST * pl)
{
STREAM_STOCK * ss;

	ss = d_alloc(sizeof(*ss));
	ss->key.s = s;
	ss->key.ip = ip;
	ss->xli = xli;
	ss->pl = pl;

	ss->next = ss_list;
	ss_list = ss;

	wakeup_task((int)&ss_list);
}

STREAM_STOCK *
_delete_ss()
{
STREAM_STOCK * ss;

retry:
	ss = ss_list;
	if ( ss == 0 )
		return 0;
	ss_list = ss->next;
	if ( ss->key.s == 0 || ss->xli == 0 ) {
		d_f_ree(ss);
		goto retry;
	}
	return ss;
}

void
sslist_scan()
{
STREAM_STOCK * ss;
	for ( ss = ss_list ; ss ; ss = ss->next ) {
		if ( s_exist(ss->key.s) )
			continue;
		ss->key.s = 0;
		ss->xli = 0;
	}
}

void
insert_ss(STREAM * s,unsigned int ip,XL_INTERPRETER * xli,PERMISSION_LIST * pl)
{

	lock_task(XoH_lock);
	_insert_ss(s,ip,xli,pl);
	unlock_task(XoH_lock,"insert_ss");
}

STREAM *
delete_ss()
{
STREAM_STOCK * ret;
	lock_task(XoH_lock);
	ret = _delete_ss();
	unlock_task(XoH_lock,"delete_ss");
	return ret;
}

void
timeout_stream(STREAM * s)
{
	s_close(s);
}


int
new_accept(
	STREAM ** new_st,
	ACCESS_KEY * key,
	XL_INTERPRETER * xli,
	PERMISSION_LIST * pl)
{
XoH_ENTRY * e;
XoH_LIST * lst;
int ret_len;
STREAM * p[2];
int ret;
void recv_task();
void send_task();
char * dummy;
HTTP_INFO o_info;

	lst = d_alloc(sizeof(*lst));
	lst->con = key->s;
	lst->next;
	new_tick(timeout_stream,-DEL_TIMEOUT1,(int)lst->con);
	if ( scan_HTTP(&lst->info,key->s,1) < 0 ) {
		del_tick_with_data(timeout_stream,(int)lst->con);
		d_f_ree(lst);
		s_close(key->s);
		return -1;
	}
	if ( lst->info.content_length >= 0 && 
			lst->info.content_length < sizeof(lst->h) ) {
		del_tick_with_data(timeout_stream,(int)lst->con);
		free_HTTP_info(&lst->info);
		d_f_ree(lst);
		s_close(key->s);
		return -1;
	}
	if ( en_do(&ret_len,s_read,key->s,&lst->h,sizeof(lst->h)) <= 0 ) {
		del_tick_with_data(timeout_stream,(int)lst->con);
		free_HTTP_info(&lst->info);
		d_f_ree(lst);
		s_close(key->s);
		return -1;
	}
	del_tick_with_data(timeout_stream,(int)lst->con);

	change_endian_XLoHTTP_HEADER(&lst->h);
	lst->h.sum = XoH_checksum(&lst->h);

	lock_task(XoH_lock);
	if ( lst->h.sum ) {
		goto err;
	}
	else if ( lst->h.type == XoH_T_OPEN ) {
	XLoHTTP_HEADER h;
		_new_cockie(lst->h.cockie);
		e = _new_XoH_entry(lst->h.cockie);
		s_open_fpipe(p);
		e->pipe = p[0];
		s_set_fpipe_ip_addr(e->pipe,key->ip);
		*new_st = p[1];

		e->ip = key->ip;
		e->xli = xli;


		if ( lst->info.flags & XoHINF_F_KEEP_ALIVE )
			e->flags |= XoH_E_KEEP_ALIVE;


		ret = 1;

		create_task(recv_task,(int)e,1);
		create_task(send_task,(int)e,1);

		h = lst->h;

		if ( e->flags & XoH_E_KEEP_ALIVE )
			h.type = XoH_T_KEEP_ALIVE;
		else	h.type = XoH_T_DATA;
		h.sum = 0;
		change_endian_XLoHTTP_HEADER(&h);
		h.sum = XoH_checksum(&h);

		if ( e->flags & XoH_E_KEEP_ALIVE )
			o_info.flags = XoHINF_F_KEEP_ALIVE;
		else	o_info.flags = 0;
		o_info.content_length = sizeof(h);

		output_server_header(lst->con,&o_info);
		en_do(&ret_len,s_write,lst->con,&h,sizeof(h));

		if ( e->flags & XoH_E_KEEP_ALIVE )
{
			_insert_ss(lst->con,e->ip,e->xli,e->pl);
}
		else	s_close(lst->con);
		free_HTTP_info(&lst->info);
		d_f_ree(lst);
	}
	else {
		e = _search_XoH_entry(lst->h.cockie);
		if ( e == 0 ) {
		XLoHTTP_HEADER h;

		err:
			h = lst->h;
			h.sum = 0;
			h.type = XoH_T_ERROR;
			change_endian_XLoHTTP_HEADER(&h);
			h.sum = XoH_checksum(&h);

			o_info.flags = 0;
			o_info.content_length = sizeof(h);

			output_server_header(lst->con,&o_info);
			en_do(&ret_len,s_write,lst->con,&h,
				sizeof(h));
			s_close(lst->con);
			free_HTTP_info(&lst->info);
			d_f_ree(lst);
			*new_st = 0;
			ret = -1;
		}
		else {
			*new_st = 0;
			ret = 0;
			_insert_list(e,lst);
		}
	}
	unlock_task(XoH_lock,"new_accept");


	return ret;
}


void
ss_task()
{
STREAM * new;
STREAM_STOCK * ss;
int ret;
	lock_task(XoH_lock);
	ss_run_tasks ++;
	unlock_task(XoH_lock,"ss_task");
	for ( ; ; ) {
		lock_task(XoH_lock);
		for ( ; ; ) {
			ss = _delete_ss();
			if ( ss )
				break;
			if ( ss_wait_tasks > 2 ) {
				unlock_task(XoH_lock,"ss_task");
				return;
			}
			ss_run_tasks --;
			ss_wait_tasks ++;
			sleep_task((int)&ss_list,XoH_lock);
			lock_task(XoH_lock);
			ss_wait_tasks --;
			ss_run_tasks ++;
			if ( ss_wait_tasks <= 0 && ss_run_tasks < 10 )
				create_task(ss_task,0,1);
		}
		unlock_task(XoH_lock,"ss_task");
		ret = new_accept(&new,&ss->key,ss->xli,ss->pl);
		switch ( ret ) {
		case -1:
			/* error */
			s_close(ss->key.s);
			d_f_ree(ss);
			break;
		case 0:
			/* nothing to do */
			d_f_ree(ss);
			break;
		case 1:
			/* launch new stream */
			launch_accept_thread(
				new,
				ss->key.ip,
				ss->xli,
				ss->pl);
			d_f_ree(ss);
			break;
		default:
			{}
		}
	}
}

XoH_LIST *
get_recv_seq_list(XoH_ENTRY * e)
{
XoH_LIST * lst;

	for ( lst = e->list ; lst ; lst = lst->next )
		if ( lst->h.seq == e->recv_seq )
			return lst;
	return 0;
}


int
_empty_reply(XoH_ENTRY * e)
{
XoH_LIST * lst;
XLoHTTP_HEADER h;
HTTP_INFO o_info;
int ret_len;
int ret;


	ret = 0;
	if ( e->list == 0 )
		goto end;
	lst = e->list;
	if ( lst->h.seq - e->recv_seq >= 0 )
		goto end;

	e->list = lst->next;

	h = lst->h;
	h.type = XoH_T_DATA;
	h.sum = 0;
	h.sum = XoH_checksum(&h);
	change_endian_XLoHTTP_HEADER(&h);

	if ( e->flags & XoH_E_KEEP_ALIVE )
		o_info.flags = XoHINF_F_KEEP_ALIVE;
	else	o_info.flags = 0;
	o_info.content_length = sizeof(h);
	output_server_header(lst->con,&o_info);
	en_do(&ret_len,s_write,lst->con,&h,sizeof(h));
	if ( !(e->flags & XoH_E_KEEP_ALIVE) )
		s_close(lst->con);
	d_f_ree(lst);
	ret = 1;
	e->send_seq ++;
	wakeup_task((int)e);
end:
	return ret;
}

void
recv_task(TKEY d)
{
XoH_ENTRY * e;
XoH_LIST * lst;
STREAM * con;
int type;
int ret_len;
int len;
	e = (XoH_ENTRY*)GET_TKEY(d);
	for ( ; ; ) {
		lock_task(XoH_lock);

		wakeup_task((int)e);

		for ( ; (lst = get_recv_seq_list(e)) == 0 &&
				(e->flags & (XoH_E_EXIT|XoH_E_F_ERR))
					 == 0 ; ) {
			sleep_task((int)e,XoH_lock);
			lock_task(XoH_lock);
		}
		if ( lst == 0 || (e->flags & XoH_E_F_ERR) ) {
			e->flags |= XoH_E_EXIT_RECV;
			if ( (e->flags & XoH_E_EXIT) == XoH_E_EXIT ) {
				s_close(e->pipe);
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			wakeup_task((int)e);
			unlock_task(XoH_lock,"send_task");
			break;
		}
		e->recv_target = con = lst->con;
		type = lst->h.type;

		if ( lst->info.content_length < 0 )
			len = -1;
		else	len = lst->info.content_length - sizeof(lst->h);
		unlock_task(XoH_lock,"get_task");
		if ( len == 0 )
			goto next;
		new_tick(timeout_stream,-DEL_TIMEOUT2,(int)con);
		switch ( s_copy_to_stream(&ret_len,con,e->pipe,len) ) {
		case 0:
			break;
		case S_COPY_TO_STREAM_SRC_ERR:
			break;
		case S_COPY_TO_STREAM_DEST_ERR:
			del_tick_with_data(timeout_stream,(int)con);
			goto last;
		}
		del_tick_with_data(timeout_stream,(int)con);
	next:
		lock_task(XoH_lock);
		e->recv_seq ++;
		unlock_task(XoH_lock,"recv_task");
		if ( type == XoH_T_LAST ) {
		last:
			lock_task(XoH_lock);
			e->flags |= XoH_E_EXIT_RECV;
			e->recv_target = 0;
			s_close(e->pipe);
			e->pipe = 0;
			if ( (e->flags & XoH_E_EXIT)
					== XoH_E_EXIT ) { 
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			wakeup_task((int)e);
			unlock_task(XoH_lock,"recv_task");
			break;
		}
		if ( e->flags & XoH_E_KEEP_ALIVE )
			insert_ss(con,e->ip,e->xli,e->pl);
		e->recv_target = 0;
	}

}

void
send_task(TKEY d)
{
XoH_ENTRY * e;
XoH_LIST * lst;
int len;
int ret_len;
XLoHTTP_HEADER h;
HTTP_INFO o_info;

	e = (XoH_ENTRY*)GET_TKEY(d);
	for ( ; ; ) {

		len = s_wait_flush_fpipe(e->pipe);

		lock_task(XoH_lock);
		if ( len < 0 ) {
			e->flags |= XoH_E_EXIT_SEND;
			if ( (e->flags & XoH_E_EXIT)
					== XoH_E_EXIT ) {
				s_close(e->pipe);
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			wakeup_task((int)e);
			unlock_task(XoH_lock,"send_task");
			break;
		}
		for ( ; (e->list == 0 ||
				e->list->h.seq != e->send_seq ||
				e->list->h.seq - e->recv_seq >= 0 ) &&
				(e->flags & (XoH_E_EXIT|XoH_E_F_ERR))
					== 0 ; ) {
			e->flags |= XoH_E_SEND_WAIT;
			sleep_task((int)e,XoH_lock);
			lock_task(XoH_lock);
			e->flags &= ~XoH_E_SEND_WAIT;
		}
		if ( e->list == 0 &&
				(e->flags & XoH_E_EXIT) ||
				(e->flags & XoH_E_F_ERR) ) {
			e->flags |= XoH_E_EXIT_SEND;
			if ( (e->flags & XoH_E_EXIT) == XoH_E_EXIT ) {
				s_close(e->pipe);
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			wakeup_task((int)e);
			unlock_task(XoH_lock,"send_task");
			break;
		}
		lst = e->list;
		e->list = lst->next;
		e->send_seq ++;

		unlock_task(XoH_lock,"send_task");

		h = lst->h;
		len = s_get_fpipe_data_size(e->pipe);
		if ( len <= 0 ) {
			lock_task(XoH_lock);
			e->flags |= XoH_E_EXIT_SEND;
			if ( (e->flags & XoH_E_EXIT)
					== XoH_E_EXIT ) {
				s_close(e->pipe);
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			unlock_task(XoH_lock,"send_task");

			h.type = XoH_T_LAST;
			h.sum = 0;
			change_endian_XLoHTTP_HEADER(&h);
			h.sum = XoH_checksum(&h);

			o_info.flags = 0;
			o_info.content_length = sizeof(h);

			output_server_header(lst->con,&o_info);
			en_do(&ret_len,s_write,lst->con,
				&h,sizeof(h));
			s_close(lst->con);
			free_HTTP_info(&lst->info);
			d_f_ree(lst);
			break;
		}
		if ( h.type != XoH_T_LAST )
			h.type = XoH_T_DATA;
		h.sum = 0;
		change_endian_XLoHTTP_HEADER(&h);
		h.sum = XoH_checksum(&h);

		if ( e->flags & XoH_E_KEEP_ALIVE )
			o_info.flags = XoHINF_F_KEEP_ALIVE;
		else	o_info.flags = 0;
		o_info.content_length = sizeof(h)+len;

		output_server_header(lst->con,&o_info);
		en_do(&ret_len,s_write,lst->con,&h,sizeof(h));
		s_copy_to_stream(&ret_len,e->pipe,lst->con,len);

		if ( lst->h.type == XoH_T_LAST ) {
			lock_task(XoH_lock);
			e->flags |= XoH_E_EXIT_SEND;
			if ( (e->flags & XoH_E_EXIT)
					== XoH_E_EXIT ) {
				s_close(e->pipe);
				e->pipe = 0;
				_destroy_XoH_entry(e);
			}
			s_close(lst->con);
			free_HTTP_info(&lst->info);
			d_f_ree(lst);
			unlock_task(XoH_lock,"send_task");
			break;
		}
		if ( !(e->flags & XoH_E_KEEP_ALIVE) )
			s_close(lst->con);
		free_HTTP_info(&lst->info);
		d_f_ree(lst);
	}
}



void
_polling(XoH_ENTRY * e,unsigned int now)
{
int ip;
	ip = s_get_socketip(e->pipe);
	if ( ip == -1 )
		goto err;
	if ( now - e->last_polling < XoH_HANGING_LIMIT )
		return;
err:
	e->flags |= XoH_E_F_ERR;
	s_close(e->pipe);
	e->pipe = 0;
	wakeup_task((int)e);
}



void
empty_reply_task()
{
int i;
XoH_ENTRY * e;
int f;
int now;
unsigned int last_polling;
int opm,op;
int hl;
unsigned int hl_last;
	hl_last = get_xltime();
	hl = XoH_HANGING_LIMIT/4;
	last_polling = get_xltime();
	for ( ; ; ) {
		now = get_xltime();
		lock_task(XoH_lock);
		for ( i = 0 ; i < XoH_HASH_SIZE ; i ++ )
			for ( e = XoH_hash[i]; e ; e = e->next )
				for ( ; _empty_reply(e) ; );
		if ( now - hl_last >= 5 ) {
			hl_last = get_xltime();
			op = s_check_resource2(&opm);
			hl = XoH_HANGING_LIMIT/4 * (opm - op) / opm;
			if ( hl < 1 )
				hl = 1;
		}

		if ( now - last_polling < hl )
			goto next;
		for ( i = 0 ; i < XoH_HASH_SIZE ; i ++ )
			for ( e = XoH_hash[i] ; e ; e = e->next )
				_polling(e,now);
		last_polling = get_xltime();
	next:
		sslist_scan();
		unlock_task(XoH_lock,"empty_reply_task");
		sleep_sec(1);
	}
}

