/*
 * 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/mod.h>
#include <bin/proc.h>
#include <bin/routines.h>
#include <bin/types.h>
#include <kprintf.h>
#include <nanowasm/nw.h>
#include <stdbool.h>

static int load(void)
{
    struct bin *const b = &bin_pending;

    for (struct bin_mod *m = b->mod_head; m;)
    {
        struct bin_mod *const next = m->next;

        switch (nw_load(&m->mod, &m->mod_out))
        {
            case NW_AGAIN:
                break;

            case NW_FATAL:
                /* TODO: call nw_loadexc. */
                kprintf("Module %s failed to load\n", m->path);

                if (bin_unload(m))
                    kprintf("Failed to unload module %s\n", m->path);

                bin_mod_free(m);
                break;

            case NW_OK:
                kprintf("Module %s loaded successfully\n", m->path);

                if (bin_start(m))
                    return -1;

                break;
        }

        m = next;
    }

    return 0;
}

static int run(void)
{
    for (struct bin_proc *p = bin_active.proc_head; p;)
    {
        struct bin_proc *const next = p->next;
        struct nw_inst *const i = &p->instance;

        switch (nw_run(i))
        {
            case STATE_AGAIN:
                break;

            case STATE_FATAL:
                kprintf("Instance %s failed to run: %s\n", p->path, nw_rexc(i));

                if (bin_stop(p))
                    kprintf("Failed to stop instance %s\n", p->path);

                break;

            case STATE_OK:
                /* TODO: remove from queue. */
                break;
        }

        p = next;
    }

    return 0;
}

static int prune(const struct bin_mod *const mod)
{
    for (struct bin_mod *m = bin_active.mod_head; m;)
    {
        struct bin_mod *const next = m->next;

        if (m == mod)
        {
            if (bin_mod_move(m, &bin_active, NULL))
                return -1;

            bin_mod_free(m);
            break;
        }

        m = next;
    }

    return 0;
}

static int kill(void)
{
    for (struct bin_proc *p = bin_zombie.proc_head; p;)
    {
        struct bin_proc *const next = p->next;
        const struct bin_mod *const mod = p->mod;
        bool used = false;

        kprintf("%s exited with status %ld\n", p->path, p->retval);

        if (bin_proc_move(p, &bin_zombie, NULL))
            return -1;

        bin_proc_free(p);

        if (!used && prune(mod))
            return -1;

        p = next;
    }

    return 0;
}

int bin_update(void)
{
    return load() || run() || kill();
}
