/*
 * 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 <nanowasm/nw.h>
#include <nanowasm/linear.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

struct argcopy
{
    long arg;
    unsigned long addr, errno_addr;
    union nw_value *ret;
    size_t read;
};

static void free_argcopy(struct argcopy *const c)
{
    if (!c)
        return;

    free(c);
}

static enum nw_state errno_set(void *const user,
    struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct argcopy *const c = p->import;

    c->ret->i32 = -1;
    free_argcopy(c);
    return NW_OK;
}

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

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

    const struct bin_set_errno cfg =
    {
        .addr = c->errno_addr,
        .error = error,
        .p = p
    };

    return bin_wnix_set_errno(&cfg, next);
}

static enum nw_state read_arg(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct argcopy *const c = p->import;
    const char *const arg = p->argv[c->arg];
    const unsigned long addr = c->addr + c->read;
    char b = arg[c->read];
    struct nw_sm_io io = {.buf = &b, .n = sizeof b};
    const enum nw_state n = nw_linear_store(&p->instance, &io, addr);

    if (n)
        return n;
    else if (++c->read >= strlen(arg) + 1)
    {
        c->ret->i32 = 0;
        free_argcopy(c);
        return NW_OK;
    }

    return NW_AGAIN;
}

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

    if (c->arg < 0 || c->arg >= p->argc || !c->addr || !c->errno_addr)
    {
        if (c->errno_addr)
        {
            if (set_errno(p, EINVAL, next))
                goto failure;
        }
        else
        {
            c->ret->i32 = -1;
            return NW_OK;
        }
    }
    else
        *next = (const struct nw_next){.fn = read_arg, .user = user};

    return NW_AGAIN;

failure:
    free_argcopy(c);
    return NW_FATAL;
}

enum nw_state bin_wnix_argcopy(const union nw_value *const params,
    union nw_value *const ret, void *const user,
    struct nw_next *const next)
{
    enum {ARG, ADDR, ERRNO};
    struct argcopy *const c = malloc(sizeof *c);

    if (!c)
        return NW_FATAL;

    *c = (const struct argcopy)
    {
        .arg = params[ARG].i32,
        .addr = params[ADDR].i32,
        .errno_addr = params[ERRNO].i32,
        .ret = ret
    };

    struct bin_proc *const p = user;

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