/* glib.c, liboop, copyright 1999 Dan Egnor

   This is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License, version 2.1 or later.
   See the file COPYING for details. */

#ifdef HAVE_TCL

#include "oop-tcl.h"
#include <tcl.h>
#include <assert.h>

struct file_handler {
        oop_call_fd *f[OOP_NUM_EVENTS];
        void *d[OOP_NUM_EVENTS];
};

struct timer_handler {
        struct timeval t;
        oop_call_time *f;
        void *d;
        Tcl_TimerToken token;
        struct timer_handler *next;
};

static int use_count = 0;
static struct oop_source source;
static struct oop_adapter_signal *sig;

static int array_size;
static struct file_handler *array;
static struct timer_handler *list;

static void file_call(ClientData data,int mask) {
        const int fd = (int) data;
        oop_source * const oop = oop_tcl_new();
        const struct file_handler *h;

        if (fd >= array_size) {
                oop_tcl_done();
                return;
        }

        /* BUG: what if !OOP_CONTINUE? */

        h = &array[fd];
        if ((mask & TCL_READABLE) && NULL != h->f[OOP_READ])
                h->f[OOP_READ](oop,fd,OOP_READ,h->d[OOP_READ]);

        h = &array[fd];
        if ((mask & TCL_WRITABLE) && NULL != h->f[OOP_WRITE])
                h->f[OOP_WRITE](oop,fd,OOP_WRITE,h->d[OOP_WRITE]);

        h = &array[fd];
        if ((mask & TCL_EXCEPTION) && NULL != h->f[OOP_EXCEPTION])
                h->f[OOP_EXCEPTION](oop,fd,OOP_EXCEPTION,h->d[OOP_EXCEPTION]);

        oop_tcl_done();
}

static void set_mask(int fd) {
        int mask = 0;
        const struct file_handler * const h = &array[fd];
        if (NULL != h->f[OOP_READ]) mask |= TCL_READABLE;
        if (NULL != h->f[OOP_WRITE]) mask |= TCL_WRITABLE;
        if (NULL != h->f[OOP_EXCEPTION]) mask |= TCL_EXCEPTION;

        if (0 == mask)
                Tcl_DeleteFileHandler(fd);
        else
                Tcl_CreateFileHandler(fd,mask,file_call,(ClientData) fd);
}

static void on_fd(oop_source *x,int fd,oop_event event,oop_call_fd *f,void *d) {
        struct file_handler *h;

        if (fd >= array_size) {
                const int new_size = fd + 1;
                struct file_handler * const new_array = 
                        oop_realloc(array,new_size * sizeof(*new_array));
                if (NULL == new_array) return; /* YUCK */

                array = new_array;
                while (array_size != new_size) {
                        int i;
                        for (i = 0; i < OOP_NUM_EVENTS; ++i)
                                array[array_size].f[i] = NULL;
                        ++array_size;
                }
        }

        h = &array[fd];
        assert(NULL == h->f[event] && NULL != f);
        h->f[event] = f;
        h->d[event] = d;
        set_mask(fd);
}

static void cancel_fd(oop_source *x,int fd,oop_event event) {
        if (fd < array_size) {
                struct file_handler * const h = &array[fd];
                h->f[event] = NULL;
                set_mask(fd);
        }
}

static void timer_call(ClientData data) {
        struct timer_handler * const timer = (struct timer_handler *) data;
        struct timer_handler **ptr;

        Tcl_DeleteTimerHandler(timer->token);
        for (ptr = &list; timer != *ptr; ptr = &(*ptr)->next) ;
        *ptr = timer->next;

        /* BUG: What if !OOP_CONTINUE? */
        timer->f(oop_signal_source(sig),timer->t,timer->d);
        oop_free(timer);
}

static void on_time(oop_source *x,struct timeval t,oop_call_time *f,void *d) {
        struct timer_handler * const timer = oop_malloc(sizeof(*timer));
        struct timeval now;
        int msec;
        if (NULL == timer) return; /* YUCK */

        gettimeofday(&now,NULL);
        if (t.tv_sec < now.tv_sec
        || (t.tv_sec == now.tv_sec && t.tv_usec < now.tv_usec))
                msec = 0;
        else {
                msec = 1000 * (t.tv_sec - now.tv_sec);
                msec = msec + (t.tv_usec - now.tv_usec) / 1000;
        }

        assert(msec >= 0);
        timer->t = t;
        timer->f = f;
        timer->d = d;
        timer->next = list;
        timer->token = Tcl_CreateTimerHandler(msec,timer_call,timer);
        list = timer;
}

static void cancel_time(oop_source *x,struct timeval t,oop_call_time *f,void *d) {
        struct timer_handler **timer;
        for (timer = &list; NULL != *timer; timer = &(*timer)->next)
                if ((*timer)->d == d && (*timer)->f == f
                &&  (*timer)->t.tv_sec == t.tv_sec
                &&  (*timer)->t.tv_usec == t.tv_usec) {
                        struct timer_handler *dead = *timer;
                        *timer = dead->next;
                        Tcl_DeleteTimerHandler(dead->token);
                        oop_free(dead);
                        return;
                }
}

oop_source *oop_tcl_new(void) {
        if (0 == use_count) {
                source.on_fd = on_fd;
                source.cancel_fd = cancel_fd;
                source.on_time = on_time;
                source.cancel_time = cancel_time;
                source.on_signal = NULL;
                source.cancel_signal = NULL;
                array = NULL;
                array_size = 0;
                list = NULL;

                /* Do this last, after everything is set up. */
                sig = oop_signal_new(&source);
                if (NULL == sig) return NULL;
        }

        ++use_count;
        return oop_signal_source(sig);
}

void oop_tcl_done(void) {
        if (0 == --use_count) {
                int i,j;
                for (i = 0; i < array_size; ++i)
                        for (j = 0; j < OOP_NUM_EVENTS; ++j)
                                assert(NULL == array[i].f[j]);
                oop_free(array);
                assert(NULL == list);
                oop_signal_delete(sig);
        }
}

#endif


syntax highlighted by Code2HTML, v. 0.9.1