// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.

#include "bootfs.h"
#include "disk.h"
#include "fat32.h"
#include "../sys/bootfs_state.h"
#include "vfs.h"
#include "core/kutils.h"
#include "core/platform.h"
#include "core/kconsole.h"
#include "memory_manager.h"

extern void serial_write(const char *str);
extern void serial_write_hex(uint64_t value);

typedef struct bootfs_custom_file {
    char name[128];       /* filename relative to /boot, e.g. "boredos.elf" */
    uint8_t *data;
    uint32_t size;
    uint32_t capacity;    /* 0 = read-only initrd pointer; >0 = heap-allocated writable */
    struct bootfs_custom_file *next;
} bootfs_custom_file_t;

typedef struct {
    char path[512];
    int offset;
    bool is_root;
    bool is_metadata_dir;
} bootfs_handle_t;

static void* bootfs_open(void *fs_private, const char *path, const char *mode);
static void bootfs_close(void *fs_private, void *handle);
static int bootfs_read(void *fs_private, void *handle, void *buf, int size);
static int bootfs_write(void *fs_private, void *handle, const void *buf, int size);
static int bootfs_seek(void *fs_private, void *handle, int offset, int whence);
static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max);
static bool bootfs_mkdir(void *fs_private, const char *rel_path);
static bool bootfs_rmdir(void *fs_private, const char *rel_path);
static bool bootfs_unlink(void *fs_private, const char *rel_path);
static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path);
static bool bootfs_exists(void *fs_private, const char *rel_path);
static bool bootfs_is_dir(void *fs_private, const char *rel_path);
static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info);
static uint32_t bootfs_get_position(void *file_handle);
static uint32_t bootfs_get_size(void *file_handle);

static vfs_fs_ops_t bootfs_ops = {
    .open = bootfs_open,
    .close = bootfs_close,
    .read = bootfs_read,
    .write = bootfs_write,
    .seek = bootfs_seek,
    .readdir = bootfs_readdir,
    .mkdir = bootfs_mkdir,
    .rmdir = bootfs_rmdir,
    .unlink = bootfs_unlink,
    .rename = bootfs_rename,
    .exists = bootfs_exists,
    .is_dir = bootfs_is_dir,
    .get_info = bootfs_get_info,
    .get_position = bootfs_get_position,
    .get_size = bootfs_get_size,
};

bootfs_state_t g_bootfs_state = {0};
static char g_limine_conf_path[64] = "";

static bootfs_custom_file_t *bootfs_find_custom(const char *name) {
    bootfs_custom_file_t *f = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
    while (f) {
        if (k_strcmp(f->name, name) == 0) return f;
        f = f->next;
    }
    return NULL;
}

void bootfs_register_file(const char *name, void *data, uint32_t size) {
    if (!name || !data) return;
    bootfs_custom_file_t *f = bootfs_find_custom(name);
    if (!f) {
        f = (bootfs_custom_file_t*)kmalloc(sizeof(bootfs_custom_file_t));
        if (!f) return;
        k_memset(f, 0, sizeof(bootfs_custom_file_t));
        k_strcpy(f->name, name);
        f->next = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
        g_bootfs_state.custom_files = f;
    }
    f->data = (uint8_t*)data;
    f->size = size;
    f->capacity = 0;
}

static bool is_metadata_path(const char *path) {
    if (!path) return false;
    return k_strncmp(path, "metadata", 8) == 0;
}

static bool is_metadata_file(const char *path) {
    if (k_strcmp(path, "metadata/boot_time") == 0) return true;
    if (k_strcmp(path, "metadata/boot_flags") == 0) return true;
    if (k_strcmp(path, "metadata/version") == 0) return true;
    return false;
}

static void* bootfs_open(void *fs_private, const char *path, const char *mode) {
    if (!path) path = "";
    if (path[0] == '/') path++;
    
    bootfs_handle_t *h = (bootfs_handle_t*)kmalloc(sizeof(bootfs_handle_t));
    if (!h) return NULL;
    
    k_memset(h, 0, sizeof(bootfs_handle_t));
    k_strcpy(h->path, path);
    h->offset = 0;
    
    if (path[0] == '\0') {
        h->is_root = true;
    } else if (is_metadata_path(path) && path[8] == '\0') {
        h->is_metadata_dir = true;
    }
    
    return h;
}

static void bootfs_close(void *fs_private, void *handle) {
    if (handle) kfree(handle);
}

static int generate_metadata_content(const char *file, char *buffer, int max_size) {
    if (!buffer || max_size <= 0) return 0;
    
    buffer[0] = '\0';
    int len = 0;
    
    if (k_strcmp(file, "metadata/boot_time") == 0) {
        extern uint32_t wm_get_ticks(void);
        uint32_t ticks = wm_get_ticks();
        
        k_strcpy(buffer, "Boot time: ");
        char time_buf[32];
        k_itoa(g_bootfs_state.boot_time_ms, time_buf);
        k_strcpy(buffer + k_strlen(buffer), time_buf);
        k_strcpy(buffer + k_strlen(buffer), " ms\nTicks: ");
        k_itoa(ticks, time_buf);
        k_strcpy(buffer + k_strlen(buffer), time_buf);
        k_strcpy(buffer + k_strlen(buffer), "\n");
        len = k_strlen(buffer);
    } else if (k_strcmp(file, "metadata/version") == 0) {
        k_strcpy(buffer, "Bootloader: ");
        k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_name);
        k_strcpy(buffer + k_strlen(buffer), "\nVersion: ");
        k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_version);
        k_strcpy(buffer + k_strlen(buffer), "\n");
        len = k_strlen(buffer);
    } else if (k_strcmp(file, "metadata/boot_flags") == 0) {
        k_strcpy(buffer, "Boot flags: 0x");
        char flags_buf[8];
        uint8_t flags = g_bootfs_state.boot_flags;
        int hex_digit = (flags >> 4) & 0xF;
        flags_buf[0] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10);
        hex_digit = flags & 0xF;
        flags_buf[1] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10);
        flags_buf[2] = '\n';
        flags_buf[3] = '\0';
        k_strcpy(buffer + k_strlen(buffer), flags_buf);
        len = k_strlen(buffer);
    }
    
    return len;
}

static int bootfs_read(void *fs_private, void *handle, void *buf, int size) {
    bootfs_handle_t *h = (bootfs_handle_t*)handle;
    if (!h || !buf || size <= 0) return -1;
    
    char *content_buffer = (char*)kmalloc(4096);
    if (!content_buffer) return -1;
    
    int content_len = 0;
    
    if (k_strcmp(h->path, "limine.conf") == 0) {
        k_memcpy(content_buffer, g_bootfs_state.limine_conf, 
                 g_bootfs_state.limine_conf_len);
        content_len = g_bootfs_state.limine_conf_len;
    } else if (k_strcmp(h->path, "kernel") == 0) {
        k_strcpy(content_buffer, "Kernel reference\nSize: ");
        char size_buf[32];
        k_itoa(g_bootfs_state.kernel_size, size_buf);
        k_strcpy(content_buffer + k_strlen(content_buffer), size_buf);
        k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n");
        content_len = k_strlen(content_buffer);
    } else if (k_strcmp(h->path, "initrd") == 0) {
        k_strcpy(content_buffer, "Initial ramdisk reference\nSize: ");
        char size_buf[32];
        k_itoa(g_bootfs_state.initrd_size, size_buf);
        k_strcpy(content_buffer + k_strlen(content_buffer), size_buf);
        k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n");
        content_len = k_strlen(content_buffer);
    } else if (k_strcmp(h->path, "initrd.tar") == 0) {
        kfree(content_buffer);
        if (h->offset >= (int)g_bootfs_state.initrd_size) return 0;
        int avail = (int)g_bootfs_state.initrd_size - h->offset;
        int to_read = (size < avail) ? size : avail;
        k_memcpy(buf, (uint8_t*)g_bootfs_state.initrd_ptr + h->offset, to_read);
        h->offset += to_read;
        return to_read;
    } else if (is_metadata_file(h->path)) {
        content_len = generate_metadata_content(h->path, content_buffer, 4096);
    } else {
        bootfs_custom_file_t *cf = bootfs_find_custom(h->path);
        if (cf) {
            kfree(content_buffer);
            if (h->offset >= (int)cf->size) return 0;
            int avail = (int)cf->size - h->offset;
            int to_read = (avail < size) ? avail : size;
            k_memcpy(buf, cf->data + h->offset, to_read);
            h->offset += to_read;
            return to_read;
        }
        kfree(content_buffer);
        return -1;
    }
    
    // Handle offset and reading
    if (h->offset >= content_len) {
        kfree(content_buffer);
        return 0;
    }
    
    int available = content_len - h->offset;
    int read_size = (available < size) ? available : size;
    
    k_memcpy(buf, content_buffer + h->offset, read_size);
    h->offset += read_size;
    
    kfree(content_buffer);
    return read_size;
}

static int bootfs_write(void *fs_private, void *handle, const void *buf, int size) {
    bootfs_handle_t *h = (bootfs_handle_t*)handle;
    if (!h || !buf || size <= 0) return -1;
    
    if (k_strcmp(h->path, "limine.conf") != 0) {
        return -1; 
    }
    
    int max_write = 2048 - h->offset;
    if (max_write <= 0) return -1;
    
    int write_size = (size < max_write) ? size : max_write;
    k_memcpy(g_bootfs_state.limine_conf + h->offset, buf, write_size);
    h->offset += write_size;
    
    if (h->offset > g_bootfs_state.limine_conf_len) {
        g_bootfs_state.limine_conf_len = h->offset;
    }
    
    if (g_limine_conf_path[0] != '\0') {
        vfs_file_t *fat_conf = vfs_open(g_limine_conf_path, "w");
        if (fat_conf) {
            vfs_write(fat_conf, g_bootfs_state.limine_conf, g_bootfs_state.limine_conf_len);
            vfs_close(fat_conf);
        }
    }
    
    return write_size;
}

static int bootfs_seek(void *fs_private, void *handle, int offset, int whence) {
    bootfs_handle_t *h = (bootfs_handle_t*)handle;
    if (!h) return -1;
    
    switch (whence) {
        case 0: // SEEK_SET
            h->offset = offset;
            break;
        case 1: // SEEK_CUR
            h->offset += offset;
            break;
        case 2: // SEEK_END
            return -1;
        default:
            return -1;
    }
    
    return h->offset;
}

static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max) {
    if (!entries || max <= 0) return 0;
    
    if (!rel_path) rel_path = "";
    if (rel_path[0] == '/') rel_path++;
    
    int count = 0;
    
    if (rel_path[0] == '\0') {
        if (count < max) {
            k_strcpy(entries[count].name, "limine.conf");
            entries[count].size = g_bootfs_state.limine_conf_len;
            entries[count].is_directory = 0;
            count++;
        }
        
        if (count < max) {
            k_strcpy(entries[count].name, "kernel");
            entries[count].size = g_bootfs_state.kernel_size;
            entries[count].is_directory = 0;
            count++;
        }
        
        if (count < max) {
            k_strcpy(entries[count].name, "initrd");
            entries[count].size = g_bootfs_state.initrd_size;
            entries[count].is_directory = 0;
            count++;
        }
        
        if (count < max) {
            k_strcpy(entries[count].name, "initrd.tar");
            entries[count].size = g_bootfs_state.initrd_size;
            entries[count].is_directory = 0;
            count++;
        }
        
        if (count < max) {
            k_strcpy(entries[count].name, "metadata");
            entries[count].size = 0;
            entries[count].is_directory = 1;
            count++;
        }
        bootfs_custom_file_t *cf = (bootfs_custom_file_t*)g_bootfs_state.custom_files;
        while (cf && count < max) {
            k_strcpy(entries[count].name, cf->name);
            entries[count].size = cf->size;
            entries[count].is_directory = 0;
            count++;
            cf = cf->next;
        }
    }
    else if (k_strcmp(rel_path, "metadata") == 0) {
        const char *meta_files[] = {
            "boot_time",
            "boot_flags",
            "version"
        };
        
        for (int i = 0; i < 3 && count < max; i++) {
            k_strcpy(entries[count].name, meta_files[i]);
            entries[count].size = 0;
            entries[count].is_directory = 0;
            count++;
        }
    }
    
    return count;
}

static bool bootfs_mkdir(void *fs_private, const char *rel_path) {
    return false;
}

static bool bootfs_rmdir(void *fs_private, const char *rel_path) {
    if (!rel_path) rel_path = "";
    if (rel_path[0] == '/') rel_path++;
    
    if (k_strcmp(rel_path, "metadata") == 0) {
        return false; /* metadata directory is protected */
    }
    
    return false; /* no other directories to remove */
}

static bool bootfs_unlink(void *fs_private, const char *rel_path) {
    if (!rel_path) return false;
    if (rel_path[0] == '/') rel_path++;
    
    /* Only limine.conf can be deleted */
    if (k_strcmp(rel_path, "limine.conf") != 0) {
        return false;
    }
    
    /* Clear the bootfs state */
    g_bootfs_state.limine_conf[0] = '\0';
    g_bootfs_state.limine_conf_len = 0;
    
    /* Delete from partition */
    extern bool vfs_delete(const char *path);
    
    bool result = vfs_delete("/limine.conf");
    
    if (result) {
        serial_write("[BOOTFS] Deleted limine.conf from partition\n");
    } else {
        serial_write("[BOOTFS] Warning: Could not delete limine.conf from partition\n");
    }
    
    return result;
}

static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path) {
    if (!old_path || !new_path) return false;
    
    const char *old_rel = old_path;
    const char *new_rel = new_path;
    
    if (old_rel[0] == '/') old_rel++;
    if (new_rel[0] == '/') new_rel++;
    
    /* Only limine.conf can be renamed */
    if (k_strcmp(old_rel, "limine.conf") != 0) {
        return false;
    }
    
    /* kernel and initrd are protected */
    if (k_strcmp(new_rel, "kernel") == 0 || k_strcmp(new_rel, "initrd") == 0) {
        return false;
    }
    
    /* metadata directory is protected */
    if (k_strncmp(new_rel, "metadata", 8) == 0) {
        return false;
    }
    
    extern bool vfs_rename(const char *old_path, const char *new_path);
    
    char new_partition_path[256];
    k_strcpy(new_partition_path, "/");
    
    /* Manually append new_rel to new_partition_path */
    int path_len = 0;
    while (new_partition_path[path_len]) path_len++;
    
    int rel_len = 0;
    while (new_rel[rel_len]) rel_len++;
    
    if (path_len + rel_len >= 256) {
        serial_write("[BOOTFS] Error: new path too long\n");
        return false;
    }
    
    k_memcpy(new_partition_path + path_len, new_rel, rel_len + 1);
    
    /* Rename on partition filesystem */
    bool result = vfs_rename("/limine.conf", new_partition_path);
    
    if (result) {
        serial_write("[BOOTFS] Renamed limine.conf to ");
        serial_write(new_rel);
        serial_write("\n");
    } else {
        serial_write("[BOOTFS] Warning: Could not rename limine.conf to ");
        serial_write(new_rel);
        serial_write("\n");
    }
    
    return result;
}

static bool bootfs_exists(void *fs_private, const char *rel_path) {
    if (!rel_path) rel_path = "";
    if (rel_path[0] == '/') rel_path++;
    
    if (rel_path[0] == '\0') return true;
    
    if (k_strcmp(rel_path, "limine.conf") == 0) return true;
    if (k_strcmp(rel_path, "efi") == 0) return true;
    if (k_strcmp(rel_path, "kernel") == 0) return true;
    if (k_strcmp(rel_path, "initrd") == 0) return true;
    if (k_strcmp(rel_path, "initrd.tar") == 0) return true;
    
    if (k_strcmp(rel_path, "metadata") == 0) return true;
    if (is_metadata_file(rel_path)) return true;
    if (bootfs_find_custom(rel_path)) return true;
    
    return false;
}

static bool bootfs_is_dir(void *fs_private, const char *rel_path) {
    if (!rel_path) rel_path = "";
    if (rel_path[0] == '/') rel_path++;
    
    if (rel_path[0] == '\0') return true;
    if (k_strcmp(rel_path, "efi") == 0) return true;
    if (k_strcmp(rel_path, "metadata") == 0) return true; 
    
    return false;
}

static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info) {
    if (!info) return -1;
    if (!rel_path) rel_path = "";
    if (rel_path[0] == '/') rel_path++;
    
    k_memset(info, 0, sizeof(vfs_dirent_t));
    
    if (rel_path[0] == '\0') {
        k_strcpy(info->name, "/");
        info->is_directory = 1;
        return 0;
    }
    
    if (k_strcmp(rel_path, "limine.conf") == 0) {
        k_strcpy(info->name, "limine.conf");
        info->size = g_bootfs_state.limine_conf_len;
        info->is_directory = 0;
        return 0;
    }
    
    if (k_strcmp(rel_path, "kernel") == 0) {
        k_strcpy(info->name, "kernel");
        info->size = g_bootfs_state.kernel_size;
        info->is_directory = 0;
        return 0;
    } else if (k_strcmp(rel_path, "initrd") == 0) {
        k_strcpy(info->name, "initrd");
        info->size = g_bootfs_state.initrd_size;
        info->is_directory = 0;
        return 0;
    } else if (k_strcmp(rel_path, "initrd.tar") == 0) {
        k_strcpy(info->name, "initrd.tar");
        info->size = g_bootfs_state.initrd_size;
        info->is_directory = 0;
        return 0;
    } else if (k_strcmp(rel_path, "metadata") == 0) {
        k_strcpy(info->name, "metadata");
        info->is_directory = 1;
        return 0;
    } else if (k_strcmp(rel_path, "efi") == 0) {
        k_strcpy(info->name, "efi");
        info->is_directory = 1;
        return 0;
    }
    
    if (is_metadata_file(rel_path)) {
        char temp_buf[4096];
        int len = generate_metadata_content(rel_path, temp_buf, 4096);
        k_strcpy(info->name, rel_path + 9); 
        info->size = len;
        info->is_directory = 0;
        return 0;
    }
    
    bootfs_custom_file_t *cf = bootfs_find_custom(rel_path);
    if (cf) {
        k_strcpy(info->name, cf->name);
        info->size = cf->size;
        info->is_directory = 0;
        return 0;
    }
    
    return -1;
}

static uint32_t bootfs_get_position(void *file_handle) {
    bootfs_handle_t *h = (bootfs_handle_t*)file_handle;
    if (!h) return 0;
    return h->offset;
}

static uint32_t bootfs_get_size(void *file_handle) {
    bootfs_handle_t *h = (bootfs_handle_t*)file_handle;
    if (!h) return 0;
    
    if (k_strcmp(h->path, "limine.conf") == 0) {
        return g_bootfs_state.limine_conf_len;
    } else if (k_strcmp(h->path, "kernel") == 0) {
        return g_bootfs_state.kernel_size;
    } else if (k_strcmp(h->path, "initrd") == 0) {
        return g_bootfs_state.initrd_size;
    } else if (k_strcmp(h->path, "initrd.tar") == 0) {
        return g_bootfs_state.initrd_size;
    } else if (is_metadata_file(h->path)) {
        char temp_buf[4096];
        return generate_metadata_content(h->path, temp_buf, 4096);
    }
    
    bootfs_custom_file_t *cf = bootfs_find_custom(h->path);
    if (cf) return cf->size;
    
    return 0;
}

vfs_fs_ops_t* bootfs_get_ops(void) {
    return &bootfs_ops;
}

void bootfs_state_init(void) {
    k_memset(&g_bootfs_state, 0, sizeof(bootfs_state_t));
    
    k_strcpy(g_bootfs_state.bootloader_name, "Limine");
    k_strcpy(g_bootfs_state.bootloader_version, "6.0.0");
    

    g_bootfs_state.limine_conf[0] = '\0';
    g_bootfs_state.limine_conf_len = 0;
    
    g_bootfs_state.kernel_size = 0;
    g_bootfs_state.initrd_size = 0;
    g_bootfs_state.boot_time_ms = 0;
}

void bootfs_init(void) {
    bootfs_state_init();
}

void bootfs_mount_boot_partition(void) {
    int count = disk_get_count();
    Disk *esp = NULL;
    
    for (int i = 0; i < count; i++) {
        Disk *d = disk_get_by_index(i);
        if (d && d->is_esp) {
            esp = d;
            break;
        }
    }
    
    if (esp) {
        void *fs_private = fat32_mount_volume(esp);
        if (fs_private) {
            vfs_mount("/boot/efi", esp->devname, "fat32", fat32_get_realfs_ops(), fs_private);
            serial_write("[BOOTFS] Mounted ESP at /boot/efi\n");
        }
    } else {
        serial_write("[BOOTFS] No ESP found for mounting\n");
    }
}

void bootfs_refresh_from_disk(void) {
    extern vfs_file_t* vfs_open(const char *path, const char *mode);
    extern int vfs_read(vfs_file_t *file, void *buf, int size);
    extern void vfs_close(vfs_file_t *file);
    
    vfs_file_t *boot_conf = vfs_open("/boot/efi/limine.conf", "r");
    if (boot_conf) {
        k_strcpy(g_limine_conf_path, "/boot/efi/limine.conf");
    } else {
        boot_conf = vfs_open("/limine.conf", "r");
        if (boot_conf) {
            k_strcpy(g_limine_conf_path, "/limine.conf");
        }
    }

    if (boot_conf) {
        int bytes_read = vfs_read(boot_conf, g_bootfs_state.limine_conf, 2047);
        if (bytes_read > 0) {
            g_bootfs_state.limine_conf[bytes_read] = '\0';
            g_bootfs_state.limine_conf_len = bytes_read;
            serial_write("[BOOTFS] Loaded limine.conf from ");
            serial_write(g_limine_conf_path);
            serial_write("\n");
        }
        vfs_close(boot_conf);
    } else {
        serial_write("[BOOTFS] Warning: limine.conf not found on disk\n");
    }
}
