// 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 "mkfs_fat32.h"
#include "../dev/disk.h"
#include <stddef.h>
#include "memory_manager.h"

extern void serial_write(const char *str);
extern void serial_write_num(uint64_t num);

// Internal helpers

static void mf_memset(void *dst, int val, int len) {
    unsigned char *p = (unsigned char *)dst;
    while (len-- > 0) *p++ = (unsigned char)val;
}

static void mf_memcpy(void *dst, const void *src, int len) {
    unsigned char *d = (unsigned char *)dst;
    const unsigned char *s = (const unsigned char *)src;
    while (len-- > 0) *d++ = *s++;
}

static void mf_strncpy(char *dst, const char *src, int n) {
    int i = 0;
    while (i < n && src[i]) { dst[i] = src[i]; i++; }
    while (i < n) { dst[i++] = ' '; }  /* FAT labels are space-padded */
}

// On-disk BPB structures

typedef struct __attribute__((packed)) {
    /* DOS 2.0 BPB */
    uint8_t  jump_boot[3];          /* EB 58 90 */
    char     oem_name[8];           /* "MSDOS5.0" */
    uint16_t bytes_per_sector;      /* 512 */
    uint8_t  sectors_per_cluster;
    uint16_t reserved_sector_count;
    uint8_t  num_fats;              /* 2 */
    uint16_t root_entry_count;      /* 0 for FAT32 */
    uint16_t total_sectors_16;      /* 0 for FAT32 */
    uint8_t  media;                 /* 0xF8 for fixed disk */
    uint16_t fat_size_16;           /* 0 for FAT32 */
    uint16_t sectors_per_track;
    uint16_t num_heads;
    uint32_t hidden_sectors;
    uint32_t total_sectors_32;

    /* FAT32 extended BPB */
    uint32_t fat_size_32;
    uint16_t ext_flags;
    uint16_t fs_version;            /* 0x0000 */
    uint32_t root_cluster;          /* 2 */
    uint16_t fs_info;               /* sector 1 */
    uint16_t backup_boot_sector;    /* sector 6 */
    uint8_t  reserved[12];
    uint8_t  drive_number;          /* 0x80 for fixed disk */
    uint8_t  reserved1;
    uint8_t  boot_sig;              /* 0x29 */
    uint32_t volume_id;
    char     volume_label[11];      /* space-padded */
    char     fs_type[8];            /* "FAT32   " */
    uint8_t  boot_code[420];
    uint16_t boot_signature;        /* 0xAA55 */
} FAT32_BPB;

typedef struct __attribute__((packed)) {
    uint32_t lead_sig;              /* 0x41615252 */
    uint8_t  reserved1[480];
    uint32_t struct_sig;            /* 0x61417272 */
    uint32_t free_count;            /* 0xFFFFFFFF = unknown */
    uint32_t next_free;             /* 0xFFFFFFFF = unknown */
    uint8_t  reserved2[12];
    uint32_t trail_sig;             /* 0xAA550000 */
} FAT32_FSInfo;

// Public API

int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label) {
    if (sector_count < MIN_FAT32_SECTORS) {
        serial_write("[MKFS] Error: partition too small for FAT32 (< 32 MB)\n");
        return -1;
    }
    if (!disk || !disk->write_sector) {
        serial_write("[MKFS] Error: null disk or no write function\n");
        return -1;
    }

    uint8_t spc;  /* sectors per cluster */
    if      (sector_count <    532480) spc =  1;  /* < 260 MB  -> 512 B clusters */
    else if (sector_count <   1064960) spc =  2;  /* < 520 MB  -> 1 KB */
    else if (sector_count <   2097152) spc =  4;  /* < 1 GB    -> 2 KB */
    else if (sector_count <  16777216) spc =  8;  /* < 8 GB    -> 4 KB */
    else if (sector_count <  33554432) spc = 16;  /* < 16 GB   -> 8 KB */
    else if (sector_count <  67108864) spc = 32;  /* < 32 GB   -> 16 KB */
    else                               spc = 64;  /* >= 32 GB  -> 32 KB */

    const uint32_t reserved_sectors = 32;
    const uint8_t  num_fats = 2;
    const uint32_t root_cluster = 2;

    uint32_t data_sectors = sector_count - reserved_sectors;
    uint32_t cluster_count = data_sectors / spc;
    uint32_t fat_bytes = (cluster_count + 2) * 4;
    uint32_t sectors_per_fat = (fat_bytes + 511) / 512;

    uint8_t *buf = (uint8_t *)kmalloc(512);
    if (!buf) {
        serial_write("[MKFS] Error: out of memory\n");
        return -1;
    }

    FAT32_BPB *bpb = (FAT32_BPB *)buf;
    mf_memset(bpb, 0, 512);

    bpb->jump_boot[0] = 0xEB;
    bpb->jump_boot[1] = 0x58;
    bpb->jump_boot[2] = 0x90;
    mf_memcpy(bpb->oem_name, "MSDOS5.0", 8);
    bpb->bytes_per_sector    = 512;
    bpb->sectors_per_cluster = spc;
    bpb->reserved_sector_count = (uint16_t)reserved_sectors;
    bpb->num_fats            = num_fats;
    bpb->root_entry_count    = 0;
    bpb->total_sectors_16    = 0;
    bpb->media               = 0xF8;
    bpb->fat_size_16         = 0;
    bpb->sectors_per_track   = 63;
    bpb->num_heads           = 255;
    bpb->hidden_sectors      = disk->partition_lba_offset;
    bpb->total_sectors_32    = sector_count;
    bpb->fat_size_32         = sectors_per_fat;
    bpb->ext_flags           = 0;
    bpb->fs_version          = 0;
    bpb->root_cluster        = root_cluster;
    bpb->fs_info             = 1;
    bpb->backup_boot_sector  = 6;
    bpb->drive_number        = 0x80;
    bpb->boot_sig            = 0x29;
    bpb->volume_id           = 0x12345678; /* arbitrary non-zero volume ID */

    /* Volume label */
    const char *vol_label = (label && label[0]) ? label : "NO NAME    ";
    char upper_label[11];
    for (int i = 0; i < 11; i++) {
        if (vol_label[i] == 0) {
            for (int j = i; j < 11; j++) upper_label[j] = ' ';
            break;
        }
        char c = vol_label[i];
        if (c >= 'a' && c <= 'z') c -= 32;
        upper_label[i] = c;
    }
    mf_memcpy(bpb->volume_label, upper_label, 11);
    mf_memcpy(bpb->fs_type, "FAT32   ", 8);

    /* Boot sector signature */
    bpb->boot_signature = 0xAA55;

    /* Write sector 0 (BPB) */
    if (disk->write_sector(disk, 0, buf) != 0) {
        serial_write("[MKFS] Error: failed to write BPB (sector 0)\n");
        kfree(buf);
        return -1;
    }

    FAT32_FSInfo *fsinfo = (FAT32_FSInfo *)buf;
    mf_memset(fsinfo, 0, 512);
    fsinfo->lead_sig   = 0x41615252;
    fsinfo->struct_sig = 0x61417272;
    fsinfo->free_count = 0xFFFFFFFF; 
    fsinfo->next_free  = 0xFFFFFFFF; 
    fsinfo->trail_sig  = 0xAA550000;

    if (disk->write_sector(disk, 1, buf) != 0) {
        serial_write("[MKFS] Error: failed to write FSInfo (sector 1)\n");
        kfree(buf);
        return -1;
    }
    FAT32_BPB *bpb2 = (FAT32_BPB *)buf;
    mf_memset(bpb2, 0, 512);
    mf_memcpy(bpb2->jump_boot, "\xEB\x58\x90", 3);
    mf_memcpy(bpb2->oem_name, "MSDOS5.0", 8);
    bpb2->bytes_per_sector    = 512;
    bpb2->sectors_per_cluster = spc;
    bpb2->reserved_sector_count = (uint16_t)reserved_sectors;
    bpb2->num_fats            = num_fats;
    bpb2->root_entry_count    = 0;
    bpb2->total_sectors_16    = 0;
    bpb2->media               = 0xF8;
    bpb2->fat_size_16         = 0;
    bpb2->sectors_per_track   = 63;
    bpb2->num_heads           = 255;
    bpb2->hidden_sectors      = disk->partition_lba_offset;
    bpb2->total_sectors_32    = sector_count;
    bpb2->fat_size_32         = sectors_per_fat;
    bpb2->ext_flags           = 0;
    bpb2->fs_version          = 0;
    bpb2->root_cluster        = root_cluster;
    bpb2->fs_info             = 1;
    bpb2->backup_boot_sector  = 6;
    bpb2->drive_number        = 0x80;
    bpb2->boot_sig            = 0x29;
    bpb2->volume_id           = 0x12345678;
    mf_memcpy(bpb2->volume_label, upper_label, 11);
    mf_memcpy(bpb2->fs_type, "FAT32   ", 8);
    bpb2->boot_signature = 0xAA55;

    if (disk->write_sector(disk, 6, buf) != 0) {
        serial_write("[MKFS] Error: failed to write backup BPB (sector 6)\n");
        kfree(buf);
        return -1;
    }

    FAT32_FSInfo *fsinfo2 = (FAT32_FSInfo *)buf;
    mf_memset(fsinfo2, 0, 512);
    fsinfo2->lead_sig   = 0x41615252;
    fsinfo2->struct_sig = 0x61417272;
    fsinfo2->free_count = 0xFFFFFFFF;
    fsinfo2->next_free  = 0xFFFFFFFF;
    fsinfo2->trail_sig  = 0xAA550000;

    if (disk->write_sector(disk, 7, buf) != 0) {
        serial_write("[MKFS] Error: failed to write backup FSInfo (sector 7)\n");
        kfree(buf);
        return -1;
    }

    /* Zero both FATs */
    mf_memset(buf, 0, 512);
    for (uint32_t f = 0; f < num_fats; f++) {
        uint32_t fat_start = reserved_sectors + (f * sectors_per_fat);
        for (uint32_t s = 0; s < sectors_per_fat; s++) {
            if (disk->write_sector(disk, fat_start + s, buf) != 0) {
                serial_write("[MKFS] Error: failed to zero FAT\n");
                kfree(buf);
                return -1;
            }
        }
    }

    /* Write markers to both FATs */
    mf_memset(buf, 0, 512);
    uint32_t *fat_buf = (uint32_t *)buf;
    fat_buf[0] = 0x0FFFFFF8; // Media type
    fat_buf[1] = 0x0FFFFFFF; // Reserved
    fat_buf[2] = 0x0FFFFFFF; // Root directory (Cluster 2)

    for (uint32_t f = 0; f < num_fats; f++) {
        uint32_t fat_start = reserved_sectors + (f * sectors_per_fat);
        if (disk->write_sector(disk, fat_start, buf) != 0) {
            serial_write("[MKFS] Error: failed to write FAT markers\n");
            kfree(buf);
            return -1;
        }
    }

    /* Zero root cluster */
    mf_memset(buf, 0, 512);
    uint32_t root_start = reserved_sectors + num_fats * sectors_per_fat;
    for (uint32_t s = 0; s < (uint32_t)spc; s++) {
        if (disk->write_sector(disk, root_start + s, buf) != 0) {
            serial_write("[MKFS] Error: failed to zero root cluster\n");
            kfree(buf);
            return -1;
        }
    }

    kfree(buf);

    serial_write("[MKFS] FAT32 formatted: ");
    serial_write(disk->devname);
    serial_write(" label=");
    char lb[12];
    mf_memcpy(lb, vol_label, 11);
    lb[11] = 0;
    for (int i = 0; i < 11; i++) lb[i] = (lb[i] >= 'a' && lb[i] <= 'z') ? lb[i] - 32 : lb[i];
    for (int i = 10; i >= 0 && lb[i] == ' '; i--) lb[i] = 0;
    serial_write(lb);
    serial_write(" spc=");
    serial_write_num(spc);
    serial_write(" fat_sectors=");
    serial_write_num(sectors_per_fat);
    serial_write("\n");

    return 0;
}
