/*
 * 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_REDICTMODULEAPI_H__
#define __HIREDICT_REDICTMODULEAPI_H__

#include "redictmodule.h"

#include "../async.h"
#include "../hiredict.h"

#include <sys/types.h>

typedef struct redictModuleEvents {
    redictAsyncContext *context;
    RedictModuleCtx *module_ctx;
    int fd;
    int reading, writing;
    int timer_active;
    RedictModuleTimerID timer_id;
} redictModuleEvents;

static inline void redictModuleReadEvent(int fd, void *privdata, int mask) {
    (void) fd;
    (void) mask;

    redictModuleEvents *e = (redictModuleEvents*)privdata;
    redictAsyncHandleRead(e->context);
}

static inline void redictModuleWriteEvent(int fd, void *privdata, int mask) {
    (void) fd;
    (void) mask;

    redictModuleEvents *e = (redictModuleEvents*)privdata;
    redictAsyncHandleWrite(e->context);
}

static inline void redictModuleAddRead(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    if (!e->reading) {
        e->reading = 1;
        RedictModule_EventLoopAdd(e->fd, REDICTMODULE_EVENTLOOP_READABLE, redictModuleReadEvent, e);
    }
}

static inline void redictModuleDelRead(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    if (e->reading) {
        e->reading = 0;
        RedictModule_EventLoopDel(e->fd, REDICTMODULE_EVENTLOOP_READABLE);
    }
}

static inline void redictModuleAddWrite(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    if (!e->writing) {
        e->writing = 1;
        RedictModule_EventLoopAdd(e->fd, REDICTMODULE_EVENTLOOP_WRITABLE, redictModuleWriteEvent, e);
    }
}

static inline void redictModuleDelWrite(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    if (e->writing) {
        e->writing = 0;
        RedictModule_EventLoopDel(e->fd, REDICTMODULE_EVENTLOOP_WRITABLE);
    }
}

static inline void redictModuleStopTimer(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    if (e->timer_active) {
        RedictModule_StopTimer(e->module_ctx, e->timer_id, NULL);
    }
    e->timer_active = 0;
}

static inline void redictModuleCleanup(void *privdata) {
    redictModuleEvents *e = (redictModuleEvents*)privdata;
    redictModuleDelRead(privdata);
    redictModuleDelWrite(privdata);
    redictModuleStopTimer(privdata);
    hi_free(e);
}

static inline void redictModuleTimeout(RedictModuleCtx *ctx, void *privdata) {
    (void) ctx;

    redictModuleEvents *e = (redictModuleEvents*)privdata;
    e->timer_active = 0;
    redictAsyncHandleTimeout(e->context);
}

static inline void redictModuleSetTimeout(void *privdata, struct timeval tv) {
    redictModuleEvents* e = (redictModuleEvents*)privdata;

    redictModuleStopTimer(privdata);

    mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
    e->timer_id = RedictModule_CreateTimer(e->module_ctx, millis, redictModuleTimeout, e);
    e->timer_active = 1;
}

/* Check if Redict is compatible with the adapter. */
static inline int redictModuleCompatibilityCheck(void) {
    if (!RedictModule_EventLoopAdd ||
        !RedictModule_EventLoopDel ||
        !RedictModule_CreateTimer ||
        !RedictModule_StopTimer) {
        return REDICT_ERR;
    }
    return REDICT_OK;
}

static inline int redictModuleAttach(redictAsyncContext *ac, RedictModuleCtx *module_ctx) {
    redictContext *c = &(ac->c);
    redictModuleEvents *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 = (redictModuleEvents*)hi_malloc(sizeof(*e));
    if (e == NULL)
        return REDICT_ERR;

    e->context = ac;
    e->module_ctx = module_ctx;
    e->fd = c->fd;
    e->reading = e->writing = 0;
    e->timer_active = 0;

    /* Register functions to start/stop listening for events */
    ac->ev.addRead = redictModuleAddRead;
    ac->ev.delRead = redictModuleDelRead;
    ac->ev.addWrite = redictModuleAddWrite;
    ac->ev.delWrite = redictModuleDelWrite;
    ac->ev.cleanup = redictModuleCleanup;
    ac->ev.scheduleTimer = redictModuleSetTimeout;
    ac->ev.data = e;

    return REDICT_OK;
}

#endif
