/*
 * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
 *
 * SPDX-FileCopyrightText: 2024 Hiredict Contributors
 * SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
 *
 * SPDX-License-Identifier: BSD-3-Clause
 * SPDX-License-Identifier: LGPL-3.0-or-later
 *
 */

#ifndef HIREDICT_LIBSDEVENT_H
#define HIREDICT_LIBSDEVENT_H
#include <systemd/sd-event.h>
#include "../hiredict.h"
#include "../async.h"

#define REDICT_LIBSDEVENT_DELETED 0x01
#define REDICT_LIBSDEVENT_ENTERED 0x02

typedef struct redictLibsdeventEvents {
    redictAsyncContext *context;
    struct sd_event *event;
    struct sd_event_source *fdSource;
    struct sd_event_source *timerSource;
    int fd;
    short flags;
    short state;
} redictLibsdeventEvents;

static void redictLibsdeventDestroy(redictLibsdeventEvents *e) {
    if (e->fdSource) {
        e->fdSource = sd_event_source_disable_unref(e->fdSource);
    }
    if (e->timerSource) {
        e->timerSource = sd_event_source_disable_unref(e->timerSource);
    }
    sd_event_unref(e->event);
    hi_free(e);
}

static int redictLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) {
    ((void)s);
    ((void)usec);
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;
    redictAsyncHandleTimeout(e->context);
    return 0;
}

static int redictLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) {
    ((void)s);
    ((void)fd);
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;
    e->state |= REDICT_LIBSDEVENT_ENTERED;

#define CHECK_DELETED() if (e->state & REDICT_LIBSDEVENT_DELETED) {\
        redictLibsdeventDestroy(e);\
        return 0; \
    }

    if ((event & EPOLLIN) && e->context && (e->state & REDICT_LIBSDEVENT_DELETED) == 0) {
        redictAsyncHandleRead(e->context);
        CHECK_DELETED();
    }

    if ((event & EPOLLOUT) && e->context && (e->state & REDICT_LIBSDEVENT_DELETED) == 0) {
        redictAsyncHandleWrite(e->context);
        CHECK_DELETED();
    }

    e->state &= ~REDICT_LIBSDEVENT_ENTERED;
#undef CHECK_DELETED

    return 0;
}

static void redictLibsdeventAddRead(void *userdata) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;

    if (e->flags & EPOLLIN) {
        return;
    }

    e->flags |= EPOLLIN;

    if (e->flags & EPOLLOUT) {
        sd_event_source_set_io_events(e->fdSource, e->flags);
    } else {
        sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redictLibsdeventHandler, e);
    }
}

static void redictLibsdeventDelRead(void *userdata) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;

    e->flags &= ~EPOLLIN;

    if (e->flags) {
        sd_event_source_set_io_events(e->fdSource, e->flags);
    } else {
        e->fdSource = sd_event_source_disable_unref(e->fdSource);
    }
}

static void redictLibsdeventAddWrite(void *userdata) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;

    if (e->flags & EPOLLOUT) {
        return;
    }

    e->flags |= EPOLLOUT;

    if (e->flags & EPOLLIN) {
        sd_event_source_set_io_events(e->fdSource, e->flags);
    } else {
        sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redictLibsdeventHandler, e);
    }
}

static void redictLibsdeventDelWrite(void *userdata) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;

    e->flags &= ~EPOLLOUT;

    if (e->flags) {
        sd_event_source_set_io_events(e->fdSource, e->flags);
    } else {
        e->fdSource = sd_event_source_disable_unref(e->fdSource);
    }
}

static void redictLibsdeventCleanup(void *userdata) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents*)userdata;

    if (!e) {
        return;
    }

    if (e->state & REDICT_LIBSDEVENT_ENTERED) {
        e->state |= REDICT_LIBSDEVENT_DELETED;
    } else {
        redictLibsdeventDestroy(e);
    }
}

static void redictLibsdeventSetTimeout(void *userdata, struct timeval tv) {
    redictLibsdeventEvents *e = (redictLibsdeventEvents *)userdata;

    uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec;
    if (!e->timerSource) {
        sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redictLibsdeventTimeoutHandler, e);
    } else {
        sd_event_source_set_time_relative(e->timerSource, usec);
    }
}

static int redictLibsdeventAttach(redictAsyncContext *ac, struct sd_event *event) {
    redictContext *c = &(ac->c);
    redictLibsdeventEvents *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 = (redictLibsdeventEvents*)hi_calloc(1, sizeof(*e));
    if (e == NULL)
        return REDICT_ERR;

    /* Initialize and increase event refcount */
    e->context = ac;
    e->event = event;
    e->fd = c->fd;
    sd_event_ref(event);

    /* Register functions to start/stop listening for events */
    ac->ev.addRead = redictLibsdeventAddRead;
    ac->ev.delRead = redictLibsdeventDelRead;
    ac->ev.addWrite = redictLibsdeventAddWrite;
    ac->ev.delWrite = redictLibsdeventDelWrite;
    ac->ev.cleanup = redictLibsdeventCleanup;
    ac->ev.scheduleTimer = redictLibsdeventSetTimeout;
    ac->ev.data = e;

    return REDICT_OK;
}
#endif
