/*
 * 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 <iso9660.h>
#include <iso9660/ops.h>
#include <iso9660/routines.h>
#include <iso9660/types.h>
#include <fs/fs.h>
#include <kprintf.h>
#include <state.h>
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

struct mount
{
    char *src, *tgt;
    struct fs_fd fd;
    struct fs_ret *r;
    struct fs_mount mount;
    struct iso9660_header header;
    struct fs_mp mp;
};

static void free_mount(struct mount *const m)
{
    if (!m)
        return;

    m->mp.fs->close(&m->fd);
    free(m->src);
    free(m->tgt);
    free(m);
}

static enum state check_header(void *const args)
{
    enum state ret = STATE_OK;
    struct mount *const m = args;

    if (iso9660_check_header(&m->header.magic))
        ret = STATE_FATAL;
    else if (fs_mountpoint(m->src, m->tgt, &iso9660, NULL, NULL))
    {
        kprintf("Failed to allocate mountpoint {source=%s, target=%s}\n",
            m->src, m->tgt);
        ret = STATE_FATAL;
    }

    free_mount(m);
    return ret;
}

static enum state read_header(void *const args)
{
    struct mount *const m = args;
    struct fs_fd *const fd = &m->fd;
    const struct fs *const fs = m->mp.fs;
    const struct fs_read r =
    {
        .buf = &m->header,
        .n = sizeof m->header,
        .fd = fd
    };

    if (fd->error)
    {
        errno = fd->error;
        goto failure;
    }

    *m->r = (const struct fs_ret)
    {
        .f = check_header,
        .args = m
    };

    fd->offset = 16 * ISO9660_SECTOR_SZ;

    if (fs->read(&r, m->r))
        goto failure;

    return STATE_AGAIN;

failure:
    free_mount(m);
    return STATE_FATAL;
}

static int search_done(const enum state state, const char *const relpath,
    const struct fs_mp *const mp, const union inode_result *const inode,
    void *const args)
{
    struct mount *const m = args;
    const struct fs_mount *const fm = &m->mount;

    if (!inode)
    {
        errno = ENOENT;
        goto failure;
    }

    const struct fs_open o =
    {
        .fd = &m->fd,
        .gid = fm->gid,
        .uid = fm->uid,
        .mode = fm->mode,
        .path = relpath
    };

    *m->r = (const struct fs_ret)
    {
        .f = read_header,
        .args = m
    };

    if (mp->fs->open(&o, mp, inode, m->r))
        goto failure;

    m->mp = *mp;
    return 0;

failure:
    free_mount(m);
    return -1;
}

int iso9660_mount(const struct fs_mount *const fm, struct fs_ret *const r)
{
    char *srcdup = NULL, *tgtdup = NULL;
    struct mount *const m = malloc(sizeof *m);

    if (!m
        || !(srcdup = strdup(fm->src))
        || !(tgtdup = strdup(fm->tgt)))
        goto failure;

    *m = (const struct mount)
    {
        .src = srcdup,
        .tgt = tgtdup,
        .mount = *fm,
        .r = r
    };

    const struct inode_search s =
    {
        .args = m,
        .path = fm->src,
        .done = search_done
    };

    if (inode_search(&s, r))
        goto failure;

    return 0;

failure:
    free(srcdup);
    free(tgtdup);
    free(m);
    return -1;
}
