#pragma once

#include "ahci.h"
#include "alloc.h"
#include "common.h"
#include "lib/stdio.h"

// FAT32 BPB (BIOS Parameter Block) sits at the start of sector 0
typedef pstruct {
    u8  jump[3];
    u8  oem[8];
    u16 bytes_per_sector;
    u8  sectors_per_cluster;
    u16 reserved_sectors;
    u8  fat_count;
    u16 root_entry_count;   // 0 for FAT32
    u16 total_sectors_16;   // 0 for FAT32
    u8  media_type;
    u16 fat_size_16;        // 0 for FAT32
    u16 sectors_per_track;
    u16 head_count;
    u32 hidden_sectors;
    u32 total_sectors_32;

    // FAT32 extended fields
    u32 fat_size_32;
    u16 ext_flags;
    u16 fs_version;         // must be 0x0000
    u32 root_cluster;       // usually 2
    u16 fs_info_sector;
    u16 backup_boot_sector;
    u8  reserved[12];
    u8  drive_number;
    u8  reserved1;
    u8  boot_signature;     // 0x29
    u32 volume_id;
    u8  volume_label[11];
    u8  fs_type[8];         // "FAT32   "
 } fat32_bpb_t;


typedef pstruct {
    u8 status; // 0x80 = bootable
    u8 chs_first[3]; // legacy
    u8 type; // partition type
    u8 chs_last[3]; // legacy
    u32 lba_start;  // first sector of the partition
    u32 sector_count; // size in sectors
} fat32_mbr_partition_t;

typedef pstruct {
     u8 bootcode[446];
     fat32_mbr_partition_t partitions[4];
     u16 signature;
 } fat32_mbr_t;

 typedef pstruct {
     u32 fat_start;
     u32 data_start;
     u32 sectors_per_cluster;
     u32 root_cluster;
     u32 part_start;
 } fat32_vol_t;

 static fat32_vol_t vol_from_bpb(fat32_bpb_t* bpb, u32 part_start) {
     fat32_vol_t vol;
     vol.part_start = part_start;
     vol.sectors_per_cluster = bpb->sectors_per_cluster;
     vol.fat_start = part_start + bpb->reserved_sectors;
     vol.data_start = vol.fat_start + bpb->fat_count * bpb->fat_size_32;
     vol.root_cluster = bpb->root_cluster;

     return vol;
 }

#define TODO_MAKE_ME_DYNAMIC_BLOCK_SIZE 512

static u32 fat32_next(int port, fat32_vol_t* vol, u32 cluster) {
    u32 fat_off = cluster*4;
    u32 fat_sect = vol->fat_start + fat_off / TODO_MAKE_ME_DYNAMIC_BLOCK_SIZE;
    u32 fat_idx = (fat_off & (TODO_MAKE_ME_DYNAMIC_BLOCK_SIZE-1));

    printf("fat_next: cluster=%u fat_off=%u fat_sect=%u byte_off=%u\n",
        cluster, fat_off, fat_sect, fat_off);

    u8* buff = (u8*) alloc(4096);
    if(ahci_read(port, fat_sect, 2, buff) < 0) {
        printf("fat_next: read failed at lba=%u\n", fat_sect);
        return 0x0FFFFFFF;
    }

    u32* tbl = (u32*) (buff + fat_idx);
    // mask top 4
    return *tbl & 0x0FFFFFFF;
}

static bool fat32_is_eof(u32 cluster) {
    return cluster >= 0x0FFFFFF8;
}

static u64 fat32_cluster_to_lba(fat32_vol_t* vol, u32 cluster) {
    // first 2 are resv.
    
    u64 lba = vol->data_start + (u64)(cluster-2) * vol->sectors_per_cluster;

    return lba;
}

static int fat32_read_cluster(int port, fat32_vol_t* vol, u32 cluster, u8* buff) {
    u64 lba = fat32_cluster_to_lba(vol, cluster);
    return ahci_read(port, lba, vol->sectors_per_cluster, buff);
}

/* directory entries and files */
typedef pstruct {
    u8 name[8];
    u8 ext[3];
    u8 attr;
    u8 resv;
    u8 creation_time_tenth;
    u16 creation_time;
    u16 creation_date;
    u16 access_date;
    u16 cluster_high; // high 16 bits of first cluster
    u16 modify_time;
    u16 modify_date;
    u16 cluster_low; //  low 16 bits of first cluster
    u32 file_size;
} fat32_dirent_t;

enum fat32_attr {
    ATTR_READ_ONLY = 1 << 0,
    ATTR_HIDDEN = 1 << 1,
    ATTR_SYSTEM = 1 << 2,
    ATTR_VOL_ID = 1 << 3,
    ATTR_DIR = 1 << 4,
    ATTR_ARCH = 1 << 5,
    ATTR_LFN = 0xF, // long file name entry
};

static u32 fat32_dirent_cluster(fat32_dirent_t* dirent) {
    return ((u32)dirent->cluster_high << 16) | dirent->cluster_low;
}
