/*
 * 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/types.h>
#include <bin/wnix/routines.h>
#include <endian.h>
#include <nanowasm/nw.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

struct readstr
{
    char *s;
    size_t i;
    struct bin_readstr cfg;
    struct nw_next next;
};

static void free_readstr(struct readstr *const rs)
{
    if (!rs)
        return ;

    free(rs->s);
    free(rs);
}

static enum nw_state read_str(struct readstr *const rs)
{
    char b;
    struct nw_sm_io io = {.buf = &b, .n = sizeof b};
    const struct bin_readstr *const cfg = &rs->cfg;
    struct bin_proc *const p = cfg->p;
    const enum nw_state n = nw_linear_load(&p->instance, &io, cfg->addr + rs->i);

    if (n)
        return n;

    char *const s = realloc(rs->s, rs->i + 1);

    if (!s)
        return NW_FATAL;

    s[rs->i++] = b;
    rs->s = s;
    return b ? NW_AGAIN : NW_OK;
}

static enum nw_state check_read_str(void *const user,
    struct nw_next *const next)
{
    struct readstr *const rs = user;
    const struct bin_readstr *const cfg = &rs->cfg;
    struct bin_proc *const p = cfg->p;
    enum nw_state ret = NW_AGAIN;
    enum {PATHMAX = 128};

    if (rs->i >= PATHMAX)
    {
        if (rs->cfg.error(p, ENAMETOOLONG, next))
            ret = NW_FATAL;
    }
    else
    {
        switch (read_str(rs))
        {
            case NW_FATAL:
                if (rs->cfg.error(p, errno, next))
                    ret = NW_FATAL;

                break;

            case NW_OK:
            {
                char *const s = strdup(rs->s);

                if (!s)
                    ret = NW_FATAL;
                else
                {
                    *cfg->str = s;
                    *next = rs->next;
                }
            }
                break;

            case NW_AGAIN:
                return NW_AGAIN;
        }
    }

    free_readstr(rs);
    return ret;
}

int bin_wnix_readstr(const struct bin_readstr *const cfg,
    struct nw_next *const next)
{
    struct readstr *const rs = malloc(sizeof *rs);

    if (!rs)
        return -1;

    *rs = (const struct readstr)
    {
        .cfg = *cfg,
        .next = *next
    };

    *next = (const struct nw_next){.fn = check_read_str, .user = rs};
    return 0;
}
