/**********************************************************************
 
	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();

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;
}

XLL_XLI_LIST * 
_new_xli_list(XL_LOCK_LIST * xll)
{
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->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;
			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;
		}
}

XL_SEXP *
xl_lock(L_CHAR * name,int type,XL_SEXP * s)
{
XL_LOCK_LIST * xll;
XLL_XLI_LIST * xli_list;
XL_SEXP * r;

	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,get_my_iid());
		if ( xli_list ) {
			xli_list->count ++;
			if ( xll->count > 0 )
				xll->count ++;
			else	xll->count --;
			unlock_task(xl_lock_lock,"xl_lock");
			return 0;
		}
		if ( type == LT_READ ) {
			if ( xll->count >= 0 ) {
				xli_list = _new_xli_list(xll);
				if ( xli_list == 0 ) {
					unlock_task(xl_lock_lock,"xl_lock");
					return 0;
				}
				xll->count ++;
				xli_list->count = 1;
				unlock_task(xl_lock_lock,"xl_lock");
				return 0;
			}
		}
		else {
			if ( xll->count == 0 ) {
				xli_list = _new_xli_list(xll);
				if ( xli_list == 0 ) {
					unlock_task(xl_lock_lock,"xl_lock");
					return 0;
				}
				xll->count = -1;
				xli_list->count = 1;
				unlock_task(xl_lock_lock,"xl_lock");
				return 0;
			}
		}
		sleep_task((int)xll,xl_lock_lock);
		lock_task(xl_lock_lock);
		if ( break_check ) {
			r = (*break_check)(s);
			if ( get_type(r) == XLT_ERROR ) {
				unlock_task(xl_lock_lock,"xl_lock");
				return r;
			}
		} 
	}
}

void
xl_unlock(L_CHAR * name)
{
XL_LOCK_LIST * xll;
XLL_XLI_LIST * xli_list;
int iid;
	lock_task(xl_lock_lock);
	xll = _search_lock_list(name);
	if ( xll == 0 )
		goto end;
	iid = get_my_iid();
	xli_list = _search_xli_list(xll,iid);
	if ( xli_list == 0 )
		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);
	}
end:
	unlock_task(xl_lock_lock,"xl_unlock");
}

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;
}


