/*
 * 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 <caio.h>
#include <caio/routines.h>
#include <caio/types.h>
#include <aio.h>
#include <fs/fs.h>
#include <loop.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

static int done(const enum state s, void *const args)
{
    struct caio *const a = args;
    const struct aio_done *const d = &a->done;

    if (loop_rm_aio(a->aio))
        return -1;

    aio_free(a->aio);
    a->aio = NULL;
    return d->f ? d->f(s, d->args) : 0;
}

static size_t read_cache(struct caio *const a, struct caio_cache *const c,
    void *const buf, size_t n)
{
    char *p = buf;
    size_t i = a->offset % sizeof c->buf;

    while (i < c->available && n)
    {
        *p++ = c->buf[i++];
        n--;
    }

    const size_t ret = p - (const char *)buf;

    a->offset += ret;
    return ret;
}

static size_t getrem(const struct caio *const a)
{
    const struct fs_fd *const fd = &a->fd;
    const off_t n = fd->size - fd->offset;
    const size_t sz = sizeof a->caches->buf / sizeof *a->caches->buf;

    return n < sz ? n : sz;
}

static struct caio_cache *rotate(struct caio *const a)
{
    const struct fs_fd *const fd = &a->fd;
    long long maxdiff = LLONG_MAX;
    struct caio_cache *ret = a->caches;

    for (struct caio_cache *c = a->caches; c; c = c->next)
    {
        if (!c->available)
            return c;

        const long long diff = (long long)c->offset - (long long)fd->offset,
            adiff = llabs(diff);

        if (adiff < maxdiff)
        {
            maxdiff = adiff;
            ret = c;
        }
    }

    return ret;
}

static int setup_cache(struct caio *const a, const struct aio_done *const d)
{
    const size_t rem = getrem(a);

    if (!rem)
        return 0;

    struct aio *aio = NULL;
    struct caio_cache *const c = rotate(a);
    const struct fs_read r =
    {
        .fd = &a->fd,
        .buf = c->buf,
        .n = rem
    };

    const struct aio_done dd =
    {
        .f = done,
        .args = a
    };

    const off_t offset = (a->offset / sizeof c->buf) * sizeof c->buf;

    a->fd.offset = offset;

    if (!(aio = aio_read(&r, &dd)) || loop_add_aio(aio))
        goto failure;
    else if (d)
        a->done = *d;
    else
        a->done = (const struct aio_done){0};

    a->aio = aio;
    c->offset = offset;
    c->available = rem;
    return 0;

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

int caio_read(struct caio *const a, void *const buf, const size_t n,
    const struct aio_done *const d)
{
    if (a->offset >= a->fd.size)
        return 0;

    struct caio_cache *const c = caio_cache(a);

    if (c)
    {
        const size_t cr = read_cache(a, c, buf, n);

        if (cr)
            return cr;
    }

    return setup_cache(a, d);
}
