/**********************************************************************
 
	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	<string.h>
#include	"task.h"
#include	"memory_debug.h"
#include	"lock_level.h"
#include	"queue.h"
#include	"utils.h"

SEM	queue_lock,queue_lock_for_gc;
SEM	queue_lock_high,queue_lock_for_gc_high;
SYS_QUEUE * q_list;
int queue_lock_for_gc_high_flag;

void
init_queue()
{
	queue_lock = new_lock(LL_QUEUE);
	queue_lock_for_gc = new_lock(LL_QUEUE_GC);
	queue_lock_high = new_lock(LL_QUEUE_HIGH);
	queue_lock_for_gc_high = new_lock(LL_QUEUE_GC_HIGH);
}


void
gc_queue_lock()
{
	lock_task(queue_lock_for_gc);
	if ( queue_lock_for_gc_high_flag == 0 )
		er_panic("required gc_queue_lock_high");
}

void
gc_queue_lock_high()
{
	lock_task(queue_lock_for_gc_high);
	queue_lock_for_gc_high_flag = 1;
}

void
gc_queue_unlock()
{
	queue_lock_for_gc_high_flag = 0;
	unlock_task(queue_lock_for_gc,"qc_queue_unlock");
	unlock_task(queue_lock_for_gc_high,"qc_queue_unlock");
}

void
gc_queue()
{
SYS_QUEUE * q;
Q_HEADER * n;

	for ( q = q_list ; q ; q = q->next ) {
		if ( q->gc_func == 0 )
			continue;
		for ( n = q->head ; n ; n = n->next )
			(*q->gc_func)(n);
	}
}

void
setup_queue(SYS_QUEUE * q)
{
	lock_task(queue_lock_for_gc);
	if ( q->flags & QF_HIGH ) {
		if ( q->gc_func )
			q->target_lock = &queue_lock_for_gc_high;
		else	q->target_lock = &queue_lock_high;
	}
	else {
		if ( q->gc_func )
			q->target_lock = &queue_lock_for_gc;
		else	q->target_lock = &queue_lock;
	}
	q->next = q_list;
	q_list = q;
	unlock_task(queue_lock_for_gc,"new_queue");
}


int
_insert_key_list(SYS_QUEUE * q,L_CHAR * key,int wait_flag)
{
KEY_LIST * k;
	if ( key == 0 )
		return 0;
	for ( k = q->keylist ; k ; k = k->next )
		if ( l_strcmp(k->key,key) == 0 ) {
			if ( wait_flag < Q_WAIT_FORCE_INSERT &&
			    		q->key_limit && 
					k->cnt >= q->key_limit )
				return -1;
			k->cnt ++;
			return 0;
		}
	k = d_alloc(sizeof(*k));

	k->key = ll_copy_str(key);
	k->cnt = 1;
	k->task_asign = 0;

	k->next = q->keylist;
	q->keylist = k;

	if ( q->key_func )
		create_task(q->key_func,(int)q,q->pri);
	return 0;
}


void
_delete_key_list(SYS_QUEUE * q,L_CHAR * key)
{
KEY_LIST ** kp,* k;
	if ( key == 0 )
		return;
	for ( kp = &q->keylist ; *kp ; kp = &(*kp)->next ) {
		k = *kp;
		if ( l_strcmp(k->key,key) )
			continue;
		k->cnt --;
	}
}

int
_insert_queue(SYS_QUEUE * q,void * _n,int wait_flag)
{
Q_HEADER * n;
	n = _n;
retry:
	if ( wait_flag < Q_WAIT_FORCE_INSERT &&
			q->total_limit && q->total_cnt >= q->total_limit )
		goto sleep;
	if ( _insert_key_list(q,n->key,wait_flag) < 0 )
		goto sleep;
	if ( n->ins )
		er_panic("_insert_queue");
	n->ins = q;
	q->total_cnt ++;
	switch ( q->flags & QFM_DIRECT ) {
	case QF_STACK:
		n->next = q->head;
		q->head = n;
		break;
	case QF_FIFO:
		n->next = 0;
		if ( q->head == 0 )
			q->head = q->tail = n;
		else {
			q->tail->next = n;
			q->tail = n;
		}
		break;
	default:
		er_panic("insert_queue");
	}
	wakeup_task((int)q);
	return 0;
sleep:
	if ( wait_flag == 0 )
		return -1;
	sleep_task((int)q,*q->target_lock);
	lock_task(*q->target_lock);
	goto retry;
}

int
insert_queue(SYS_QUEUE * q,void * n,int wait_flag)
{
int ret;
	lock_task(*q->target_lock);
	ret = _insert_queue(q,n,wait_flag);
	unlock_task(*q->target_lock,"insert_queue");
	return ret;
}


void *
_delete_queue_simple(SYS_QUEUE * q,int wait_flag)
{
Q_HEADER * ret;
int sleep_tim;

retry:
	if ( q->head == 0 ) {
		if ( wait_flag == 0 )
			return 0;
		if ( wait_flag < 0 ) {
			new_timeout((int)q,-wait_flag);
			sleep_tim = get_xltime();
		}
		sleep_task((int)q,*q->target_lock);
		if ( wait_flag < 0 ) {
			del_timeout((int)q);
			wait_flag = wait_flag +
				get_xltime() - sleep_tim;
			if ( wait_flag > 0 )
				wait_flag = 0;
		}
		lock_task(*q->target_lock);
		goto retry;
	}
	ret = q->head;

	if ( ret && q->gc_get )
		(*q->gc_get)(ret);

	q->head = ret->next;
	if ( q->head == 0 )
		q->tail = 0;
	ret->next = 0;
	ret->ins = 0;

	q->total_cnt --;

	_delete_key_list(q,ret->key);

	wakeup_task((int)q);

	return ret;
}

void
dq_tick(SYS_QUEUE * q)
{
	wakeup_task((int)q);
}

void *
_delete_queue_cond(SYS_QUEUE * q,int (*cond)(),void * work,int wait_flag)
{
Q_HEADER * ret, ** retp, * n;
int sleep_tim;

retry:
	for ( retp = &q->head ; *retp ; retp = &(*retp)->next )
		if ( (*cond)(q,*retp,work) == 0 )
			goto ok;
	if ( wait_flag == 0 )
		return 0;
	if ( q->head )
		new_tick(dq_tick,-1,(int)q);
	if ( wait_flag < 0 ) {
		new_timeout((int)q,-wait_flag);
		sleep_tim = get_xltime();
	}
	sleep_task((int)q,*q->target_lock);
	if ( wait_flag < 0 ) {
		del_timeout((int)q);
		wait_flag = wait_flag + (get_xltime() - sleep_tim);
		if ( wait_flag > 0 )
			wait_flag = 0;
	}
	lock_task(*q->target_lock);
	del_tick_with_data(dq_tick,(int)q);
	goto retry;

ok:
	ret = *retp;

	if ( ret && q->gc_get )
		(*q->gc_get)(ret);

	*retp = ret->next;
	if ( q->tail == ret ) {
		if ( q->head == 0 )
			q->tail = 0;
		else {
			for ( n = q->head ; n->next ; n = n->next );
			q->tail = n;
		}
	}
	ret->next = 0;
	ret->ins = 0;

	q->total_cnt --;

	_delete_key_list(q,ret->key);

	wakeup_task((int)q);

	return ret;
}

void *
delete_queue(SYS_QUEUE * q,int (*cond)(),void * work,int wait_flag)
{
Q_HEADER * ret;
	lock_task(*q->target_lock);
	if ( cond )
		ret = _delete_queue_cond(q,cond,work,wait_flag);
	else	ret = _delete_queue_simple(q,wait_flag);
	unlock_task(*q->target_lock,"delete_queue");
	return ret;
}


int
_check_queue(SYS_QUEUE * q,int (*cond)(),void * work)
{
Q_HEADER ** retp, * n;
int ret;

	ret = 0;
	for ( retp = &q->head ; *retp ; retp = &(*retp)->next ) {
		if ( cond == 0 ) {
			ret ++;
			continue;
		}
		if ( (*cond)(q,*retp,work) == 0 )
			ret ++;
	}
	return ret;
}


int
check_queue(SYS_QUEUE * q,int (*cond)(),void * work)
{
int ret;
	lock_task(*q->target_lock);
	ret = _check_queue(q,cond,work);
	unlock_task(*q->target_lock,"check_queue");
	return ret;
}



L_CHAR *
_touch_qkey(SYS_QUEUE * q)
{
KEY_LIST * k;
	for ( k = q->keylist ; k ; k = k->next )
		if ( k->task_asign == 0 ) {
			k->task_asign = 1;
			return ll_copy_str(k->key);
		}
	return 0;
}

L_CHAR *
touch_qkey(SYS_QUEUE * q)
{
L_CHAR * ret;
	lock_task(*q->target_lock);
	ret = _touch_qkey(q);
	unlock_task(*q->target_lock,"get_queue_key");
	return ret;
}

void
_release_qkey(SYS_QUEUE * q,L_CHAR * key)
{
KEY_LIST * k, ** kp;
	for ( kp = &q->keylist ; *kp ; kp = &(*kp)->next ) {
		k = *kp;
		if ( l_strcmp(k->key,key) )
			continue;
		if ( k->cnt ) {
			k->task_asign = 0;
			create_task(q->key_func,(int)q,q->pri);
		}
		else {
			*kp = k->next;
			d_f_ree(k->key);
			d_f_ree(k);
		}
		return;
	}
	ss_printf("invalid key %ls\n",key);
	er_panic("key");
}

void
release_qkey(SYS_QUEUE * q,L_CHAR * key)
{
	lock_task(*q->target_lock);
	_release_qkey(q,key);
	unlock_task(*q->target_lock,"release_qkey");
}


int
sq_key_cond(SYS_QUEUE * q,void * n,void * work)
{
L_CHAR * key;
Q_HEADER * _n;
	_n = n;
	key = work;
	if ( _n->key == 0 )
		return -1;
	if ( l_strcmp(_n->key,key) == 0 )
		return 0;
	return -1;
}


L_CHAR * 
get_server_key(URL * u)
{
char * key;
L_CHAR * ret;
	key = d_alloc(l_strlen(u->server)*4 + 20);
	if ( u->server && u->proto ) 
		sprintf(key,"%s://%s:%i",
			n_string(std_cm,u->proto),
			n_string(std_cm,u->server),
			u->port);
	else if ( u->proto == 0 && u->server == 0 )
		sprintf(key,"null");
	else if ( u->proto == 0 )
		sprintf(key,"xlp://%s:%i",
			n_string(std_cm,u->server),
			u->port);
	else	sprintf(key,"%s://server:%i",
			n_string(std_cm,u->proto),
			u->port);
	ret = nl_copy_str(std_cm,key);
	d_f_ree(key);
	return ret;
}



void *
new_queue_node(int size)
{
Q_HEADER * h;
	h = d_alloc(size);
	memset(h,0,sizeof(*h));
	return h;
}

int
_get_active_que_thread(SYS_QUEUE * q)
{
KEY_LIST * k;
int ret;
	ret = 0;
	for ( k = q->keylist ; k ; k = k->next )
		if ( k->task_asign )
			ret ++;
	return ret;
}

int
get_active_que_thread(SYS_QUEUE * q)
{
int ret;
	lock_task(*q->target_lock);
	ret = _get_active_que_thread(q);
	unlock_task(*q->target_lock,"release_qkey");
	return ret;
}

