#ifndef _EVENT_DEBUG_H_
#define _EVENT_DEBUG_H_

#ifdef EVENT_LIB_DEBUG

#include "dhash.h"

#define print(fmt,...)	\
    PerlIO_printf(PerlIO_stderr(), fmt, __VA_ARGS__)
#define print_(fmt,...)	\
{   \
    print("________________________________________________________________________\n", NULL);\
    print(fmt"\n", __VA_ARGS__);	\
    print("------------------------------------------------------------------------\n", NULL);\
}


#define ENV(var)    (getenv(var) && atoi(getenv(var)))

#define DEBUG_warn(...)  \
    if (ENV("EVENT_LIB_DEBUG_DESTROY"))   \
	print_(__VA_ARGS__)

dhash_t EVENTS	    = { 0, 0, NULL };	/* record pending events */
dhash_t ALLO	    = { 0, 0, NULL };	/* record allocation and destruction of events */

int EVENT_NEW_COUNT = 0, 
    SIGNAL_NEW_COUNT = 0,
    TIMER_NEW_COUNT = 0;

#define DEBUG_init_count(...)	EVENT_NEW_COUNT = SIGNAL_NEW_COUNT = TIMER_NEW_COUNT = 0
#define DEBUG_inc_count(var)	var++

void DEBUG_init_pending (pTHX) {
    if (ENV("EVENT_LIB_DEBUG_PENDING")) {
	dhash_init(&EVENTS);
    }
    if (ENV("EVENT_LIB_DEBUG_ALLOCS") && ALLO.size == 0) {
	dhash_init(&ALLO);
    }
}

#define DUMP_one_event(i) \
{   \
        print("%i:\n", i);  \
	print("   flags: %i\n", EVENTS.ary[i].flags);	\
	print("   event: 0x%p\n", EVENTS.ary[i].ev);	\
}

#define DUMP_dhash(ev)	\
{			\
    if (ENV("EVENT_LIB_DEBUG_DHASH")) {\
    int i;		\
    print("__________\n", NULL);\
    print("%s for 0x%p\n", __FUNCTION__, ev);	\
    print("size:%i count:%i\n", EVENTS.size, EVENTS.count);\
    for (i = 0; i < EVENTS.size; i++)	\
	DUMP_one_event(i);		\
    print("----------\n", NULL);\
    }\
}

void DEBUG_record_event (pTHX_ SV *ev) {
    register int i;
    struct event_args *args = (struct event_args*)SvIV(SvRV(ev));
    dhash_val_t val = EMPTY;

    if (!ENV("EVENT_LIB_DEBUG_PENDING")) {
	return;
    }
    
    DUMP_dhash(args);

    /* barf when an event to be added already is in the hash. 
     * Exception is when in callback to allow something like this:
     *	 sub handler { my $ev; $ev->add }
     */
    if (!IN_CALLBACK)
	assert(dhash_find(&EVENTS, args) == NULL);
    
    if (dhash_find(&EVENTS, args)) {
	print("not adding element 0x%p again\n", args);
	goto done;
    }
    
    /* prepare the type of event */
    if (sv_derived_from(ev, "Event::Lib::timer"))
	val.flags |= EV_TIMEOUT;
    else if (sv_derived_from(ev, "Event::Lib::signal"))
	val.flags |= EV_SIGNAL|EV_PERSIST;
    else /* fh-event */
	val.flags = args->ev.ev_events;
   
    val.ev = args;
    dhash_store(&EVENTS, val);
    
done:
    DUMP_dhash(args);
}

void DEBUG_record_allo (pTHX_ SV *ev) {
    register int i;
    struct event_args *args = (struct event_args*)SvIV(SvRV(ev));
    dhash_val_t val = EMPTY;

    if (!ENV("EVENT_LIB_DEBUG_ALLOCS")) {
	return;
    }
    
    /* barf when an event to be added already is in the hash. 
     * Exception is when in callback to allow something like this:
     *	 sub handler { my $ev; $ev->add }
     */
    if (!IN_CALLBACK)
	assert(dhash_find(&ALLO, args) == NULL);
    
    if (dhash_find(&ALLO, args)) {
	print("not adding element 0x%p again\n", args);
	return;
    }
    
    /* prepare the type of event */
    if (sv_derived_from(ev, "Event::Lib::timer"))
	val.flags |= EV_TIMEOUT;
    else if (sv_derived_from(ev, "Event::Lib::signal"))
	val.flags |= EV_SIGNAL|EV_PERSIST;
    else /* fh-event */
	val.flags = args->evtype;
   
    val.ev = args;
    dhash_store(&ALLO, val);
}

void DEBUG_delete_event (pTHX_ SV *ev) {
    struct event_args *args = (struct event_args*)SvIV(SvRV(ev));
    SV *recref;

    if (!ENV("EVENT_LIB_DEBUG_PENDING")) {
	return;
    }
  
    DUMP_dhash(args);

    /* an event not yet set has never been added to EVENT 
     * so don't even try to delete it */
    if (!EvEVENT_SET(args) && !dhash_find(&EVENTS, args)) {
	print("Attempt to delete non-set event SV = 0x%x with event at 0x%x caught\n", ev, args);
	return;
    }
    
    assert(dhash_find(&EVENTS, args));
   
    dhash_delete(&EVENTS, args);
    
    DUMP_dhash(args);
}

void DEBUG_delete_allo (pTHX_ SV *ev) {
    struct event_args *args = (struct event_args*)SvIV(SvRV(ev));
    SV *recref;

    if (!ENV("EVENT_LIB_DEBUG_ALLOCS")) {
	return;
    }
  
    assert(dhash_find(&ALLO, args));
    dhash_delete(&ALLO, args);
}
 
#define DEBUG_get_pending_events(...)	\
    int i, j;				\
    if (!ENV("EVENT_LIB_DEBUG_PENDING")) { \
	XSRETURN_EMPTY;			\
    }					\
    EXTEND(SP, EVENTS.count);		\
    for (i = 0, j = 0; i <= EVENTS.size; i++) { \
	if (!EVENTS.ary[i].ev)		\
	    continue;			\
	ST(j) = (SV*)EVENTS.ary[i].ev->ev.ev_arg;  \
	j++;				\
    }					\
    XSRETURN(EVENTS.count)		\

void DEBUG_dump_pending_events (pTHX) {
    
    register int i;

    if (!ENV("EVENT_LIB_DEBUG_PENDING")) {
	return;
    }

    if (EVENTS.count == 0) {
	print("No pending events\n", NULL);
	return;
    }
    
    for (i = 0; i < EVENTS.size; i++) {
	int type;
	struct event_args *ev;
	
	if (!EVENTS.ary[i].ev)
	    continue;

	ev = EVENTS.ary[i].ev;
	type = EVENTS.ary[i].flags;

	print("EV = 0x%p\n", ev);
	print("   type   = ", NULL);
	if (type & EV_PERSIST) {
	    print("EV_PERSIST | ", NULL);
	    type &= ~EV_PERSIST;
	}
	if (type & EV_TIMEOUT)	    print("EV_TIMEOUT", NULL);
	else if (type & EV_SIGNAL)  print("EV_SIGNAL", NULL);
	else if (type & EV_READ)    print("EV_READ", NULL);
	else if (type & EV_WRITE)   print("EV_WRITE", NULL);
	else if (type & EV_READ &&
		 type & EV_WRITE)   print("EV_READ | EV_WRITE", NULL);
	if (type & 0x20)	    print(" | EVf_EVENT_TRACED", NULL);
	print("\n", NULL);
	print("   args   = %i\n", ev->num);
	print("   refcnt = %i\n", SvREFCNT((SV*)ev->ev.ev_arg));
	print("   cback  = %s\n", ev->cbname);
	if (type & (EV_READ|EV_WRITE)) 
	    print("   fhid   = %s from %s:%i (fd=%i)\n", 
		    GvNAME((GV*)SvRV(ev->io)), 
		    GvFILE((GV*)SvRV(ev->io)),
		    GvLINE((GV*)SvRV(ev->io)),
		    ev->ev.ev_fd);
    }
    print("----------------------------------------------------------------------------\n", NULL);
    print("total: %i events still pending\n", EVENTS.count);
}	

void DEBUG_dump_allocated (pTHX) {
    
    register int i;

    if (!ENV("EVENT_LIB_DEBUG_ALLOCS")) {
	return;
    }

    if (ALLO.count == 0) {
	print("No allocated events\n", NULL);
	return;
    }
    
    for (i = 0; i < ALLO.size; i++) {
	int type;
	struct event_args *ev;
	
	if (!ALLO.ary[i].ev)
	    continue;

	ev = ALLO.ary[i].ev;
	type = ALLO.ary[i].flags;

	print("EV = 0x%p\n", ev);
	print("   type   = ", NULL);
	if (type & EV_PERSIST) {
	    print("EV_PERSIST | ", NULL);
	    type &= ~EV_PERSIST;
	}
	if (type & EV_TIMEOUT)	    print("EV_TIMEOUT", NULL);
	else if (type & EV_SIGNAL)  print("EV_SIGNAL", NULL);
	else if (type & EV_READ)    print("EV_READ", NULL);
	else if (type & EV_WRITE)   print("EV_WRITE", NULL);
	else if (type & EV_READ &&
		 type & EV_WRITE)   print("EV_READ | EV_WRITE", NULL);
	if (type & 0x20)	    print(" | EVf_EVENT_TRACED", NULL);
	print("\n", NULL);
	print("   args   = %i\n", ev->num);
	print("   cback  = %s\n", ev->cbname);
	print("   loc    = %s\n", SvPV_nolen(ev->loc));
	if (type & (EV_READ|EV_WRITE)) 
	    print("   fhid   = %s from %s:%i (fd=%i)\n", 
		    GvNAME((GV*)SvRV(ev->io)), 
		    GvFILE((GV*)SvRV(ev->io)),
		    GvLINE((GV*)SvRV(ev->io)),
		    ev->ev.ev_fd);
    }
    print("----------------------------------------------------------------------------\n", NULL);
    print("total: %i events still allocated\n", ALLO.count);
}

#define DEBUG_trace(e)		    \
{				    \
    if (EvEVENT_TRACED(e)) {	    \
	print("________________________________________________________________________\n", NULL);  \
	print("EV = 0x%p (%s) (%s)\n  (from %s)\n  touched at %s (%i)\n  (by %s:%d)\n", \
		 e, e->type, e->cbname, SvPV_nolen(e->loc), __FUNCTION__, __LINE__,	\
		 CopFILE(PL_curcop) ? CopFILE(PL_curcop) : "unknown", \
		 CopLINE(PL_curcop) ? CopLINE(PL_curcop) : -1);	\
	if (EvEVENT_SET(e))	{ \
	    sv_dump((SV*)e->ev.ev_arg); \
	    if (event_initialized(&e->ev) && SvOK((SV*)e->ev.ev_arg))	    \
		sv_dump((SV*)SvRV((SV*)e->ev.ev_arg));	\
	}\
	print ("------------------------------------------------------------------------\n", NULL);  \
    } \
}
   
void DEBUG_store_location (pTHX_ struct event_args* args) {
    New(0, args->cbname, strlen(HvNAME(GvSTASH(CvGV(args->func)))) + 
			 strlen("::") + 
			 strlen(GvNAME(CvGV(args->func))) + 1, char);
    sprintf(args->cbname, "%s::%s", HvNAME(GvSTASH(CvGV(args->func))), GvNAME(CvGV(args->func)));
    if (CopFILE(PL_curcop) && CopLINE(PL_curcop))
	args->loc = newSVpvf("%s:%d", CopFILE(PL_curcop), CopLINE(PL_curcop));
    else
	args->loc = newSVpv("unknown location", 0);
}

#else
#   define DEBUG_init_count(...)
#   define DEBUG_inc_count(...)
#   define DEBUG_warn(...)
#   define DEBUG_enter_callback(...)
#   define DEBUG_leave_callback(...)
#   define DEBUG_init_pending(...)
#   define DEBUG_record_event(...)
#   define DEBUG_record_allo(...)
#   define DEBUG_delete_event(...)
#   define DEBUG_delete_allo(...)
#   define DEBUG_get_pending_events(...)
#   define DEBUG_dump_pending_events(...)
#   define DEBUG_dump_allocated(...)
#   define DEBUG_trace(...)
#   define DEBUG_store_location(...)
#   define EVENT_NEW_COUNT  -1
#   define SIGNAL_NEW_COUNT -1
#   define TIMER_NEW_COUNT  -1
#endif

#endif	/* _EVENT_DEBUG_H_ */