#ifndef _INIT_FAT32_H_
#define _INIT_FAT32_H_

#include <fs/fs.h>
#include <drivers/sdhc.h>

#define FAT32_SHORTNAME_SIZE      8 // number of bytes in a FAT32 shortname
#define FAT32_SHORTNAME_EXT_SIZE  3 // number of bytes in a FAT32 shortname extension
#define FAT32_BLOCK_SIZE SDHC_BLOCK_SIZE // static blocksize used by our implementation

#define FAT32_EOFC_MARK 0x0FFFFFF8 // cluster entries equal to or larger than this value mark it as the end of the cluster  chain

#define FAT32_MAX_FILESIZE (0x100000000 - 1) // "your FAT file system driver must not allow a cluster chain to be created that is longer than 0x100000000 bytes, and the last byte of the last cluster in a chain that long cannot be allocated to the file" - FAT32 spec

#define FAT32_ATTR_READ_ONLY 0x01 // Indicates that writes to the file should fail.
#define FAT32_ATTR_HIDDEN    0x02 // Indicates that normal directory listings should not show this file.
#define FAT32_ATTR_SYSTEM    0x04 // Indicates that this is an operating system file.
#define FAT32_ATTR_VOLUME_ID 0x08 // There should only be one “file” on the volume that has this attribute set, and that file must be in the root directory. This name of this file is actually the label for the volume. DIR_FstClusHI and DIR_FstClusLO must always be 0 for the volume label (no data clusters are allocated to the volume label file).
#define FAT32_ATTR_DIRECTORY 0x10 // Indicates that this file is actually a container for other files.
#define FAT32_ATTR_ARCHIVE   0x20 // This attribute supports backup utilities. This bit is set by the FAT file system driver when a file is created, renamed, or written to. Backup utilities may use this attribute to indicate which files on the volume have been modified since the last time that a backup was performed.
#define FAT32_ATTR_LONG_NAME (FAT32_ATTR_READ_ONLY|FAT32_ATTR_HIDDEN|FAT32_ATTR_SYSTEM|FAT32_ATTR_VOLUME_ID)

/* block driver functions used by the filesystem to operate on the disk (and synchronize) */
typedef errval_t (*fat_block_read_fn_t)(uint32_t sector_number, size_t offset, size_t size, void *dst);
typedef errval_t (*fat_block_write_fn_t)(uint32_t sector_number, size_t offset, size_t size, const void *src);
typedef errval_t (*fat_block_lock_fn_t)(void);
typedef errval_t (*fat_block_unlock_fn_t)(void);
typedef errval_t (*fat_block_register_handle_fn_t)(uint64_t directory_entry_id);
typedef errval_t (*fat_block_unregister_handle_fn_t)(uint64_t directory_entry_id);
typedef errval_t (*fat_block_count_handles_fn_t)(uint64_t directory_entry_id, size_t *count);

#pragma pack(1)
/**
 * @brief FAT32 specific part of the boot sector
 */
struct bpb_fat32 {
    /**
     * @brief count of sectors occupied by a single FAT
     */
    uint32_t fat_sectors_32_count;
    /**
     * @brief only valid if mirror flag is set. zero based index of active FAT
     */
    uint16_t ext_flag_active_fat:4;
    uint16_t _ext_flag_reserved_1:3;
    /**
     * @brief 0 -> FAT is mirrored to all FATs, 1-> single FAT is active (specified in bits 0-3)
     */
    uint16_t ext_flag_mirrored:1;
    uint16_t _ext_flag_reserved_2:8;
    /**
     * @brief Currently only version 0 specified
     */
    uint16_t fs_version;
    /**
     * @brief cluster number of the first cluster of the root directory
     * 
     * usually 2
     */
    uint32_t root_cluster_number;
    /**
     * @brief sector number of the fs infor structure in the reserved area of the FAT32 volume
     * 
     * usually 1
     */
    uint16_t fs_info_sector_number;
    /**
     * @brief if non-zero indicates the sector in the reserved area of the volume containing a copy of the bpb
     * 
     * usually 6
     */
    uint16_t backup_boot_sector_number;
    uint8_t _reserved[12];
    /**
     * @brief int 0x13 drive number
     * 
     * don't think we need this
     */
    uint8_t drive_number;
    uint8_t _reserved1;
    /**
     * @brief indicates that the next 3 fields are present
     * 0x29 means that the next 3 fields are present? no other values specified...
     */
    uint8_t extended_boot_signature;
    /**
     * @brief together with volume_label allows volume tracking on removable media
     * 
     */
    uint32_t volume_serial_number;
    /**
     * @brief same as the 11-byte volume label recorded in the root directory
     * "NO NAME    " means that there is no label defined
     */
    char volume_label[11];
    /**
     * @brief Always set to "FAT32   " for FAT32 file systems
     * 
     * MUST NOT be used for file system type determination.
     * just seems to be here for fun?
     */
    char file_system_type[8];
};

#pragma pack(1)
union bpb_fat {
    struct bpb_fat32 bpb_fat32;
};

#pragma pack(1)
/**
 * @brief Boot sector and BPB data at the start of the disk
 */
struct bpb {
    uint8_t jmp_boot[3];
    unsigned char oem_name[8];
    /**
     * @brief number of bytes in a sector
     * We expect this to be 512
     */
    uint16_t bytes_per_sector;
    /**
     * @brief number of consecutive sectors making up a cluster
     */
    uint8_t sectors_per_cluster;
    /**
     * @brief number of sectors before the first FAT
     */
    uint16_t reserved_sector_count;
    /**
     * @brief number of consecutive FAT copies (there is only one "main" FAT but n-1 copies)
     * should be 2 in most cases but can be any value >= 1
     */
    uint8_t fat_count;
    /**
     * @brief MUST be 0 for FAT32
     */
    uint16_t root_entry_count;
    /**
     * @brief MUST be 0 for FAT32
     */
    uint16_t total_sector_16_count;
    /**
     * @brief valid values: 0xF0 0xF8 0xF9 0xFA 0xFB 0xFC 0xFD 0xFE 0xFF
     * not really used anymore
     */
    uint8_t media;
    /**
     * @brief MUST be 0 for FAT32
     */
    uint16_t fat_sectors_16_count;
    /**
     * @brief unsure. something to do with interrput 0x13
     * not used by us
     */
    uint16_t sectors_per_track;
    /**
     * @brief unsure. something to do with interrput 0x13
     * not used by us
     */
    uint16_t number_of_heads;
    /**
     * @brief number of sectors preceding the partition containing this FAT volume
     * 
     * should be 0 for non-partitioned devices, operating system specific, mainly relevant for 0x13 interrupt
     * 
     * not used by us
     */
    uint32_t hidden_sector_count;
    /**
     * @brief total count of sectors on the volume
     */
    uint32_t total_sector_32_count;
    /**
     * @brief FAT type specific information
     */
    union bpb_fat bpb_fat;
};
STATIC_ASSERT(sizeof(struct bpb) <= 512-2, "BPB struct may be at most 512-2 bytes long");

#pragma pack(1)
/**
 * @brief structure to speed up free cluster searches and free space checks
 * 
 * not used or updated by us
 */
struct fat32_fsinfo {
    /**
     * @brief Value 0x41615252
     */
    uint32_t lead_signature;
    uint8_t _reserved1[480];
    /**
     * @brief Value 0x61417272
     */
    uint32_t structure_signature;
    /**
     * @brief number of free clusters
     * 0xFFFFFFFF means free count is unknown
     */
    uint32_t free_count;
    /**
     * @brief just a hint at which we should start looking for free clusters
     * 0xFFFFFFFF means not set
     */
    uint32_t next_free;
    uint8_t _reserved2[12];
    /**
     * @brief Value 0xAA550000
     */
    uint32_t trail_signature;
};

#pragma pack(1)
/**
 * @brief Single entry of a directory
 * a directory is like a file but containing a list of structs of this type
 * 
 */
struct fat32_directory_entry {
    /**
     * @brief name of a directory
     * DIR_Name[0] == 0xE5, then the directory entry is free
     * If DIR_Name[0] == 0x00, then the directory entry is free (same as for 0xE5), and there are no allocated directory entries after this on
     * If DIR_Name[0] == 0x05, then the actual file name character for this byte is 0xE5. 0xE5 is actually a valid KANJI lead byte value for the character set used in Japan. The special 0x05 value is used so that this special file name case for Japan can be handled properly and not cause FAT file system code to think that the entry is free.
     * 
     * The DIR_Name field is actually broken into two parts+ the 8-character main part of the name, and the 3-character extension. These two parts are “trailing space padded” with bytes of 0x20.
     * DIR_Name[0] may not equal 0x20. There is an implied ‘.’ character between the main part of the name and the extension part of the name that is not present in DIR_Name
     * Lower case characters are not allowed in DIR_Name (what these characters are is country specific). The following characters are not legal in any bytes of DIR_Name:
     * - Values less than 0x20 except for the special case of 0x05 in DIR_Name[0] described above.
     * - 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, and 0x7C
     */
    char short_name[FAT32_SHORTNAME_SIZE];
    char short_name_ext[FAT32_SHORTNAME_EXT_SIZE];
    /**
     * @brief upper two bits are reserved and should be 0
     * Flags are available as FAT32_ATTR_ defines
     */
    uint8_t attributes;
    uint8_t nt_reserved;
    uint8_t _unknown[7];
    
    uint16_t first_cluster_number_high_word;
    /**
     * @brief Time of last write
     */
    uint16_t write_time;
    /**
     * @brief Date of last write
     */
    uint16_t write_date;
    uint16_t first_cluster_number_low_word;
    /**
     * @brief file size in bytes
     * zero for directories
     */
    uint32_t file_size;
};
STATIC_ASSERT(sizeof(struct fat32_directory_entry) == 32, "FAT32 Directory entries are 32 bytes in size");

/**
 * @brief Reference to a directory entry
 */
struct fat32_directory_entry_ref {
    /**
     * @brief sector number where the directory entry is contained
     */
    uint32_t sector_number;
    /**
     * @brief index of the directory entry in the sector
     */
    size_t index_in_sector;
};

/**
 * @brief Struct for an open fat32 file system
 * 
 * Note: We don't format file systems so the boot sector never changes
 * look at the bpb and bpb_fat32 structs for more info on the fields contained in here
 */
struct fat32 {

    // block driver functions
    fat_block_read_fn_t read_object_fn;
    fat_block_write_fn_t write_object_fn;
    fat_block_lock_fn_t lock_fn;
    fat_block_lock_fn_t unlock_fn;
    fat_block_register_handle_fn_t register_handle_fn;
    fat_block_unregister_handle_fn_t unregister_handle_fn;
    fat_block_count_handles_fn_t count_handles_fn;

    // very primitive implementation of mounting
    char *mount;

    // metadata about the file system
    uint16_t bytes_per_sector;
    uint8_t sectors_per_cluster;
    uint16_t reserved_sector_count;
    uint8_t fat_count;
    uint32_t total_sector_32_count;
    uint32_t fat_sectors_32_count;
    uint8_t ext_flag_active_fat:4;
    uint8_t ext_flag_mirrored:1;
    uint32_t root_cluster_number;

    // information calculated during init

    /**
     * @brief the first sector containing actual directory or file data
     */
    uint32_t first_data_sector;
    /**
     * @brief the largest valid cluster number
     */
    uint32_t max_cluster_number;
    /**
     * @brief the number of bytes in a cluster
     */
    size_t bytes_per_cluster;
};

/**
 * @brief handle to a file or directory
 */
struct fat32_handle {
    /**
     * @brief indicates whether this is a directory or file handle
     */
    bool is_dir;

    /**
     * @brief the first cluster of this directory or file for convenience purposes
     */
    uint32_t first_cluster;
    /**
     * @brief the how manieth cluster in the chain is currently stored in the current_cluster field (0-indexed)
     */
    size_t current_cluster_index_in_chain;
    /**
     * @brief cluster containing the current byte offset
     *
     * valid if != 0
     */
    uint32_t current_cluster;
    /**
     * @brief current byte offset in the file or directory
     */
    size_t byte_offset;

    /**
     * @brief reference to the directory entry for this file or directory
     */
    struct fat32_directory_entry_ref directory_entry_ref;
};

#define FAT32_FAT_ENTRIES_PER_SECTOR (SDHC_BLOCK_SIZE / sizeof(uint32_t)) // number of cluster chain entries that are in a sector
STATIC_ASSERT(FAT32_FAT_ENTRIES_PER_SECTOR * sizeof(uint32_t) == SDHC_BLOCK_SIZE, "fat does not align with blocks");
#define FAT32_DIRECTORY_ENTRIES_PER_SECTOR (SDHC_BLOCK_SIZE / sizeof(struct fat32_directory_entry)) // number of directory entry structures that are in a sector
STATIC_ASSERT(FAT32_DIRECTORY_ENTRIES_PER_SECTOR * sizeof(struct fat32_directory_entry) == SDHC_BLOCK_SIZE, "directories do not align with blocks");

/**
 * @brief Open a fat32 file system by initializing the fat32 structure
 * 
 * @param return_fat32 fat32 structure to initialize
 * @param read_block_fn block driver function to read parts of a block
 * @param write_block_fn block driver function to write parts of a block
 * @param lock_fn block driver function to lock the block driver for exclusive access
 * @param unlock_fn block driver function to unlock the block driver
 * @param register_handle_fn block driver function to register a handle to a directory entry
 * @param unregister_handle_fn block driver function to unregister a registered handle to a directory entry
 * @param count_handles_fn block driver function to get the number of handles to a directory entry
 * @param mount literal path prefix that should be ignored
 * @return errval_t 
 */
errval_t fat32_init(
    struct fat32 **return_fat32,
    fat_block_read_fn_t read_block_fn,
    fat_block_write_fn_t write_block_fn,
    fat_block_lock_fn_t lock_fn,
    fat_block_unlock_fn_t unlock_fn,
    fat_block_register_handle_fn_t register_handle_fn,
    fat_block_unregister_handle_fn_t unregister_handle_fn,
    fat_block_count_handles_fn_t count_handles_fn,
    char *mount
);

// dirs
errval_t fat32_mkdir(struct fat32 *fat32, const char *path);
errval_t fat32_rmdir(struct fat32 *fat32, const char *path);
errval_t fat32_opendir(struct fat32 *fat32, const char *path, struct fat32_handle **dir_handle);
errval_t fat32_closedir(struct fat32 *fat32, struct fat32_handle *dir_handle);
errval_t fat32_stat(struct fat32 *fat32, struct fat32_handle *dir_handle, struct fs_fileinfo *fileinfo);
errval_t fat32_readdir(struct fat32 *fat32, struct fat32_handle *dir_handle, char **name);
errval_t fat32_rm(struct fat32 *fat32, const char *path);

// files
errval_t fat32_fopen(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle);
errval_t fat32_fcreate(struct fat32 *fat32, const char *path, struct fat32_handle **file_handle);
errval_t fat32_fread(struct fat32 *fat32, struct fat32_handle *file_handle, void *buffer, size_t bytes, size_t *bytes_read);
errval_t fat32_fwrite(struct fat32 *fat32, struct fat32_handle *file_handle, const void *buffer, size_t bytes, size_t *bytes_written);
errval_t fat32_fclose(struct fat32 *fat32, struct fat32_handle *file_handle);
errval_t fat32_seek(struct fat32 *fat32, struct fat32_handle *file_handle, enum fs_seekpos whence, off_t offset);
errval_t fat32_tell(struct fat32 *fat32, struct fat32_handle *file_handle, size_t *pos);

#endif /* _INIT_FAT32_H_ */