/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   File-related system calls
 * @license GPL-3.0-only
 */

#include <stddef.h>
#include <kernel.h>
#include <config.h>
#include <sched/process.h>
#include <string/string.h>
#include <fs/initfs.h>
#include <io/kbd/ps2.h>

extern struct process* current_process;

/*
 * normalize_path - remove leading slashes from a path
 * @path: path to normalize
 *
 * Return:
 * %path - normalized path
 * %NULL - if bad argument
 */
static const char* normalize_path(const char* path)
{
    if (!path) return NULL;

    while (*path == '/') {
        path++;
    }

    return path;
}

/*
 * sys_open - Open a file
 * @filename: Absolute path to file
 * @flags:    (Not implemented)
 *
 * This system call opens a file in read-only mode,
 * because of the TAR filesystem.
 *
 * Return:
 * %fd - file descriptor refering to file
 * On error, a negative number representing the error code is returned.
 */
int sys_open(const char* filename, int flags)
{
    // TODO: support flags (right now everything is read only, O_RDONLY)
    (void)flags; // gcc shut up
    const char* path = normalize_path(filename);

    if (tar_exists(path) < 0) {
        return -ENOENT; // file doesn't exist..
    }
    // file exists here!
    if (current_process->next_free_fd >= FDT_MAX) {
        return -EMFILE;
    }
    int fd = current_process->next_free_fd++;
    current_process->fdt[fd].fd = fd;
    current_process->fdt[fd].open = true;
    current_process->fdt[fd].cursor = 0;
    strncpy(current_process->fdt[fd].filename, path, PROCESS_NAME_MAX - 1);
    return fd;
}

/*
 * sys_close - Close a file
 * @fd: file descriptor
 *
 * This system call closes the file referred to by
 * the file descriptor @fd.
 *
 * Return:
 * %0       - file closed
 * %-EBADFD - bad file descriptor
 */
int sys_close(int fd)
{
    if (fd < 0 || fd >= FDT_MAX) {
        return -EBADFD;
    }

    if (!current_process->fdt[fd].open) {
        return -EBADFD; // FD not opened in the first place
    }

    current_process->fdt[fd].open = false;
    current_process->fdt[fd].filename[0] = '\0';
    current_process->fdt[fd].cursor = 0;
    return 0;
}

/*
 * sys_lseek - reposition file cursor
 * @fd:     file descriptor referring to file
 * @offset: byte amount to be used according to @whence
 * @whence: resposition directive
 *
 * This system call repositions the cursor for @fd by @offset,
 * from somewhere in the file, depending on the value of @whence:
 * SEEK_SET (0) -> from the beginning
 * SEEK_CUR (1) -> from the actual value of the cursor
 * SEEK_END (2) -> from the end of the file
 *
 * Return:
 * %new_cursor - the new cursor value
 * On error, a negative value corresponding to an error code is returned.
 */
int sys_lseek(int fd, int offset, int whence)
{
    if (fd < 0 || fd >= FDT_MAX) {
        return -EBADFD;
    }
    if (!current_process->fdt[fd].open) {
        return -EBADFD;
    }

    int filesize = tar_exists(current_process->fdt[fd].filename);
    if (filesize < 0) {
        return -ENOENT;
    }

    int cursor = current_process->fdt[fd].cursor;
    int base;
    switch (whence) {
        case SEEK_SET: base = 0; break;
        case SEEK_CUR: base = cursor; break;
        case SEEK_END: base = filesize; break;
        default: return -EINVAL;
    }

    int new_cursor = base + offset;
    if (new_cursor < 0) new_cursor = 0;
    if (new_cursor > filesize) new_cursor = filesize;

    current_process->fdt[fd].cursor = new_cursor;
    return new_cursor;
}

/*
 * sys_tell - Get cursor position for a file
 * @fd: file descriptor to use
 *
 * Return:
 * %cursor - cursor position of the file descriptor
 * On error, a negative value with the corresponding error code is set.
 */
int sys_tell(int fd)
{
    if (fd < 0 || fd >= FDT_MAX) {
        return -EBADFD;
    }
    if (!current_process->fdt[fd].open) {
        return -EBADFD;
    }
    return current_process->fdt[fd].cursor;
}

/*
 * sys_eof - Are we at the end of the file yet?
 * @fd: file descriptor to use
 *
 * This function determines if the cursor for the
 * file descriptor @fd is at or past the end of
 * the file it refers to.
 *
 * Return:
 * %>0 - we are at or past EOF
 * %0  - not yet (still have bytes to read)
 */
int sys_eof(int fd)
{
    if (fd < 0 || fd >= FDT_MAX) {
        return -EBADFD;
    }
    if (!current_process->fdt[fd].open) {
        return -EBADFD;
    }

    int filesize = tar_exists(current_process->fdt[fd].filename);
    if (filesize < 0) {
        return -ENOENT;
    }

    return current_process->fdt[fd].cursor >= (uint64_t)filesize;
}

/*
 * sys_read - read from an open file
 * @fd:    file descriptor to use
 * @buf:   out buffer for read data
 * @count: amount of bytes to read
 *
 * Return:
 * %sz - amount of bytes read (on success)
 * On error, a negative value with an error code is returned.
 */
int sys_read(unsigned int fd, char* buf, size_t count)
{
    size_t i;
    switch (fd) {
        case 0: //read from stdin (keyboard)
            for (i=0; i<count; i++) {
                buf[i] = keyboard_getchar();
            }
            return i;
        case 1: // from stdout
        case 2: // from stderr
            return -EBADFD;
        default: // from an open file?
            if (current_process->fdt[fd].open == false) {
                return -EBADFD; // File descriptor wasn't open
            }
            // Here fd refers to a valid opened file..
            int sz = tar_read(current_process->fdt[fd].filename, buf, count,
                            current_process->fdt[fd].cursor);
            if (sz == 0) {
                return -ENOENT;
            } else {
                current_process->fdt[fd].cursor += sz;
                return sz;
            }
    }
    return -EBADFD;
}

/*
 * sys_write - write to a file
 * @fd:    file descriptor to write to
 * @buf:   buffer of bytes to write
 * @count: number of bytes to write
 *
 * Return:
 * %count - number of bytes written
 * On error, a negative value with corresponding error code is returned.
 */
int sys_write(unsigned int fd, const char* buf, size_t count)
{
    switch (fd) {
        case 1: //stdout
        case 2: //stderr
            for (size_t i=0; i<count; i++) {
                internal_putc(buf[i], NULL);
            }
            return count;
        default:
            return -EBADFD;
    }
}