/*
    select.c - main application loop
    Copyright (C) 2002-2022  Ladislav Vaiz <ok1zia@nagano.cz>
    and authors of web browser Links 0.96

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/


#include <eprintf.h>
#include <zerror.h>
#include <zselect.h>
#include <zsock.h>
#include <zdebug.h>
#include <zlist.h>
#include <zthread.h>
#include <ztime.h>

#include <errno.h>
#define _USE_MATH_DEFINES // for MSVC
#include <math.h>
#include <stdarg.h>
#include <string.h>
#ifdef Z_HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef Z_HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef Z_HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#include <glib.h>


#ifdef Z_MSC
#include <crtdbg.h>
#endif

struct timeval start;

struct zselect_fd {	  
	int sock;
    void (*read_func)(void *);
	char *read_fname;
    void (*write_func)(void *);
	char *write_fname;
    void (*error_func)(void *);
	char *error_fname;
    void *arg;
#ifdef Z_LEAK_DEBUG_LIST
	char *file;
	int line;
    char *read_fname;
    char *write_fname;
    char *error_fname;
#endif	
};


struct ztimer {
    struct ztimer *next;
    struct ztimer *prev;
    ztime interval;

    void (*func)(void *);
    void *arg;
    int id;
//#ifdef Z_LEAK_DEBUG_LIST
    char *fname;    
//#endif
};

struct bottom_half {
    struct bottom_half *next;
    struct bottom_half *prev;
    void (*fn)(void *);
    void *arg;
};

struct zselect{
    int terminate;
#ifdef Z_MSC_MINGW
    GPtrArray *fds3;
#else
	struct zselect_fd fds2[FD_SETSIZE];
#endif
    struct ztimer timers;
    struct bottom_half bottom_halves;

	MUTEX_DEFINE(fdsets);
    fd_set w_read;
    fd_set w_write;
    fd_set w_error;
	// end of mutex protected data
    
    int w_max;
    int timer_id;
    ztime last_time;
     
    void (*redraw)(void);
    int msgpipe[2];
    GString *msg_recv;
    void (*msg_handler)(struct zselect *, int, char **);

    int profile;
    double limit;
	char hint[100];
    int select_running; // run from thread
};

#ifdef Z_UNIX_ANDROID
#define NUM_SIGNALS 32

struct signal_handler {
    void (*fn)(void *, siginfo_t *, void *);
    void *arg;
    int critical;
};

int signal_mask[NUM_SIGNALS];
struct signal_handler signal_handlers[NUM_SIGNALS];
int signal_init = 0;
#endif

static void zselect_msg_read_handler(void *);


struct zselect *zselect_init(void (*redraw)(void)){
    
    struct zselect *zsel = g_new0(struct zselect, 1);
    init_list(zsel->timers);
    init_list(zsel->bottom_halves);
    zsel->timer_id = 1;
	MUTEX_INIT(zsel->fdsets);

    FD_ZERO(&zsel->w_read);
    FD_ZERO(&zsel->w_write);
    FD_ZERO(&zsel->w_error);
    zsel->w_max = 0;
    zsel->last_time = zselect_time();
//#ifdef Z_HAVE_SIGNAL_H  //under mingw is signal.h without SIGPIPE
#ifdef Z_UNIX
    signal(SIGPIPE, SIG_IGN);
#endif
#ifdef Z_MSC_MINGW
	zsel->fds3 = g_ptr_array_new();
#endif

    zselect_signal_init();
    zsel->redraw = redraw;

    if (z_pipe(zsel->msgpipe)) zinternal("zselect_init: can't create msg pipe");
    zselect_set(zsel, zsel->msgpipe[0], zselect_msg_read_handler, NULL, NULL, zsel);
    zsel->msg_recv = g_string_sized_new(100);

    return zsel;
}

void zselect_free(struct zselect *zsel){
    
    zselect_set(zsel, zsel->msgpipe[0], NULL, NULL, NULL, zsel);
	MUTEX_FREE(zsel->fdsets);
	z_pipe_close(zsel->msgpipe[0]);
    z_pipe_close(zsel->msgpipe[1]);
    g_string_free(zsel->msg_recv, TRUE);
}

static struct zselect_fd *get_fd(struct zselect *zsel, int sock){
	struct zselect_fd *zfd;
#ifdef Z_MSC_MINGW
	int i;

	for (i = 0; i < (int)zsel->fds3->len; i++){
		zfd = (struct zselect_fd *)g_ptr_array_index(zsel->fds3, i);
		if (zfd->sock == sock) return zfd;
	}
	zfd = g_new0(struct zselect_fd, 1);
	zfd->sock = sock;
	g_ptr_array_add(zsel->fds3, zfd);
	return zfd;

	//zinternal("get_fd: socket %d not found", sock);
#else
    if (sock < 0 || sock >= FD_SETSIZE){
        zinternal("get_fd: handle %d out of bounds", sock);
	}
	zfd = zsel->fds2 + sock;
    zfd->sock = sock;

    return zfd;
#endif
}

static struct zselect_fd *get_fd_index(struct zselect *zsel, int i){
#ifdef Z_MSC_MINGW
	return (struct zselect_fd *)g_ptr_array_index(zsel->fds3, i);
#else
	return zsel->fds2 + i;
#endif
}


ztime zselect_time(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}


int zselect_bh_new(struct zselect *zsel, void (*fn)(void *), void *arg)
{
    struct bottom_half *bh;
    foreach(bh, zsel->bottom_halves) if (bh->fn == fn && bh->arg == arg) return 0;

    if (!(bh = (struct bottom_half *)g_malloc(sizeof(struct bottom_half)))) return -1;
	/*dbg("register_bottom_half(0x%x,0x%x)=0x%x\n", fn, arg, bh);*/
    bh->fn = fn;
    bh->arg = arg;
    add_to_list(zsel->bottom_halves, bh);
    return 0;
}

void zselect_bh_check(struct zselect *zsel)
{
    struct bottom_half *bh;
    void (*fn)(void *);
    void *arg;
rep:;
    if (list_empty(zsel->bottom_halves)) return;
    bh = zsel->bottom_halves.prev;
    fn = bh->fn;
    arg = bh->arg;
    del_from_list(bh);
    g_free(bh);
    fn(arg);
    goto rep;
}

#define CHK_BH if (!list_empty(zsel->bottom_halves)) zselect_bh_check(zsel);
        


static void zselect_timers_check(struct zselect *zsel)
{
    struct ztimer *t;
	int timer_count = 0;
    ztime interval = zselect_time() - zsel->last_time;

    foreach(t, zsel->timers) {
		t->interval -= interval;
		timer_count++;
	}
    ch:
    foreach(t, zsel->timers){
        if (t->interval <= 0) {
            struct ztimer *tt = t;
            del_from_list(tt);
#ifdef Z_LEAK_DEBUG_LIST        
            //dbg("---%s\n", tt->fname);
#endif
			if (zsel->profile) gettimeofday(&start, NULL);
			//dbg("%s\n", tt->fname);
            tt->func(tt->arg);
			if (zsel->profile) zselect_handle_profile(zsel, &start, tt->func, tt->fname);
            g_free(tt);
            CHK_BH;
			if (timer_count-- == 0) break;
            goto ch;
        }else{
            break;
        }
    }
    zsel->last_time += interval;
}


#ifdef Z_LEAK_DEBUG_LIST
int debug_install_timer(char *file, int line, ztime t, void (*func)(void *), void *arg, char *fname)
#else    
//int zselect_timer_new(struct zselect *zsel, ztime t, void (*func)(void *), void *arg)
int zselect_timer_new_dbg(struct zselect *zsel, ztime t, void (*func)(void *), char *fname, void *arg)
#endif    
{
    struct ztimer *tm, *tt;
#ifdef Z_LEAK_DEBUG_LIST
    tm = debug_g_malloc(file, line, sizeof(struct ztimer));
    tm->fname = fname;
#else            
    tm = g_new(struct ztimer, 1);
#endif        
    tm->interval = t;
    tm->func = func;
	tm->fname = fname;
    tm->arg = arg;
    tm->id = zsel->timer_id++;
#if 0
	dbg("before:\n");
	foreach(tt, timers){
		dbg("    id=%d inter=%d fce=%p\n",tt->id, tt->interval, tt->func);  
	}
#endif	
	
    foreach(tt, zsel->timers) if (tt->interval >= t) break;
    add_at_pos(tt->prev, tm);
	/*if (t != 50){
    dbg("__installed ztimer %d %p t=%d\n",tm->id, func,t );
	} */
#if 0
	dbg("after:\n");
	foreach(tt, timers){
		dbg("    id=%d inter=%d fce=%p\n",tt->id, tt->interval, tt->func);  
	}
	dbg("\n");
#endif	
    return tm->id;
}

int zselect_timer_new_at(struct zselect *zsel, struct timeval *at, void (*func)(void *), void *arg)
{
    struct ztimer *tm, *tt;
#ifdef Z_LEAK_DEBUG_LIST
    tm = debug_g_malloc(file, line, sizeof(struct ztimer));
    tm->fname = fname;
#else            
    tm = g_new(struct ztimer, 1);
#endif        
    
    struct timeval now;
    gettimeofday(&now, NULL);
    double d = z_difftimeval_double(at, &now);
    ztime t = (ztime)round(d*1000.0);
    tm->interval = t;
    tm->func = func;
	//tm->fname = fname;
    tm->arg = arg;
    tm->id = zsel->timer_id++;
#if 0
	dbg("before:\n");
	foreach(tt, timers){
		dbg("    id=%d inter=%d fce=%p\n",tt->id, tt->interval, tt->func);  
	}
#endif	
	
    foreach(tt, zsel->timers) if (tt->interval >= t) break;
    add_at_pos(tt->prev, tm);
	/*if (t != 50){
    dbg("__installed ztimer %d %p t=%d\n",tm->id, func,t );
	} */
#if 0
	dbg("after:\n");
	foreach(tt, timers){
		dbg("    id=%d inter=%d fce=%p\n",tt->id, tt->interval, tt->func);  
	}
	dbg("\n");
#endif	
    return tm->id;
}

#ifdef Z_LEAK_DEBUG_LIST
void debug_kill_timer(char *file, int line, int id)
#else    
void zselect_timer_kill(struct zselect *zsel, int id)
#endif    
{
    struct ztimer *tm;
    int k = 0;
	//dbg("__kill_timer(%d)\n", id);
    foreach(tm, zsel->timers) {
        if (tm->id == id) {
            struct ztimer *tt = tm;
            del_from_list(tm);
            tm = tm->prev;
#ifdef Z_LEAK_DEBUG_LIST        
            debug_g_free(file, line, tt);
#else        
            g_free(tt);
#endif        
            k++;
        }
    }
    if (!k) zinternal("trying to kill nonexisting ztimer id=%d", id);
    if (k >= 2) zinternal("more timers with same id=%d", id);
}


ztime zselect_timer_get(struct zselect *zsel, int id)
{
    struct ztimer *tm;
    foreach(tm, zsel->timers){
        if (tm->id == id) {
            return tm->interval;
        }
    }
    return -1;
}


void *zselect_get(struct zselect *zsel, int sock, int typ)
{
	struct zselect_fd *zfd = get_fd(zsel, sock);

    switch (typ) {
        case H_READ:    return zfd->read_func;
        case H_WRITE:   return zfd->write_func;
        case H_ERROR:   return zfd->error_func;
        case H_DATA:    return zfd->arg; 
    }
    zinternal("get_handler: bad type %d for socket %d", typ, sock);
    return NULL;
}


#ifdef Z_LEAK_DEBUG_LIST
void debug_set_handlers(char *file, int line, int fd, 
        void (*read_func)(void *), void (*write_func)(void *), void (*error_func)(void *), 
        void *arg, 
        char *read_fname, char *write_fname, char *error_fname)
#else
//void zselect_set(struct zselect *zsel, int fd, void (*read_func)(void *), void (*write_func)(void *), void (*error_func)(void *), void *arg)
void zselect_set_dbg(struct zselect *zsel, int fd, void (*read_func)(void *), char *read_fname, void (*write_func)(void *), char *write_fname, void (*error_func)(void *), char *error_fname, void *arg)
#endif
{
	struct zselect_fd *zfd = get_fd(zsel, fd);
    /*dbg("set_handlers(%d,%p,%p,%p,%p)\n",fd,read_func,write_func,error_func,arg);*/
    
    //if (fd!=0) sock_debug(fd, "set_handlers %p,%p,%p %p",read_func,write_func,error_func,arg);

    zfd->read_func = read_func;
	zfd->read_fname = read_fname;
    zfd->write_func = write_func;
	zfd->write_fname = write_fname;
    zfd->error_func = error_func;
	zfd->error_fname = error_fname;
    zfd->arg = arg;


	MUTEX_LOCK(zsel->fdsets);
	if (read_func) FD_SET(fd, &zsel->w_read);
    else FD_CLR(fd, &zsel->w_read);
   
    if (write_func) FD_SET(fd, &zsel->w_write);
    else FD_CLR(fd, &zsel->w_write);
    
    if (error_func) FD_SET(fd, &zsel->w_error);
    else FD_CLR(fd, &zsel->w_error);
	MUTEX_UNLOCK(zsel->fdsets);


#ifdef Z_MSC_MINGW
	if (!read_func && !write_func && !error_func){
		g_ptr_array_remove_fast(zsel->fds3, zfd);
	}
	zsel->w_max = zsel->fds3->len;
#else
    if (read_func || write_func || error_func) {
        if (fd >= zsel->w_max) zsel->w_max = fd + 1;
    } else if (fd == zsel->w_max - 1) {
        int i;
        for (i = fd - 1; i >= 0; i--)
            if (FD_ISSET(i, &zsel->w_read) || FD_ISSET(i, &zsel->w_write) ||
                FD_ISSET(i, &zsel->w_error)) break;
        zsel->w_max = i + 1;
    }
#endif

#ifdef Z_LEAK_DEBUG_LIST
	zsel->fds[fd].file=file;
	zsel->fds[fd].line=line;
    zsel->fds[fd].read_fname = read_fname;
    zsel->fds[fd].write_fname = write_fname;
    zsel->fds[fd].error_fname = error_fname;
#endif	
}


void zselect_set_read(struct zselect *zsel, int fd, void (*read_func)(void *), void *arg){
	struct zselect_fd *zfd = get_fd(zsel, fd);
    zfd->read_func = read_func;
	zfd->arg = arg;
   
	MUTEX_LOCK(zsel->fdsets);
	if (read_func) FD_SET(fd, &zsel->w_read);
    else FD_CLR(fd, &zsel->w_read);
	MUTEX_UNLOCK(zsel->fdsets);


#ifdef Z_MSC_MINGW
	if (!read_func && !zfd->write_func && !zfd->error_func){
		g_ptr_array_remove_fast(zsel->fds3, zfd);
	}
	zsel->w_max = zsel->fds3->len;
#else
    if (read_func || zfd->write_func || zfd->error_func) {
        if (fd >= zsel->w_max) zsel->w_max = fd + 1;
    } else if (fd == zsel->w_max - 1) {
        int i;
        for (i = fd - 1; i >= 0; i--)
            if (FD_ISSET(i, &zsel->w_read) || FD_ISSET(i, &zsel->w_write) ||
                FD_ISSET(i, &zsel->w_error)) break;
        zsel->w_max = i + 1;
    }
#endif

}

void zselect_set_write(struct zselect *zsel, int fd, void (*write_func)(void *), void *arg){
	struct zselect_fd *zfd = get_fd(zsel, fd);
    zfd->write_func = write_func;
	zfd->arg = arg;
   
	MUTEX_LOCK(zsel->fdsets);
	if (write_func) FD_SET(fd, &zsel->w_write);
    else FD_CLR(fd, &zsel->w_write);
    if (zsel->select_running) zselect_msg_send_raw(zsel, "\n"); 
	MUTEX_UNLOCK(zsel->fdsets);


#ifdef Z_MSC_MINGW
	if (!zfd->read_func && !write_func && !zfd->error_func){
		g_ptr_array_remove_fast(zsel->fds3, zfd);
	}
	zsel->w_max = zsel->fds3->len;
#else
    if (zfd->read_func || write_func || zfd->error_func) {
        if (fd >= zsel->w_max) zsel->w_max = fd + 1;
    } else if (fd == zsel->w_max - 1) {
        int i;
        for (i = fd - 1; i >= 0; i--)
            if (FD_ISSET(i, &zsel->w_read) || FD_ISSET(i, &zsel->w_write) ||
                FD_ISSET(i, &zsel->w_error)) break;
        zsel->w_max = i + 1;
    }
#endif

}

int critical_section = 0;

void check_for_select_race(void);

#ifdef Z_UNIX_ANDROID

void got_signal(int sig, siginfo_t *siginfo, void *ctx)
{
    dbg("got_signal(%d, %p, %p)\n", sig, siginfo, ctx);

    if (sig >= NUM_SIGNALS || sig < 0) {
        error("ERROR: bad signal number: %d", sig);
        return;
    }
    /* for debugging of unknown error */
    /*if (sig == 11) {
        sig_segv(NULL); 
        return;
    } */

    //dbg("    handler[%d] = %p, critical = %d\n", sig, signal_handlers[sig].fn, signal_handlers[sig].critical);
    if (!signal_handlers[sig].fn) return;
    if (signal_handlers[sig].critical) {
        signal_handlers[sig].fn(signal_handlers[sig].arg, siginfo, ctx);
        return;
    }
    signal_mask[sig] = 1;
    check_for_select_race();
}


void zselect_signal_set(int sig, void (*fn)(void *, siginfo_t *, void *ctx), void *arg, int critical)
{
//#ifdef Z_HAVE_SIGNAL_H  //under mingw is signal.h without SIGPIPE
    struct sigaction sa;
    dbg("zselect_signal_set(%d, %p, %p, %d)\n", sig, fn, arg, critical);
    if (sig >= NUM_SIGNALS || sig < 0) {
        zinternal("bad signal number: %d", sig);
        return;
    }
    memset(&sa, 0, sizeof sa);
    if (!fn) {
		sa.sa_handler = SIG_IGN;
	}else{
	   	sa.sa_sigaction = got_signal;
    	sa.sa_flags |= SA_SIGINFO;
	}
    sigfillset(&sa.sa_mask);
    /*sa.sa_flags = SA_RESTART;*/
    if (!fn) sigaction(sig, &sa, NULL);
    signal_handlers[sig].fn = fn;
    signal_handlers[sig].arg = arg;
    signal_handlers[sig].critical = critical;
    if (fn) sigaction(sig, &sa, NULL);
}
#endif

int pending_alarm = 0;

#ifdef Z_UNIX_ANDROID
void alarm_handler(void *arg, siginfo_t *siginfo, void *ctx)
{
    pending_alarm = 0;
    check_for_select_race();
}
#endif

void check_for_select_race(void)
{
    if (critical_section) {
#ifdef SIGALRM
        zselect_signal_set(SIGALRM, alarm_handler, NULL, 1);
#endif
        pending_alarm = 1;
#ifdef Z_HAVE_ALARM
        /*alarm(1);*/
#endif
    }
}

void uninstall_alarm(void)
{
    pending_alarm = 0;
#ifdef Z_HAVE_ALARM
    alarm(0);
#endif
}

#ifdef Z_UNIX
static int check_signals(struct zselect *zsel)
{

    int i, r = 0;
    for (i = 0; i < NUM_SIGNALS; i++)
        if (signal_mask[i]) {
            signal_mask[i] = 0;
            if (signal_handlers[i].fn) signal_handlers[i].fn(signal_handlers[i].arg, NULL, NULL);
            CHK_BH;
            r = 1;
        }
    return r;
}
#endif


void zselect_loop(struct zselect *zsel)
{
	int err;
	fd_set x_read;
	fd_set x_write;
	fd_set x_error;



    CHK_BH;
    while (!zsel->terminate) {
        int n, i;
        struct timeval tv;
        struct timeval *tm = NULL;

#ifdef Z_UNIX
        check_signals(zsel);
#endif
        zselect_timers_check(zsel);
        
        if (zsel->redraw) {
			if (zsel->profile) gettimeofday(&start, NULL);
			//dbg("redraw\n");
			zsel->redraw();
			if (zsel->profile) zselect_handle_profile(zsel, &start, NULL, "redraw");
        }

        if (!list_empty(zsel->timers)) {
            ztime tt = zsel->timers.next->interval + 1;
            if (tt < 0) tt = 0;
            tv.tv_sec = tt / 1000;
            tv.tv_usec = (tt % 1000) * 1000;
            tm = &tv;
        }

		MUTEX_LOCK(zsel->fdsets);
        memcpy(&x_read, &zsel->w_read, sizeof(fd_set));
        memcpy(&x_write, &zsel->w_write, sizeof(fd_set));
        memcpy(&x_error, &zsel->w_error, sizeof(fd_set));
		MUTEX_UNLOCK(zsel->fdsets);

		if (zsel->terminate) break;
        if (!zsel->w_max && list_empty(zsel->timers)) break;
#ifdef Z_UNIX
        critical_section = 1;
        if (check_signals(zsel)) {
            critical_section = 0;
            continue;
        }
#endif
/*          {
                int i;
                printf("\nR:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_read)) printf("%d,", i);
                printf("\nW:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_write)) printf("%d,", i);
                printf("\nE:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_error)) printf("%d,", i);
                fflush(stdout);
            }*/
        CHK_BH;


#if 0
	if (tm)	
		dbg("SELECT: tm=%d.%06d\n", tm->tv_sec, tm->tv_usec);
	else
		dbg("SELECT: no timers-----------------\n");
#endif
#if 0
    struct ztimer *tt;
	foreach(tt, timers){
		dbg("    id=%d inter=%d fce=%p\n",tt->id, tt->interval, tt->func);  
	}
	dbg("\n");
#endif	
     /*   ST_STOP;
        sound(0);  */
	    MUTEX_LOCK(zsel->fdsets);
        zsel->select_running = 1;
	    MUTEX_UNLOCK(zsel->fdsets);

        n = select(zsel->w_max, &x_read, &x_write, &x_error, tm);

	    MUTEX_LOCK(zsel->fdsets);
        zsel->select_running = 0;
	    MUTEX_UNLOCK(zsel->fdsets);
       /* sound(800);
        ST_START;      */
        if (n < 0) {
			err = z_sock_errno;
            critical_section = 0;
            uninstall_alarm();
            if (err != EINTR) {
                int i;
                fd_set x, used;
                struct timeval tv;
                GString *gs = g_string_sized_new(1024);

                g_string_append_printf(gs, "ERROR: select failed: %d", err);
				
                FD_ZERO(&used);
                g_string_append(gs, "\nR:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_read)) { g_string_append_printf(gs, "%d,", i); FD_SET(i, &used); }
                g_string_append(gs, "\nW:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_write)) { g_string_append_printf(gs, "%d,", i); FD_SET(i, &used); }
                g_string_append(gs, "\nE:");
                for (i = 0; i < 256; i++) if (FD_ISSET(i, &x_error)) { g_string_append_printf(gs, "%d,", i); FD_SET(i, &used); }

                for (i = 0; i < 256; i++) {
                    if (!FD_ISSET(i, &used)) continue;
                    FD_ZERO(&x);
                    FD_SET(i, &x);
                    tv.tv_sec=0;
                    tv.tv_usec=1;
                    if (select(i+1, &x, NULL, NULL, &tv)<0 && errno==EBADF)
#ifdef Z_LEAK_DEBUG_LIST							
                    g_string_append_printf(gs, "\nfd %d failed(err=%d) at %s:%d\n", i, errno, zsel->fds[i].file, zsel->fds[i].line);
#else						
                    g_string_append_printf(gs, "\nfd %d failed(err=%d)\n ", i, errno);
#endif							
                }
                
                zinternal("%s", gs->str);
                g_string_free(gs, TRUE);
            }
            
            continue;
        }
        critical_section = 0;
        uninstall_alarm();
#ifdef Z_UNIX
        check_signals(zsel);
#endif
/*      printf("sel: %d\n", n);*/
/*      dbg(".\n");*/
        zselect_timers_check(zsel);

                    /*dbg("R:");
                    for (i = 0; i < 32; i++) if (FD_ISSET(i, &x_read)) {dbg("%d,", i); }
                    dbg("  W:");
                    for (i = 0; i < 32; i++) if (FD_ISSET(i, &x_write)) {dbg("%d,", i); }
                    dbg("  E:");
                    for (i = 0; i < 32; i++) if (FD_ISSET(i, &x_error)) {dbg("%d,", i); }
                    dbg("\n");*/

        i = -1;
        while (n > 0 && ++i < zsel->w_max) {
            int k = 0;
			struct zselect_fd *zfd = get_fd_index(zsel, i);
			if (FD_ISSET(zfd->sock, &x_read)) {
                if (zfd->read_func) {
					if (zsel->profile) gettimeofday(&start, NULL);
					//dbg("%s\n", zfd->read_fname);
                    zfd->read_func(zfd->arg);
					if (zsel->profile) zselect_handle_profile(zsel, &start, zfd->read_func, zfd->read_fname);
                    CHK_BH;
                }
                k = 1;
            }
            if (FD_ISSET(zfd->sock, &x_write)) {
                if (zfd->write_func) {
					if (zsel->profile) gettimeofday(&start, NULL);
					//dbg("%s\n", zfd->write_fname);
					zfd->write_func(zfd->arg);
					if (zsel->profile) zselect_handle_profile(zsel, &start, zfd->write_func, zfd->write_fname);
                    CHK_BH;
                }
                k = 1;
            }
            if (FD_ISSET(zfd->sock, &x_error)) {
                if (zfd->error_func) {
					if (zsel->profile) gettimeofday(&start, NULL);
					//dbg("%s\n", zfd->error_fname);
					zfd->error_func(zfd->arg);
					if (zsel->profile) zselect_handle_profile(zsel, &start, zfd->error_func, zfd->error_fname);
                    CHK_BH;
                }
                k = 1;
            }
            n -= k;
        }
    }

}


void zselect_terminate(struct zselect *zsel){
    zsel->terminate = 1;
}

int zselect_terminating(struct zselect *zsel){
    return zsel->terminate;
}



int zselect_msg_send(struct zselect *zsel, char *fmt, ...){
    va_list l;
    int ret;
    GString *gs;

    gs = g_string_sized_new(256);
    va_start(l, fmt);
    ret = zg_string_veprintfa("e", gs, fmt, l); 
    g_string_append(gs, "\n");
    va_end(l);
    zselect_msg_send_raw(zsel, gs->str);

    g_string_free(gs, TRUE);
    return ret;
}

int zselect_msg_send_raw(struct zselect *zsel, char *buf){
    int ret, len;

    len = strlen(buf);
    ret = z_pipe_write(zsel->msgpipe[1], buf, len);
    if (ret < 0) zinternal("zselect_msg_send_raw: can't write to msg pipe, error %d", z_sock_errno);
    if (ret != len) zinternal("zselect_msg_send_raw: written only %d/%d bytes", ret, len);
    return ret;
}



int zselect_msg_set(struct zselect *zsel, void (*handler)(struct zselect *, int n, char **items)){
    zsel->msg_handler = handler;
	return 0;
}

static void zselect_msg_read_handler(void *xxx){
    int ret;
    char buf[1025], *lf, *line;
    struct zselect *zsel = (struct zselect*)xxx;

    //struct zsel *sel = (struct zsel*)xxx;
    ret = z_pipe_read(zsel->msgpipe[0], buf, sizeof(buf)-1);
    if (ret <= 0) zinternal("zselect_msg_read_handler: can't read from msg pipe, error %d", z_sock_errno);

    buf[ret] = '\0';
    g_string_append(zsel->msg_recv, buf);
    while(1){
        lf = strchr(zsel->msg_recv->str, '\n');
        if (!lf) break; 

        line = g_strndup(zsel->msg_recv->str, lf - zsel->msg_recv->str + 1);
        line[lf - zsel->msg_recv->str] = '\0';
        g_string_erase(zsel->msg_recv, 0, lf - zsel->msg_recv->str + 1);
        if (strlen(line) > 0){
            char **items;
            int i, tokenpos = 0;
            int n = z_tokens(line);

            items = g_new0(char *, n + 1);
            for (i = 0; i < n; i++) {
                items[i] = g_strdup(z_tokenize(line, &tokenpos));
            }
            if (zsel->msg_handler) zsel->msg_handler(zsel, n, items);
            for (i = 0; i < n; i++){
                g_free(items[i]);
            }
            g_free(items);
        }
        g_free(line);
    }
}


void zselect_start_profile(struct zselect *zsel, double limit){
	zsel->profile = 1;
	zsel->limit = limit;
}

void zselect_hint(struct zselect *zsel, const char *fmt, ...){
	va_list ap;
	va_start(ap, fmt);
	g_vsnprintf(zsel->hint, 100, fmt, ap);
	va_end(ap);
}

void zselect_handle_profile(struct zselect *zsel, struct timeval *start, void *fce, char *fname){
	struct timeval stop;
	double d;

	gettimeofday(&stop, NULL);
	d = z_difftimeval_double(&stop, start);
	if (d > zsel->limit) {
	if (fname)
			dbg("%6.3f: %s %s \n", d, fname, zsel->hint); 
		else
			dbg("%5.3f: %p %s \n", d, fce, zsel->hint); 
	}
	*zsel->hint = '\0';
}

int zselect_profiling(struct zselect *zsel){
	return zsel->profile;
}

void zselect_abort_exception(void *arg){
	zinternal("Socket exception handler called");

}

#ifdef Z_UNIX_ANDROID
void zselect_sigterm(void *arg, siginfo_t *siginfo, void *ctx)
{
    struct zselect *zsel = (struct zselect *)arg;
    zselect_terminate(zsel);
}
#endif

void zselect_signal_init(){
#ifdef Z_UNIX
    if (!signal_init){
        signal_init = 1;
        memset(signal_mask, 0, sizeof signal_mask);
        memset(signal_handlers, 0, sizeof signal_handlers);
    }
#endif

}
