/*
 * wnix, a Unix-like operating system for WebAssembly applications.
 * Copyright (C) 2025  Xavier Del Campo Romero
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <bin.h>
#include <bin/wnix.h>
#include <bin/wnix/routines.h>
#include <bin/proc.h>
#include <bin/types.h>
#include <aio.h>
#include <loop.h>
#include <nanowasm/nw.h>
#include <errno.h>
#include <stdlib.h>

struct open
{
    char *path;
    struct fs_fd fd;
    unsigned long path_addr, errno_addr;
    long flags, mode;
    union nw_value *ret;
    struct aio *aio;
    int error;
};

static void free_open(struct open *const op)
{
    if (!op)
        return;

    aio_free(op->aio);
    free(op->path);
    free(op);
}

static enum nw_state fail(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct open *const op = p->import;

    op->ret->i32 = -1;
    free_open(op);
    return NW_OK;
}

static int set_errno(struct bin_proc *const p, const int error,
    struct nw_next *const next)
{
    struct open *const op = p->import;

    if (op->errno_addr)
    {
        const struct bin_set_errno cfg =
        {
            .addr = op->errno_addr,
            .error = error,
            .p = p
        };

        *next = (const struct nw_next)
        {
            .fn = fail,
            .user = p
        };

        if (bin_wnix_set_errno(&cfg, next))
        {
            free_open(op);
            return -1;
        }
    }
    else
        return fail(p, next);

    return 0;
}

static enum nw_state check_open(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct open *const op = p->import;
    int fd;

    if (op->error)
        return set_errno(p, op->error, next);
    else if ((fd = bin_proc_fd_alloc(p, &op->fd, op->flags)) < 0)
        return set_errno(p, errno, next);

    op->ret->i32 = fd;
    free_open(op);
    return NW_OK;
}

static int opened(const enum state state, void *const args)
{
    struct bin_proc *const p = args;
    struct open *const op = p->import;

    op->error = state ? errno : 0;

    if (bin_proc_unlock(p) || loop_rm_aio(op->aio))
    {
        free_open(op);
        return -1;
    }

    aio_free(op->aio);
    op->aio = NULL;
    return 0;
}

static enum nw_state do_open(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct open *const op = p->import;
    const struct aio_done d =
    {
        .f = opened,
        .args = p
    };

    const struct fs_open fo =
    {
        .flags = op->flags,
        .mode = op->mode,
        .path = op->path,
        .fd = &op->fd,
        .uid = p->uid,
        .gid = p->gid,
        .ss = p->ss
    };

    struct aio *const aio = aio_open(&fo, &d);

    if (!aio || bin_proc_lock(p) || loop_add_aio(aio))
    {
        aio_free(aio);
        free_open(op);
        return NW_FATAL;
    }

    op->aio = aio;
    *next = (const struct nw_next){.fn = check_open, .user = user};
    return NW_AGAIN;
}

static enum nw_state read_path(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct open *const op = p->import;
    const struct bin_readstr cfg =
    {
        .error = set_errno,
        .addr = op->path_addr,
        .str = &op->path,
        .p = p
    };

    *next = (const struct nw_next){.fn = do_open, .user = user};

    if (bin_wnix_readstr(&cfg, next))
    {
        free_open(op);
        return NW_FATAL;
    }

    return NW_AGAIN;
}

static enum nw_state check(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct open *const op = p->import;

    if (!op->path_addr || !op->errno_addr)
        return set_errno(p, EINVAL, next);
    else
        *next = (const struct nw_next){.fn = read_path, .user = user};

    return NW_AGAIN;
}

enum nw_state bin_wnix_open(const union nw_value *const params,
    union nw_value *const ret, void *const user,
    struct nw_next *const next)
{
    enum {PATH, FLAGS, MODE, ERRNO};
    struct open *const op = malloc(sizeof *op);

    if (!op)
        return NW_FATAL;

    *op = (const struct open)
    {
        .path_addr = params[PATH].i32,
        .flags = params[FLAGS].i32,
        .mode = params[MODE].i32,
        .errno_addr = params[ERRNO].i32,
        .ret = ret
    };

    struct bin_proc *const p = user;

    *next = (const struct nw_next){.fn = check, .user = user};
    p->import = op;
    return NW_AGAIN;
}
