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

struct mount
{
    struct mountstr
    {
        unsigned long addr;
        char *str;
    } src, tgt, type;

    unsigned long flags, args, errno_addr;
    struct endian_le32 le32;
    union nw_value *ret;
    struct nw_sm_io io;
    enum state state;
    struct aio *aio;
    int error;
    size_t i;
};

static void free_mount(struct mount *const m)
{
    aio_free(m->aio);
    free(m->src.str);
    free(m->tgt.str);
    free(m->type.str);
    free(m);
}

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

    m->ret->i32 = -1;
    free_mount(m);
    return NW_OK;
}

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

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

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

    return bin_wnix_set_errno(&cfg, next);
}

static enum nw_state mounted(void *const user, struct nw_next *const next)
{
    enum nw_state ret = NW_FATAL;
    struct bin_proc *const p = user;
    struct mount *const m = p->import;

    if (m->state)
    {
        if (set_errno(p, m->error, next))
            goto end;

        return NW_AGAIN;
    }

    m->ret->i32 = 0;
    ret = NW_OK;
end:
    free_mount(m);
    return ret;
}

static int done(const enum state state, void *const args)
{
    struct bin_proc *const p = args;
    struct mount *const m = p->import;

    if (bin_proc_unlock(p))
    {
        free_mount(m);
        return -1;
    }
    else if (state)
        m->error = errno;

    m->state = state;
    return 0;
}

static enum nw_state do_mount(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct mount *const m = p->import;
    const struct aio_mount am =
    {
        .type = m->type.str,
        .mount =
        {
            .src = m->src.str,
            .tgt = m->tgt.str,
            .mode = m->flags,
            .gid = p->gid,
            .uid = p->uid
        }
    };

    const struct aio_done d =
    {
        .f = done,
        .args = p
    };

    if (!(m->aio = aio_mount(&am, &d)))
    {
        if (set_errno(p, errno, next))
            goto failure;

        return NW_AGAIN;
    }
    else if (bin_proc_lock(p))
        goto failure;

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

failure:
    free_mount(m);
    return NW_FATAL;
}

static enum nw_state read_string(struct bin_proc *const p,
    struct mountstr *const ms, struct nw_next *const next)
{
    struct mount *const m = p->import;

    const struct bin_readstr cfg =
    {
        .error = set_errno,
        .addr = ms->addr,
        .str = &ms->str,
        .p = p
    };

    if (bin_wnix_readstr(&cfg, next))
    {
        free_mount(m);
        return NW_FATAL;
    }

    return NW_AGAIN;
}

static enum nw_state read_type(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct mount *const m = p->import;

    *next = (const struct nw_next){.fn = do_mount, .user = user};
    return read_string(p, &m->type, next);
}

static enum nw_state read_tgt(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct mount *const m = p->import;

    *next = (const struct nw_next){.fn = read_type, .user = user};
    return read_string(p, &m->tgt, next);
}

static enum nw_state read_src(void *const user, struct nw_next *const next)
{
    struct bin_proc *const p = user;
    struct mount *const m = p->import;

    *next = (const struct nw_next){.fn = read_tgt, .user = user};
    return read_string(p, &m->src, next);
}

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

    if (!m->src.addr || !m->tgt.addr || !m->type.addr || !m->errno_addr)
    {
        if (m->errno_addr)
        {
            if (set_errno(p, EINVAL, next))
                goto failure;
        }
        else
        {
            m->ret->i32 = -1;
            return NW_OK;
        }
    }
    else
        *next = (const struct nw_next){.fn = read_src, .user = user};

    return NW_AGAIN;

failure:
    free_mount(m);
    return NW_FATAL;
}

enum nw_state bin_wnix_mount(const union nw_value *const params,
    union nw_value *const ret, void *const user,
    struct nw_next *const next)
{
    enum {SRC, TGT, TYPE, FLAGS, ARGS, ERRNO};
    struct mount *const m = malloc(sizeof *m);

    if (!m)
        return NW_FATAL;

    *m = (const struct mount)
    {
        .args = params[ARGS].i32,
        .flags = params[FLAGS].i32,
        .type.addr = params[TYPE].i32,
        .tgt.addr = params[TGT].i32,
        .src.addr = params[SRC].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 = m;
    return NW_AGAIN;
}
