/*
 * 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 <init.h>
#include <init/port.h>
#include <init/types.h>
#include <aio.h>
#include <kprintf.h>
#include <loop.h>
#include <stdlib.h>
#include <time.h>

static int retry(struct init *);
enum {RETRIES = 5};
static const char *const paths[] =
{
    "/dev/tty",
    "/dev/null",
    "/dev/stdout",
    "/dev/stdin",
    "/dev/stderr"
};

static int run(struct init *const init)
{
    return init_port_run();
}

static int port(struct init *const init)
{
    const size_t path_i = init->path_i + 1;

    kprintf("%s initilization successful (took %u attempts)\n",
        paths[init->path_i], RETRIES - init->retries + 1);

    if (path_i >= sizeof paths / sizeof *paths)
    {
        if (init_port_boot())
            return -1;

        init->next = run;
    }
    else
        init_vars = (const struct init)
        {
            .retries = RETRIES,
            .next = retry,
            .path_i = path_i
        };

    return 0;
}

static int wait_retry(struct init *const init)
{
    struct timespec ts;
    const time_t last = init->last_retry.tv_sec, timeout = 5;

    if (clock_gettime(CLOCK_REALTIME, &ts))
        return -1;
    else if (ts.tv_sec - last >= timeout)
        init->next = retry;

    return 0;
}

static int stat_done(const enum state s, void *const args)
{
    int ret = 0;
    struct init *const init = args;

    if (s)
    {
        struct timespec ts;

        if (!--init->retries || clock_gettime(CLOCK_REALTIME, &ts))
        {
            ret = -1;
            goto end;
        }

        init->last_retry = ts;
        init->next = wait_retry;
    }
    else
        init->next = port;

end:
    loop_rm_aio(init->aio);
    aio_free(init->aio);
    return ret;
}

static int retry(struct init *const init)
{
    struct aio *aio = NULL;
    const size_t path_i = init->path_i;
    const struct fs_stat s = {.path = paths[init->path_i], .sb = &init->sb};
    const struct aio_done d = {.f = stat_done, .args = init};
    const unsigned retries = init->retries;

    if (!(aio = aio_stat(&s, &d)) || loop_add_aio(aio))
        goto failure;

    *init = (const struct init)
    {
        .aio = aio,
        .retries = retries,
        .path_i = path_i,
        .last_retry = {.tv_sec = (time_t)-1}
    };

    return 0;

failure:
    free(aio);
    return -1;
}

int init_boot(void)
{
    init_vars = (const struct init)
    {
        .retries = RETRIES,
        .next = retry
    };

    return 0;
}
