/*
 * 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_GLIB_H__
#define __HIREDICT_GLIB_H__

#include <glib.h>

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

typedef struct
{
    GSource source;
    redictAsyncContext *ac;
    GPollFD poll_fd;
} RedictSource;

static void
redict_source_add_read (gpointer data)
{
    RedictSource *source = (RedictSource *)data;
    g_return_if_fail(source);
    source->poll_fd.events |= G_IO_IN;
    g_main_context_wakeup(g_source_get_context((GSource *)data));
}

static void
redict_source_del_read (gpointer data)
{
    RedictSource *source = (RedictSource *)data;
    g_return_if_fail(source);
    source->poll_fd.events &= ~G_IO_IN;
    g_main_context_wakeup(g_source_get_context((GSource *)data));
}

static void
redict_source_add_write (gpointer data)
{
    RedictSource *source = (RedictSource *)data;
    g_return_if_fail(source);
    source->poll_fd.events |= G_IO_OUT;
    g_main_context_wakeup(g_source_get_context((GSource *)data));
}

static void
redict_source_del_write (gpointer data)
{
    RedictSource *source = (RedictSource *)data;
    g_return_if_fail(source);
    source->poll_fd.events &= ~G_IO_OUT;
    g_main_context_wakeup(g_source_get_context((GSource *)data));
}

static void
redict_source_cleanup (gpointer data)
{
    RedictSource *source = (RedictSource *)data;

    g_return_if_fail(source);

    redict_source_del_read(source);
    redict_source_del_write(source);
    /*
     * It is not our responsibility to remove ourself from the
     * current main loop. However, we will remove the GPollFD.
     */
    if (source->poll_fd.fd >= 0) {
        g_source_remove_poll((GSource *)data, &source->poll_fd);
        source->poll_fd.fd = -1;
    }
}

static gboolean
redict_source_prepare (GSource *source,
                      gint    *timeout_)
{
    RedictSource *redict = (RedictSource *)source;
    *timeout_ = -1;
    return !!(redict->poll_fd.events & redict->poll_fd.revents);
}

static gboolean
redict_source_check (GSource *source)
{
    RedictSource *redict = (RedictSource *)source;
    return !!(redict->poll_fd.events & redict->poll_fd.revents);
}

static gboolean
redict_source_dispatch (GSource      *source,
                       GSourceFunc   callback,
                       gpointer      user_data)
{
    RedictSource *redict = (RedictSource *)source;

    if ((redict->poll_fd.revents & G_IO_OUT)) {
        redictAsyncHandleWrite(redict->ac);
        redict->poll_fd.revents &= ~G_IO_OUT;
    }

    if ((redict->poll_fd.revents & G_IO_IN)) {
        redictAsyncHandleRead(redict->ac);
        redict->poll_fd.revents &= ~G_IO_IN;
    }

    if (callback) {
        return callback(user_data);
    }

    return TRUE;
}

static void
redict_source_finalize (GSource *source)
{
    RedictSource *redict = (RedictSource *)source;

    if (redict->poll_fd.fd >= 0) {
        g_source_remove_poll(source, &redict->poll_fd);
        redict->poll_fd.fd = -1;
    }
}

static GSource *
redict_source_new (redictAsyncContext *ac)
{
    static GSourceFuncs source_funcs = {
        .prepare  = redict_source_prepare,
        .check     = redict_source_check,
        .dispatch = redict_source_dispatch,
        .finalize = redict_source_finalize,
    };
    redictContext *c = &ac->c;
    RedictSource *source;

    g_return_val_if_fail(ac != NULL, NULL);

    source = (RedictSource *)g_source_new(&source_funcs, sizeof *source);
    if (source == NULL)
        return NULL;

    source->ac = ac;
    source->poll_fd.fd = c->fd;
    source->poll_fd.events = 0;
    source->poll_fd.revents = 0;
    g_source_add_poll((GSource *)source, &source->poll_fd);

    ac->ev.addRead = redict_source_add_read;
    ac->ev.delRead = redict_source_del_read;
    ac->ev.addWrite = redict_source_add_write;
    ac->ev.delWrite = redict_source_del_write;
    ac->ev.cleanup = redict_source_cleanup;
    ac->ev.data = source;

    return (GSource *)source;
}

#endif /* __HIREDICT_GLIB_H__ */
