#include <epp.h>
#include "epp_internals.h"

static epp_event_descriptor	__epp_event_descriptors[EPP_MAX_EVENTS];
static epp_event_descriptor	*__epp_free_event_descriptors;	EPP_LOCKABLE(__epp_free_event_descriptors);
								EPP_WAITABLE(__epp_free_event_descriptors);

static epp_event_descriptor	*__epp_event_queues[EPP_MAX_PRIORITY+1];
								EPP_LOCKABLE(__epp_event_queues);
								EPP_WAITABLE(__epp_event_queues);

static unsigned char		epp_unblock_request;

__attribute__((__constructor__)) static int __epp_events_init(void) {
    unsigned int i = EPP_MAX_EVENTS;
    epp_event_descriptor *e;

    e = __epp_free_event_descriptors = __epp_event_descriptors;
    epp_unblock_request = 0;

    EPP_LOCK_INIT(__epp_free_event_descriptors);
    EPP_WAIT_INIT(__epp_free_event_descriptors);

    EPP_LOCK_INIT(__epp_event_queues);
    EPP_WAIT_INIT(__epp_event_queues);

    while(i--) {
	e -> next = i ? e + 1 :  NULL;
	e++;
    }
    
    return(0);
}

__attribute__((__destructor__)) static void __epp_events_shutdown(void) {
    EPP_SIGNAL(__epp_event_queues);
    EPP_WAIT_SHUTDOWN(__epp_event_queues);
    EPP_LOCK_SHUTDOWN(__epp_event_queues);

    EPP_SIGNAL(__epp_free_event_descriptors);
    EPP_WAIT_SHUTDOWN(__epp_free_event_descriptors);
    EPP_LOCK_SHUTDOWN(__epp_free_event_descriptors);
}

static epp_event_descriptor *__epp_alloc_event_descriptor(void) {
    epp_event_descriptor *e;
    
    EPP_LOCK(__epp_free_event_descriptors);
    
    while(!__epp_free_event_descriptors)
	EPP_WAIT(__epp_free_event_descriptors);

    e = __epp_free_event_descriptors;
    __epp_free_event_descriptors = e -> next;

    EPP_UNLOCK(__epp_free_event_descriptors);

    return (e);
}

static void __epp_free_event_descriptor(epp_event_descriptor *e) {
    EPP_LOCK(__epp_free_event_descriptors);
    
    e -> next = __epp_free_event_descriptors;
    __epp_free_event_descriptors = e;
    
    EPP_UNLOCK(__epp_free_event_descriptors);
    EPP_SIGNAL(__epp_free_event_descriptors);
}

static void __epp_process_event(epp_event_descriptor *e) {
    av_call(e -> open_query_call);
    if (e -> retval != PL_TRUE) {
	if (e -> notify) e -> notify(e -> retval, e -> userdata);
	return;
    }

    while(e -> retval == PL_TRUE) {
	e -> retval = pl_next_solution(&(e -> query));
	if (e -> notify) {
	    if (e -> notify(e -> retval, e -> userdata) == EPP_CLOSE_QUERY)
		break;
	}
    }

    pl_close_query(&(e -> query));
}

static int __epp_generic_callback(int retval, void *userdata) {
    epp_query *q = (epp_query*)userdata;
    
    EPP_OBJECT_LOCK(q);

    q -> retval = retval;

    EPP_OBJECT_SIGNAL(q);
    EPP_OBJECT_WAIT(q);
    
    retval = q -> retval;

    EPP_OBJECT_UNLOCK(q);

    return(retval);
}

unsigned int EPP_Process(unsigned int events, unsigned int min, unsigned int max, unsigned int flags) {
    unsigned int i = 0;
    unsigned int p;
    
    if (!(flags & EPP_NO_BLOCK))
	epp_unblock_request = 0;

    while (events--) {
	epp_event_descriptor *e = NULL;

	EPP_LOCK(__epp_event_queues);

	for (p = min; p <= max; p++) {
	    e = NULL;

	    while (__epp_event_queues[p]) {
		e = __epp_event_queues[p];
		__epp_event_queues[p] = e -> next;
		break;
	    }
	    
	    if (e != NULL)
		break;
	}

	EPP_UNLOCK(__epp_event_queues);
	EPP_SIGNAL(__epp_event_queues);

	if (e) {
	    __epp_process_event(e);
	    __epp_free_event_descriptor(e);
	} else if (!(flags & EPP_NO_BLOCK)) {
	    EPP_LOCK(__epp_event_queues);
	    EPP_WAIT(__epp_event_queues);
	    EPP_UNLOCK(__epp_event_queues);
	    
	    if (epp_unblock_request) {
		i = 0;
		break;
	    }
	} else {
	    break;
	}
    }

    return(i);
}

void EPP_ProcessUnblockAll(void) {
	epp_unblock_request = 1;
	EPP_SIGNAL(__epp_event_queues);
}

int epp_vacall(epp_notify_function callback, void *userdata, unsigned int priority, const char *predicate, const char *params_spec, va_list args) {
    epp_event_descriptor *e = __epp_alloc_event_descriptor();
    epp_event_descriptor *f;
    int retval = PL_TRUE;

    av_start_int(e -> open_query_call, pl_open_query, &(e -> retval));
    av_ptr(e -> open_query_call, pl_query*, &(e -> query));
    av_ptr(e -> open_query_call, const char*, predicate);

    if (__epp_make_av_args(&(e -> open_query_call), params_spec, args)) {
	__epp_free_event_descriptor(e);
	return(PL_ERROR_PAL);
    }

    e -> userdata	= userdata;
    e -> notify		= callback;
    e -> next		= NULL;

    EPP_LOCK(__epp_event_queues);
    
    if (!__epp_event_queues[priority]) {
	__epp_event_queues[priority] = e;
    } else {
	f = __epp_event_queues[priority];
	while (f -> next) f = f -> next;
	f -> next = e;
    }

    EPP_UNLOCK(__epp_event_queues);
    EPP_SIGNAL(__epp_event_queues);

    return(retval);
}

int epp_acall(epp_notify_function callback, void *userdata, unsigned int priority, const char *predicate, const char *params_spec, ...) {
    int retval;
    va_list vl;

    va_start(vl, params_spec);
    retval = epp_vacall(callback, userdata, priority, predicate, params_spec, vl);
    va_end(vl);
    
    return(retval);
}

int epp_vopen_query(epp_query *query, unsigned int priority, const char *predicate, const char *params_spec, va_list args) {
    EPP_OBJECT_LOCK_INIT(query);
    EPP_OBJECT_WAIT_INIT(query);

    EPP_OBJECT_LOCK(query);

    if (priority == (unsigned int)EPP_DIRECT_CALL) {
	query -> flags = DIRECT;
	EPP_OBJECT_UNLOCK(query);
	return(pl_vopen_query(&(query -> plquery), predicate, params_spec, args));
    }

    query -> flags = FIRST_CALL;
    query -> retval = 0xDEADBEEF;

    EPP_OBJECT_UNLOCK(query);
    return(epp_vacall(__epp_generic_callback, query, priority, predicate, params_spec, args));
}

int epp_open_query(epp_query *query, unsigned int priority, const char *predicate, const char *params_spec, ...) {
    int retval;
    va_list vl;

    va_start(vl, params_spec);
    retval = epp_vopen_query(query, priority, predicate, params_spec, vl);
    va_end(vl);
    
    return(retval);
}

int epp_next_solution(epp_query *query) {
    int retval = PL_ERROR_PROLOG;

    EPP_OBJECT_LOCK(query);

    if (query -> flags == DIRECT)
	retval = pl_next_solution(&(query -> plquery));
    
    if (query -> flags != DIRECT && query -> flags != NO_MORE_CALLS) {
	if (query -> flags == FIRST_CALL) {
	    if (query -> retval == 0xDEADBEEF)
		EPP_OBJECT_WAIT(query);
	
	    query -> flags = NONE;
	    retval = query -> retval;
	} else {
	    query -> retval = EPP_NEXT_SOLUTION;
	    EPP_OBJECT_SIGNAL(query);
	    EPP_OBJECT_WAIT(query);
	    retval = query -> retval;
	}

	if (retval != PL_TRUE) {
	    query -> flags = NO_MORE_CALLS;
	    query -> retval = EPP_CLOSE_QUERY;
	    EPP_OBJECT_SIGNAL(query);
	}
    }
    
    EPP_OBJECT_UNLOCK(query);
    return(retval);
}

void epp_close_query(epp_query *query) {
    EPP_OBJECT_LOCK(query);

    if (query -> flags == DIRECT)
	pl_close_query(&(query -> plquery));

    if (query -> flags == FIRST_CALL && query -> retval == 0xDEADBEEF)
	EPP_OBJECT_WAIT(query);

    if (query -> flags != DIRECT && query -> flags != NO_MORE_CALLS) {
	query -> flags = NO_MORE_CALLS;
	query -> retval = EPP_CLOSE_QUERY;
	EPP_OBJECT_SIGNAL(query);
    }

    EPP_OBJECT_UNLOCK(query);

    EPP_OBJECT_WAIT_SHUTDOWN(query);
    EPP_OBJECT_LOCK_SHUTDOWN(query);
}

int epp_vcall(unsigned int priority, const char *predicate, const char *params_spec, va_list args) {
    epp_query q;
    int error;
    
    error = epp_vopen_query(&q, priority, predicate, params_spec, args);
    if (error != PL_TRUE) return(error);

    error = epp_next_solution(&q);
    epp_close_query(&q);
    return(error);
}

int epp_call(unsigned int priority, const char *predicate, const char *params_spec, ...) {
    int retval;
    va_list vl;

    va_start(vl, params_spec);
    retval = epp_vcall(priority, predicate, params_spec, vl);
    va_end(vl);
    
    return(retval);
}
