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

#define CL_LIVE_TIME		3600
#define CL_HASH_SIZE		103

typedef struct call_lock_interpreter {
	struct call_lock_interpreter *	next;
	int				xli;
	int				ref;
	int				status;
#define LIS_IDLE		0
#define LIS_CALL		1
#define LIS_ERROR		2
	unsigned int			call_time;
} CALL_LOCK_INTERPRETER;

typedef struct call_lock_thread {
	struct call_lock_thread *	next;
	int				tid;
} CALL_LOCK_THREAD;

typedef struct call_lock_table {
	struct call_lock_table *	next;
	L_CHAR *			name;
	int				tid;
	CALL_LOCK_THREAD *		threads;
	CALL_LOCK_INTERPRETER *		cli;
} CALL_LOCK_TABLE;


CALL_LOCK_INTERPRETER *		li_list;
CALL_LOCK_TABLE *			lt_list;
SEM				call_lock_lock;
int				lock_port;
L_CHAR *			lock_hostname;


void
call_lock_tick();

void
init_call_lock()
{
	call_lock_lock = new_lock(LL_CALL_LOCK);
	new_tick(call_lock_tick,CL_LIVE_TIME,0);
}



int
_connect_lock_xli()
{
XL_SEXP * port;
XL_SEXP * hostname;
XL_INTERPRETER * xli;
int retry_cnt;
int id;

	if ( lock_port == 0 ) {
		port = eval(gblisp_top_env1,
			get_symbol(l_string(std_cm,"LockPort")));
		switch ( get_type(port) ) {
		case XLT_ERROR:
			return -1;
		case XLT_INTEGER:
			lock_port = port->integer.data;
			break;
		default:
			return -1;
		}
	}
	if ( lock_hostname == 0 ) {
		hostname = eval(gblisp_top_env1,
			get_symbol(l_string(std_cm,"LockHostName")));
		switch ( get_type(hostname) ) {
		case XLT_ERROR:
			lock_port = 0;
			return -1;
		case XLT_STRING:
			lock_hostname = ll_copy_str(hostname->string.data);
			break;
		default:
			lock_port = 0;
			return -1;
		}
	}

	retry_cnt = 3;
retry:
	xli = new_xl_interpreter();
	xli->a_type = XLA_CONNECT;
	xli->env = gblisp_top_env1;
	xli->environment = 0;
	xli->port = lock_port;
	xli->hostname = ll_copy_str(lock_hostname);
	id = setup_i(xli);
	if ( id < 0 ) {
		retry_cnt --;
		if ( retry_cnt > 0 ) {
			sleep_sec(5);
			goto retry;
		}
		return -1;
	}
	return id;
}


void
_connect_unlock_xli(CALL_LOCK_INTERPRETER * cli,L_CHAR * dir)
{
char * cmd;
L_CHAR * l_cmd;
XL_SEXP * ret;

	cli->status = LIS_CALL;
	gc_push(0,0,"connect_unlock"); 
	cmd = d_alloc(1000);
	sprintf(cmd,"<Unlock> %s </Unlock>",
		n_string(std_cm,dir));
	l_cmd = nl_copy_str(std_cm,cmd);
	d_f_ree(cmd);

	unlock_task(call_lock_lock,"_connect_unlock_xli");
	ret = remote_query(cli->xli,gblisp_top_env1,l_cmd,0);
	get_type(ret);

	lock_task(call_lock_lock);
	d_f_ree(l_cmd);
	gc_pop(0,0);
	cli->status = LIS_IDLE;
}

CALL_LOCK_INTERPRETER *
_new_cli()
{
CALL_LOCK_INTERPRETER * ret;
	ret = d_alloc(sizeof(*ret));
	memset(ret,0,sizeof(*ret));
	ret->xli = _connect_lock_xli();
	ret->ref = 0;
	ret->status = LIS_IDLE;
	ret->call_time = get_xltime();
	ret->next = li_list;
	li_list = ret;
	return ret;
}

void
_free_cli(CALL_LOCK_INTERPRETER * cli)
{
CALL_LOCK_INTERPRETER ** clp;
	if ( cli->status == LIS_CALL )
		er_panic("_free_cli");
	if ( cli->ref )
		er_panic("_free_cli");
	for ( clp = &li_list ;
		*clp && *clp != cli;
		clp = &(*clp)->next );
	if ( *clp == 0 )
		er_panic("_free_cli");
	*clp = cli->next;
	d_f_ree(cli);
}

CALL_LOCK_INTERPRETER *
_search_cli_idle()
{
CALL_LOCK_INTERPRETER * ret;
	for ( ret = li_list ; ret ; ret = ret->next )
		if ( ret->status == LIS_IDLE )
			return ret;
	return 0;
}


CALL_LOCK_TABLE *
_get_clt_from_desc(CALL_LOCK_DESCRIPTER d)
{
CALL_LOCK_TABLE * ret;
CALL_LOCK_TABLE * _ret;
	_ret = d.d;
	if ( _ret == 0 )
		return 0;
	for ( ret = lt_list ; ret && ret != _ret ; ret = ret->next );
	return ret;
}

void
_cl_insert_thread(CALL_LOCK_TABLE * clt,int tid)
{
CALL_LOCK_THREAD * t;
	t = d_alloc(sizeof(*t));
	t->tid = tid;
	t->next= clt->threads;
	clt->threads = t;
}

int
_cl_delete_thread(CALL_LOCK_TABLE * clt,int tid)
{
CALL_LOCK_THREAD ** tp, * t;
	for ( tp = &clt->threads ; *tp ; tp = &(*tp)->next ) {
		t = *tp;
		if ( t->tid == tid ) {
			*tp = t->next;
			d_f_ree(t);
			return 0;
		}
	}
	return -1;
}


void
_free_clt(CALL_LOCK_TABLE * clt)
{
CALL_LOCK_INTERPRETER * cli;
CALL_LOCK_TABLE ** clp;
	if ( clt->threads )
		er_panic("_free_clt");

	for ( clp = &lt_list ;
			*clp && *clp != clt;
			clp = &(*clp)->next );
	if ( *clp == 0 )
		er_panic("*_free_clt");
	*clp = clt->next;
	cli = clt->cli;

	if ( cli ) {
		_connect_unlock_xli(cli,clt->name);
		cli->ref --;
		if ( cli->ref < 0 )
			er_panic("_free_clt");
	}

	d_f_ree(clt->name);
	d_f_ree(clt);
}


CALL_LOCK_TABLE *
_new_clt(L_CHAR * lock_dir,int _type,int tid)
{
CALL_LOCK_TABLE * ret;
CALL_LOCK_INTERPRETER * cl_int;
char * cmd;
L_CHAR * l_cmd;
int retry_cnt;
char * type;
XL_SEXP * rq_ret;
int n_cli;

	ret = d_alloc(sizeof(*ret));
	memset(ret,0,sizeof(*ret));
	ret->name = ll_copy_str(lock_dir);
	switch ( _type ) {
	case CLT_READ_LOCK:
		type = "read";
		ret->tid = 0;
		break;
	case CLT_WRITE_LOCK:
		type = "write";
		ret->tid = tid;
		break;
	default:
		er_panic("_new_clt");
	}
	_cl_insert_thread(ret,tid);
	ret->next = lt_list;
	lt_list = ret;

	retry_cnt = 3;
retry:
	n_cli = 0;
	cl_int = _search_cli_idle();
	if ( cl_int == 0 ) {
		n_cli = 1;
		cl_int = _new_cli();
	}

	cmd = d_alloc(1000);
	sprintf(cmd,"<Lock type=\"%s\"> %s </Lock>",
		type,n_string(std_cm,lock_dir));
	l_cmd = nl_copy_str(std_cm,cmd);
	d_f_ree(cmd);
	cl_int->ref ++;

	unlock_task(call_lock_lock,"new_clt");
	rq_ret = remote_query(cl_int->xli,gblisp_top_env1,l_cmd,0);

	d_f_ree(l_cmd);
	if ( get_type(rq_ret) == XLT_ERROR ) {
		lock_task(call_lock_lock);
		cl_int->ref --;
		cl_int->status = LIS_ERROR;
		close_interpreter(cl_int->xli);
		if ( n_cli )
			_free_cli(cl_int);
		retry_cnt --;
		if ( retry_cnt <= 0 ) {
			_cl_delete_thread(ret,tid);
			_free_clt(ret);
			return 0;
		}
		goto retry;
	}
	lock_task(call_lock_lock);
	cl_int->ref --;

	cl_int->ref ++;
	ret->cli = cl_int;
	cl_int->call_time = get_xltime();
	wakeup_task((int)&lt_list);
	return ret;
}

CALL_LOCK_TABLE *
_search_clt(L_CHAR * dir)
{
CALL_LOCK_TABLE * ret;
int len_dir,len_ret;
	len_dir = l_strlen(dir);
	for ( ret = lt_list ; ret ; ret = ret->next ) {
		len_ret = l_strlen(ret->name);
		if ( len_ret < len_dir ) {
			if ( memcmp(ret->name,dir,
					len_ret*sizeof(L_CHAR))
					== 0 )
				return ret;
		}
		else	if ( memcmp(ret->name,dir,
					len_dir*sizeof(L_CHAR))
					== 0 )
				return ret;
	}
	return 0;
}



CALL_LOCK_DESCRIPTER
_call_lock(L_CHAR * lock_dir,int type)
{
CALL_LOCK_TABLE * clt;
int tid;
CALL_LOCK_DESCRIPTER d;

	tid = get_tid();
retry:
	clt = _search_clt(lock_dir);
	if ( clt ) {
		if ( clt->cli == 0 )
			goto sleep;
		clt->cli->call_time = get_xltime();

		if ( clt->tid == 0 && clt->threads == 0 ) {
			/* no locked */
			er_panic("_call_lock");
		}
		else if ( clt->tid == 0 ) {
			/* read locked */
			if ( type == CLT_WRITE_LOCK )
				goto sleep;
			if ( l_strcmp(clt->name,lock_dir) == 0 ) {
				_cl_insert_thread(clt,tid);
				d.d = clt;
				d.tid = tid;
				return d;
			}
		}
		else {
			/* write locked */
			if ( clt->tid != tid )
				goto sleep;
			if ( l_strcmp(clt->name,lock_dir) == 0 ) {
				_cl_insert_thread(clt,tid);
				d.d = clt;
				d.tid = tid;
				return d;
			}
		}
	}
	clt = _new_clt(lock_dir,type,tid);
	d.d = clt;
	d.tid = tid;
	return d;
sleep:
	sleep_task((int)&lt_list,call_lock_lock);
	lock_task(call_lock_lock);
	goto retry;
}



int
_call_unlock(CALL_LOCK_DESCRIPTER d)
{
CALL_LOCK_TABLE * clt;
	clt = _get_clt_from_desc(d);
	if ( clt == 0 )
		return -1;
	if ( _cl_delete_thread(clt,d.tid) < 0 )
		return -1;
	clt->cli->call_time = get_xltime();
	if ( clt->threads == 0 ) {
		wakeup_task((int)&lt_list);
		_free_clt(clt);
	}
	return 0;
}

CALL_LOCK_DESCRIPTER
call_lock(L_CHAR * lock_dir,int type)
{
CALL_LOCK_DESCRIPTER ret;
	lock_task(call_lock_lock);
	ret = _call_lock(lock_dir,type);
	unlock_task(call_lock_lock,"call_lock");
	return ret;
}

int
call_unlock(CALL_LOCK_DESCRIPTER d)
{
int ret;
	lock_task(call_lock_lock);
	ret = _call_unlock(d);
	unlock_task(call_lock_lock,"call_unlock");
	return ret;
}


int
cl_error_check(CALL_LOCK_DESCRIPTER d)
{
	if ( d.d == 0 )
		return -1;
	return 0;
}

XL_SEXP *
get_cl_error(XL_SEXP * ss,char * func)
{
	return get_error(
		ss->h.file,
		ss->h.line,
		XLE_PROTO_INV_PARAM,
		l_string(std_cm,func),
		n_get_string("lock operation error"));
}


CALL_LOCK_DESCRIPTER
cl_invalid()
{
CALL_LOCK_DESCRIPTER ret;
	ret.d = 0;
	return ret;
}



void
_call_lock_tick()
{
CALL_LOCK_INTERPRETER * cli, * cli2;
unsigned int expire_time;
int cnt;
	cnt = 0;
	expire_time = get_xltime() - CL_LIVE_TIME;
	for ( cli = li_list ; cli ; cli = cli2) {
		cnt ++;
		cli2 = cli->next;
		if ( cli->status == LIS_CALL )
			continue;
		if ( cli->ref )
			continue;
		if ( cli->call_time > expire_time )
			continue;
		close_interpreter(cli->xli);
		_free_cli(cli);
	}
}

void
call_lock_tick()
{
	lock_task(call_lock_lock);
	_call_lock_tick();
	unlock_task(call_lock_lock,"call_lock_tick");
}

