use core::str::{Utf8Error, from_utf8};

use bitflags::bitflags;
use thiserror::Error;

use crate::utils::{read_u16_le, read_u32_le, read_u64_le};

#[derive(Error, Debug)]
pub enum Error {
    #[error("invalid utf8 string encountered ({0})")]
    Utf8(#[from] Utf8Error),
}

/// boot sector describes the exfat volume structure
#[derive(Debug)]
pub struct BootSector {
    /// must be [235, 118, 144]
    pub jump_boot: [u8; 3],

    /// must be "EXFAT   "
    pub file_system_name: heapless::String<8>,

    /// media-relative offset of the partition which hosts this exFAT volume
    pub partition_offset: u64,

    /// size of exFAT volume in sectors
    pub volume_length: u64,

    /// volume-relative sector offset of the first FAT
    pub fat_offset: u32,

    /// fat length in sectors
    pub fat_length: u32,

    /// volume-relative sector offset of the cluster heap
    pub cluster_heap_offset: u32,

    /// number of clusters in the cluster heap
    pub cluster_count: u32,

    /// cluster index of the first cluster of the root directory
    pub first_cluster_of_root_dir: u32,

    /// autogenerated by combining date and time when formatting the volume
    pub volume_serial_number: u32,

    /// major (high byte) and minor (low byte) revision number of the exfat structures
    pub file_system_revision: u16,

    /// status of various file system structures in the exfat volume
    pub volume_flags: VolumeFlags,

    /// num bytes per sector (512 bytes is typical for an SD card)
    pub bytes_per_sector: u16,

    /// number of sectors per cluster (64 is normal for an SD card of 8GB - that equates to 32KB per cluster)
    pub sectors_per_cluster: u8,

    /// number of fats and allocation bitmaps (normally 1 but can be two for TexFAT volumes)
    pub number_of_fats: u8,

    /// 13h drive number for bootstrapping purposes (128 is standard)
    pub drive_select: u8,

    /// percentage of clusters in the cluster heap that are allocated
    pub percent_in_use: u8,
}

impl TryFrom<&[u8; 512]> for BootSector {
    type Error = Error;

    fn try_from(value: &[u8; 512]) -> Result<Self, Self::Error> {
        let mut jump_boot = [0u8; 3]; // should be [0xEB, 0x76, 0x90]
        jump_boot.copy_from_slice(&value[..3]); // should be "EXFAT   "
        let mut file_system_name = heapless::String::<8>::new();
        file_system_name.push_str(from_utf8(&value[3..11])?).ok();
        let partition_offset = read_u64_le::<64, _>(value);
        let volume_length = read_u64_le::<72, _>(value);
        let fat_offset = read_u32_le::<80, _>(value);
        let fat_length = read_u32_le::<84, _>(value);
        let cluster_heap_offset = read_u32_le::<88, _>(value);
        let cluster_count = read_u32_le::<92, _>(value);
        let first_cluster_of_root_dir = read_u32_le::<96, _>(value);
        let volume_serial_number = read_u32_le::<100, _>(value);
        let file_system_revision = read_u16_le::<104, _>(value);
        let volume_flags = VolumeFlags::from_bits_truncate(read_u16_le::<106, _>(value));
        let bytes_per_sector = 1u16 << value[108]; // bytes_per_sector_shift 
        let sectors_per_cluster = 1u8 << value[109]; // sectors_per_cluster_shift
        let number_of_fats = value[110];
        let drive_select = value[111];
        let percent_in_use = value[112];

        Ok(BootSector {
            jump_boot,
            file_system_name,
            partition_offset,
            volume_length,
            fat_offset,
            fat_length,
            cluster_heap_offset,
            cluster_count,
            first_cluster_of_root_dir,
            volume_serial_number,
            file_system_revision,
            volume_flags,
            bytes_per_sector,
            sectors_per_cluster,
            number_of_fats,
            drive_select,
            percent_in_use,
        })
    }
}

bitflags! {
    /// Represents a set of volume flags.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct VolumeFlags: u16 {
        /// The value `FirstFat`, at bit position `0`.
        /// Value 0 for ActiveFat and 1 for TexFAT
        const FirstFat = 0b0000_0001;

        /// The value `VolumeDirty`, at bit position `1`.
        /// Set on mount, restored on unmount
        const VolumeDirty = 0b0000_0010;

        /// The value `MediaFailure`, at bit position `2`.
        /// Set on unrecoverable disk error, cleared after a surface scan.
        const MediaFailure = 0b0000_0100;
    }
}
