/*
 * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * SPDX-FileCopyrightText: 2024 Hiredict Contributors
 * SPDX-FileCopyrightText: 2024 Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * SPDX-License-Identifier: LGPL-3.0-or-later
 *
 */

#ifndef __HIREDICT_LIBEV_H__
#define __HIREDICT_LIBEV_H__
#include <stdlib.h>
#include <sys/types.h>
#include <ev.h>
#include "../hiredict.h"
#include "../async.h"

typedef struct redictLibevEvents {
    redictAsyncContext *context;
    struct ev_loop *loop;
    int reading, writing;
    ev_io rev, wev;
    ev_timer timer;
} redictLibevEvents;

static void redictLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY
    ((void)EV_A);
#endif
    ((void)revents);

    redictLibevEvents *e = (redictLibevEvents*)watcher->data;
    redictAsyncHandleRead(e->context);
}

static void redictLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
#if EV_MULTIPLICITY
    ((void)EV_A);
#endif
    ((void)revents);

    redictLibevEvents *e = (redictLibevEvents*)watcher->data;
    redictAsyncHandleWrite(e->context);
}

static void redictLibevAddRead(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif
    if (!e->reading) {
        e->reading = 1;
        ev_io_start(EV_A_ &e->rev);
    }
}

static void redictLibevDelRead(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif
    if (e->reading) {
        e->reading = 0;
        ev_io_stop(EV_A_ &e->rev);
    }
}

static void redictLibevAddWrite(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif
    if (!e->writing) {
        e->writing = 1;
        ev_io_start(EV_A_ &e->wev);
    }
}

static void redictLibevDelWrite(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif
    if (e->writing) {
        e->writing = 0;
        ev_io_stop(EV_A_ &e->wev);
    }
}

static void redictLibevStopTimer(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif
    ev_timer_stop(EV_A_ &e->timer);
}

static void redictLibevCleanup(void *privdata) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
    redictLibevDelRead(privdata);
    redictLibevDelWrite(privdata);
    redictLibevStopTimer(privdata);
    hi_free(e);
}

static void redictLibevTimeout(EV_P_ ev_timer *timer, int revents) {
#if EV_MULTIPLICITY
    ((void)EV_A);
#endif
    ((void)revents);
    redictLibevEvents *e = (redictLibevEvents*)timer->data;
    redictAsyncHandleTimeout(e->context);
}

static void redictLibevSetTimeout(void *privdata, struct timeval tv) {
    redictLibevEvents *e = (redictLibevEvents*)privdata;
#if EV_MULTIPLICITY
    struct ev_loop *loop = e->loop;
#endif

    if (!ev_is_active(&e->timer)) {
        ev_init(&e->timer, redictLibevTimeout);
        e->timer.data = e;
    }

    e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
    ev_timer_again(EV_A_ &e->timer);
}

static int redictLibevAttach(EV_P_ redictAsyncContext *ac) {
    redictContext *c = &(ac->c);
    redictLibevEvents *e;

    /* Nothing should be attached when something is already attached */
    if (ac->ev.data != NULL)
        return REDICT_ERR;

    /* Create container for context and r/w events */
    e = (redictLibevEvents*)hi_calloc(1, sizeof(*e));
    if (e == NULL)
        return REDICT_ERR;

    e->context = ac;
#if EV_MULTIPLICITY
    e->loop = EV_A;
#else
    e->loop = NULL;
#endif
    e->rev.data = e;
    e->wev.data = e;

    /* Register functions to start/stop listening for events */
    ac->ev.addRead = redictLibevAddRead;
    ac->ev.delRead = redictLibevDelRead;
    ac->ev.addWrite = redictLibevAddWrite;
    ac->ev.delWrite = redictLibevDelWrite;
    ac->ev.cleanup = redictLibevCleanup;
    ac->ev.scheduleTimer = redictLibevSetTimeout;
    ac->ev.data = e;

    /* Initialize read/write events */
    ev_io_init(&e->rev,redictLibevReadEvent,c->fd,EV_READ);
    ev_io_init(&e->wev,redictLibevWriteEvent,c->fd,EV_WRITE);
    return REDICT_OK;
}

#endif
