/**********************************************************************
 
	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	"xl.h"
#include	"xllock.h"
#include	"long_char.h"
#include	"utils.h"
#include	"lock_level.h"

XL_LOCK_LIST * xl_lock_hash[XL_LOCK_HASH_SIZE];
SEM xl_lock_lock;
int xll_close_xli();
void lock_tick();
XLL_WAITING_LIST * xll_w_list;

void
init_xl_lock()
{
	xl_lock_lock = new_lock(LL_XL_LOCK);
	agent_close_xli = xll_close_xli;
	new_tick(lock_tick,2,0);
}

unsigned int
lock_hash_key(L_CHAR * name)
{
unsigned int key;
	key = 0;
	for ( ; *name ; name ++ )
		key += *name;
	return key%XL_LOCK_HASH_SIZE;
}


XL_LOCK_LIST *
_search_lock_list(L_CHAR * name)
{
int key;
XL_LOCK_LIST * ret;
	key = lock_hash_key(name);
	ret = xl_lock_hash[key];
	for ( ; ret ; ret = ret->next )
		if ( l_strcmp(name,ret->name) == 0 )
			return ret;
	return 0;
}

XLL_XLI_LIST * 
_search_xli_list(XL_LOCK_LIST * xll,int iid)
{
XLL_XLI_LIST * ret;

	for ( ret = xll->xli_list ; ret ; ret = ret->next )
		if ( ret->iid == iid )
			return ret;
	return 0;
}

void
_set_xli_list_point(XLL_XLI_LIST * xll_list,L_CHAR * file,int line,int data)
{
	if ( xll_list->file )
		d_f_ree(xll_list->file);
	xll_list->file = ll_copy_str(file);
	xll_list->line = line;
	xll_list->data = data;
}

XLL_XLI_LIST * 
_new_xli_list(XL_LOCK_LIST * xll,L_CHAR * file,int line,int data)
{
XLL_XLI_LIST * ret;
int iid;
	iid = get_my_iid();
	if ( check_iid(iid) == 0 )
		return 0;
	if ( _search_xli_list(xll,iid) )
		return 0;
	ret = d_alloc(sizeof(*ret));
	ret->iid = iid;
	ret->count = 0;
	ret->file = 0;
	ret->line = 0;
	ret->seq = 0;
	_set_xli_list_point(ret,file,line,data);
	ret->next = xll->xli_list;
	xll->xli_list = ret;
	return ret;
}

void
_free_xli_list(XL_LOCK_LIST * xll,int iid)
{
XLL_XLI_LIST ** xxp, * xx;
	for ( xxp = &xll->xli_list ; *xxp ; xxp = &(*xxp)->next )
		if ( (*xxp)->iid == iid ) {
			xx = *xxp;
			*xxp = xx->next;
			if ( xx->file )
				d_f_ree(xx->file);
			d_f_ree(xx);
			return;
		}
}

XL_LOCK_LIST *
_new_lock_list(L_CHAR * name)
{
XL_LOCK_LIST * xll;
int key;
	xll = d_alloc(sizeof(*xll));
	xll->name = ll_copy_str(name);
	xll->xli_list = 0;
	xll->count = 0;
	key = lock_hash_key(name);
	xll->next = xl_lock_hash[key];
	xl_lock_hash[key] = xll;
	return xll;
}

void
_free_lock_list(XL_LOCK_LIST * xll)
{
XL_LOCK_LIST ** xllp;
int key;

	if ( xll->xli_list )
		er_panic("_free_lock_list(1)");
	key = lock_hash_key(xll->name);
	for ( xllp = &xl_lock_hash[key] ; *xllp ; xllp = &(*xllp)->next )
		if ( *xllp == xll ) {
			*xllp = xll->next;
			d_f_ree(xll->name);
			d_f_ree(xll);
			return;
		}
}


void
_insert_waiting_list(int iid)
{
XLL_WAITING_LIST * w;
	for ( w = xll_w_list ; w ; w = w->next )
		if ( w->iid == iid )
			return;
	w = d_alloc(sizeof(*w));
	w->iid = iid;
	w->next = xll_w_list;
	xll_w_list = w;
}

void
_delete_waiting_list(int iid)
{
XLL_WAITING_LIST * w,** wp;
	for ( wp = &xll_w_list ; *wp ; wp = &(*wp)->next ) {
		w = *wp;
		if ( w->iid == iid )
			break;
	}
	*wp = w->next;
	d_f_ree(w);
	return;
}


int
_exist_lock_info(int iid)
{
XL_LOCK_LIST * xll;
int key;
XLL_WAITING_LIST * w;
	for ( key = 0 ; key < XL_LOCK_HASH_SIZE ; key ++ )
		for ( xll = xl_lock_hash[key] ; xll ; xll = xll->next ) {
			if ( _search_xli_list(xll,iid) )
				return 0;
		}
	for ( w = xll_w_list ; w ; w = w->next )
		if ( w->iid == iid )
			return 0;
	return -1;
}

int
exist_lock_info(int iid)
{
int ret;
	lock_task(xl_lock_lock);
	ret = _exist_lock_info(iid);
	unlock_task(xl_lock_lock,"exist_lock_info");
	return ret;
}

void
_wakeup_lockking_proc(XL_LOCK_LIST * xll,int iid)
{
XLL_XLI_LIST * xli_list;
char * buff;
	for ( xli_list = xll->xli_list ; xli_list ;
			xli_list = xli_list->next ) {
		if ( xli_list->seq == 0 )
			continue;
		if ( xli_list->iid == iid )
			continue;
		buff = d_alloc(1000);
		sprintf(buff,"(call-lock-event %i)\n",
				xli_list->seq);
		gc_push(0,0,"_wakeup_lockking_proc");
		remote_query(xli_list->iid,gblisp_top_env1,
			l_string(std_cm,buff),0);
		gc_pop(0,0);
	}
}


XL_SEXP *
xl_lock(L_CHAR * name,int type,XL_SEXP * s,
	L_CHAR * __file,int __line,int __data,int __seq)
{
XL_LOCK_LIST * xll;
XLL_XLI_LIST * xli_list;
XL_SEXP * r;
int iid;

	iid = get_my_iid();
	lock_task(xl_lock_lock);
	for ( ; ; ) {
		xll = _search_lock_list(name);
		if ( xll == 0 )
			xll = _new_lock_list(name);
		xli_list = _search_xli_list(xll,iid);
		if ( xli_list ) {
			xli_list->count ++;
			if ( xll->count > 0 )
				xll->count ++;
			else	xll->count --;
			_set_xli_list_point(xli_list,__file,__line,__data);
			unlock_task(xl_lock_lock,"xl_lock");
			return 0;
		}
		if ( type == LT_READ ) {
			if ( xll->count >= 0 ) {
				xli_list = _new_xli_list(xll,__file,__line,__data);
				if ( xli_list == 0 ) {
					unlock_task(xl_lock_lock,"xl_lock");
					return 0;
				}
				xll->count ++;
				xli_list->seq = __seq;
				xli_list->count = 1;
				_wakeup_lockking_proc(xll,iid);
				unlock_task(xl_lock_lock,"xl_lock");
				return 0;
			}
		}
		else {
			if ( xll->count == 0 ) {
				xli_list = _new_xli_list(xll,__file,__line,__data);
				if ( xli_list == 0 ) {
					unlock_task(xl_lock_lock,"xl_lock");
					return 0;
				}
				xll->count = -1;
				xli_list->count = 1;
				xli_list->seq = __seq;
				_wakeup_lockking_proc(xll,iid);
				unlock_task(xl_lock_lock,"xl_lock");
				return 0;
			}
		}
		_insert_waiting_list(iid);
		_wakeup_lockking_proc(xll,iid);

		sleep_task((int)xll,xl_lock_lock);

		lock_task(xl_lock_lock);
		_delete_waiting_list(iid);
		if ( break_check ) {
			r = (*break_check)(s);
			if ( get_type(r) == XLT_ERROR ) {
				unlock_task(xl_lock_lock,"xl_lock");
				return r;
			}
		} 
	}
}

int
xl_unlock(L_CHAR * name)
{
XL_LOCK_LIST * xll;
XLL_XLI_LIST * xli_list;
int iid;
int ret;
	lock_task(xl_lock_lock);
	xll = _search_lock_list(name);
	if ( xll == 0 ) {
		ret = -1;
		goto end;
	}
	iid = get_my_iid();
	xli_list = _search_xli_list(xll,iid);
	if ( xli_list == 0 ) {
		ret = -2;
		goto end;
	}
	xli_list->count --;
	if ( xli_list->count == 0 )
		_free_xli_list(xll,iid);
	if ( xll->count > 0 )
		xll->count --;
	else	xll->count ++;
	if ( xll->count == 0 ) {
		_free_lock_list(xll);
		wakeup_task((int)xll);
	}
	ret = 0;
end:
	unlock_task(xl_lock_lock,"xl_unlock");
	return ret;
}

int
xll_close_xli(XL_INTERPRETER * xli)
{
int i;
XL_LOCK_LIST * xll, * xll2;
XLL_XLI_LIST * xli_list;
int cnt;

	lock_task(xl_lock_lock);
	for ( i = 0 ; i < XL_LOCK_HASH_SIZE ; i ++ )
		for ( xll = xl_lock_hash[i] ; xll ;) {
			xli_list = _search_xli_list(xll,xli->id);
			if ( xli_list == 0 ) {
				xll = xll->next;
				continue;
			}
			cnt = xli_list->count;
			xli_list->count = 0;
			_free_xli_list(xll,xli->id);
			if ( xll->count > 0 )
				xll->count -= cnt;
			else	xll->count += cnt;
			if ( xll->count == 0 ) {
				xll2 = xll->next;
				_free_lock_list(xll);
				wakeup_task((int)xll);
				xll = xll2;
			}
			else {
				xll = xll->next;
			}
		}
	unlock_task(xl_lock_lock,"xli_lock_close");
	return 0;
}

void
_check_xll(XL_LOCK_LIST * xll)
{
XLL_XLI_LIST * lst, * lst2;
int cnt;
	for ( lst = xll->xli_list ; lst ; ) {
		lst2 = lst->next;
		if ( check_iid(lst->iid) ) {
			lst = lst2;
			continue;
		}
		cnt = lst->count;
		lst->count = 0;
		_free_xli_list(xll,lst->iid);
		if ( xll->count > 0 )
			xll->count -= cnt;
		else	xll->count += cnt;
		if ( xll->count == 0 ) {
			_free_lock_list(xll);
			wakeup_task((int)xll);
			return;
		}
		lst = lst2;
	}
}

void
lock_tick()
{
XL_LOCK_LIST * xll, * xll2;
int i;
	lock_task(xl_lock_lock);
	for ( i = 0 ; i < XL_LOCK_HASH_SIZE ; i ++ )
		for ( xll = xl_lock_hash[i] ; xll ; ) {
			xll2 = xll->next;
			_check_xll(xll);
			wakeup_task((int)xll);
			xll = xll2;
		}
	unlock_task(xl_lock_lock,"lock_tick");
}


int
lock_sync()
{
int i;
int ret;
	ret = 0;
	lock_task(xl_lock_lock);
	for ( i = 0 ; i < XL_LOCK_HASH_SIZE ; i ++ )
		if ( xl_lock_hash[i] ) {
			ret = -1;
			break;
		}
	unlock_task(xl_lock_lock,"lock_sync");
	return ret;
}


