/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Initial TAR filesystem (read-only)
 * @license GPL-3.0-only
 */

#include <sched/process.h>
#include <limine.h>
#include <fs/initfs.h>
#include <kernel.h>
#include <mem/utils.h>
#include <string/string.h>

void* archive_start_addr;
uint64_t archive_size;

/*
 * tar_oct2bin - convert octal size string to an integer 
 * @str:  octal size string
 * @size: size of string
 *
 * Return:
 * $n - file size as an integer
 */
int tar_oct2bin(unsigned char* str, int size)
{
    int n = 0;
    unsigned char* c = str;
    while (size-- > 0) {
        n *= 8;
        n += *c - '0';
        c++;
    }
    return n;
}

/*
 * tar_lookup - lookup a file in the TAR file
 * @archive:  pointer to beginning of the archive
 * @filename: file to lookup (absolute path)
 * @out:      where to store file data if found
 *
 * Return:
 * $filesize - size of the file, if found 
 * $-ENOENT  - file not found
 */
int tar_lookup(unsigned char* archive, char* filename, char** out)
{
    unsigned char *ptr = archive;

    while (!memcmp(ptr + 257, "ustar", 5)) {
        int filesize = tar_oct2bin(ptr + 0x7c, 11);
        if (!memcmp(ptr, filename, strlen(filename) + 1)) {
            *out = (char*)(ptr + 512);
            return filesize;
        }
        ptr += (((filesize + 511) / 512) + 1) * 512;
    }
    return -ENOENT;
}

/*
 * tar_list - list all files present in archive
 */
void tar_list()
{
    printf("++ Contents of initial filesystem ++\r\n\r\n");
    unsigned char *ptr = archive_start_addr;

    while (!memcmp(ptr + 257, "ustar", 5)) {
        int filesize = tar_oct2bin(ptr + 0x7c, 11);
        char* filename = (char*)ptr;
        printf("file: %s\r\n", filename);
        ptr += (((filesize + 511) / 512) + 1) * 512;
    }
}

/*
 * tar_read - read a file in the TAR archive
 * @filename: file to read (absolute path)
 * @out:      where to store file data if found
 * @count:    amount of bytes to read
 * @offset:   read from byte offset (0 for none)
 *
 * Return:
 * $filesize - size of the file, if found 
 * $-ENOENT  - file not found
 */
int tar_read(char* filename, char* out, int count, int offset)
{
    char* file_data;
    int filesize = tar_lookup(archive_start_addr, filename, &file_data);

    if (filesize <= 0) {
        return filesize;
    }

    if (offset >= filesize) {
        return -EINVAL;
    }

    int remaining = filesize - offset;
    int to_read = remaining < count ? remaining : count;

    memcpy(out, file_data + offset, to_read);

    return to_read;
}

/*
 * tar_exists - check if a file exists in the TAR archive
 * @filename: file to check (absolute path)
 *
 * Return:
 * $filesize - size of the file, if found 
 * $-ENOENT  - file not found
 */
int tar_exists(const char* filename)
{
    unsigned char* ptr = archive_start_addr;

    while (!memcmp(ptr + 257, "ustar", 5)) {
        int filesize = tar_oct2bin(ptr + 0x7c, 11);
        if (!memcmp(ptr, filename, strlen(filename) + 1)) {
            return filesize;
        }
        ptr += (((filesize + 511) / 512) + 1) * 512;
    }
    return -ENOENT;
}


/*
 * initfs_init - initialize the TAR initial filesystem
 * @tar_file: pointer to the Limine-loaded archive
 *
 * Return:
 * $0       - on success
 */
int initfs_init(struct limine_file* tar_file)
{
    archive_start_addr = tar_file->address;
    archive_size = tar_file->size;

    DEBUG("Loaded TAR initial filesystem (initfs.tar)");
    return 0;
}