//! The Block Group Descriptor is the second field of Ext4 Block Group.
//!
//! | Super Block | Group Descriptor | Reserved GDT Blocks |
//! | Block Bitmap | Inode Bitmap | Inode Table | Data Blocks |
//!
//! See [`super`] for more information.

use super::crc::*;
use super::AsBytes;
use super::MetadataChecksumSeed;
use crate::prelude::*;

/// The Block Group Descriptor.
///
/// Each block group on the filesystem has one of these descriptors associated with it.
/// In ext2, ext3, and ext4 (when the 64bit feature is not enabled), the block group
/// descriptor was only 32 bytes long and therefore ends at bg_checksum. On an ext4
/// filesystem with the 64bit feature enabled, the block group descriptor expands to
/// at least the 64 bytes described below; the size is stored in the superblock.
///
/// We only implement the 64 bytes version for simplicity. Guarantee that `sb.desc_size`
/// equals to 64. This value will be checked when loading the filesystem.
#[derive(Debug, Default, Clone, Copy)]
#[repr(C, packed)]
pub struct BlockGroupDesc {
    block_bitmap_lo: u32,            // 块位图块
    inode_bitmap_lo: u32,            // 节点位图块
    inode_table_first_block_lo: u32, // 节点表块
    free_blocks_count_lo: u16,       // 空闲块数
    free_inodes_count_lo: u16,       // 空闲节点数
    used_dirs_count_lo: u16,         // 目录数
    flags: u16,                      // EXT4_BG_flags (INODE_UNINIT, etc)
    exclude_bitmap_lo: u32,          // 快照排除位图
    block_bitmap_csum_lo: u16,       // crc32c(s_csum_seed+bbitmap) LE
    inode_bitmap_csum_lo: u16,       // crc32c(s_csum_seed+ibitmap) LE
    itable_unused_lo: u16,           // 未使用的节点数
    checksum: u16,                   // crc16(sb_uuid+group+desc)

    block_bitmap_hi: u32,            // 块位图块 MSB
    inode_bitmap_hi: u32,            // 节点位图块 MSB
    inode_table_first_block_hi: u32, // 节点表块 MSB
    free_blocks_count_hi: u16,       // 空闲块数 MSB
    free_inodes_count_hi: u16,       // 空闲节点数 MSB
    used_dirs_count_hi: u16,         // 目录数 MSB
    itable_unused_hi: u16,           // 未使用的节点数 MSB
    exclude_bitmap_hi: u32,          // 快照排除位图 MSB
    block_bitmap_csum_hi: u16,       // crc32c(s_csum_seed+bbitmap) BE
    inode_bitmap_csum_hi: u16,       // crc32c(s_csum_seed+ibitmap) BE
    reserved: u32,                   // 填充
}

unsafe impl AsBytes for BlockGroupDesc {}

impl BlockGroupDesc {
    #[allow(unused)]
    const MIN_BLOCK_GROUP_DESC_SIZE: usize = 32;
    #[allow(unused)]
    const MAX_BLOCK_GROUP_DESC_SIZE: usize = 64;

    pub fn block_bitmap_block(&self) -> PBlockId {
        ((self.block_bitmap_hi as PBlockId) << 32) | self.block_bitmap_lo as PBlockId
    }

    pub fn inode_bitmap_block(&self) -> PBlockId {
        ((self.inode_bitmap_hi as PBlockId) << 32) | self.inode_bitmap_lo as PBlockId
    }

    pub fn itable_unused(&self) -> u32 {
        ((self.itable_unused_hi as u32) << 16) | self.itable_unused_lo as u32
    }

    pub fn used_dirs_count(&self) -> u32 {
        ((self.used_dirs_count_hi as u32) << 16) | self.used_dirs_count_lo as u32
    }

    pub fn set_used_dirs_count(&mut self, cnt: u32) {
        self.used_dirs_count_lo = cnt as u16;
        self.used_dirs_count_hi = (cnt >> 16) as u16;
    }

    pub fn set_itable_unused(&mut self, cnt: u32) {
        self.itable_unused_lo = cnt as u16;
        self.itable_unused_hi = (cnt >> 16) as u16;
    }

    pub fn set_free_inodes_count(&mut self, cnt: u32) {
        self.free_inodes_count_lo = cnt as u16;
        self.free_inodes_count_hi = (cnt >> 16) as u16;
    }

    pub fn free_inodes_count(&self) -> u32 {
        ((self.free_inodes_count_hi as u32) << 16) | self.free_inodes_count_lo as u32
    }

    pub fn inode_table_first_block(&self) -> PBlockId {
        ((self.inode_table_first_block_hi as u64) << 32) | self.inode_table_first_block_lo as u64
    }

    pub fn get_free_blocks_count(&self) -> u64 {
        ((self.free_blocks_count_hi as u64) << 16) | self.free_blocks_count_lo as u64
    }

    pub fn set_free_blocks_count(&mut self, cnt: u64) {
        self.free_blocks_count_lo = cnt as u16;
        self.free_blocks_count_hi = (cnt >> 16) as u16;
    }

    fn bitmap_csum(seed: MetadataChecksumSeed, bytes: &[u8], len: usize) -> Option<u32> {
        let covered = bytes.get(..len)?;
        Some(seed.crc32c(covered))
    }

    pub fn verify_inode_bitmap_csum(
        &self,
        seed: MetadataChecksumSeed,
        bytes: &[u8],
        len: usize,
    ) -> bool {
        Self::bitmap_csum(seed, bytes, len).is_some_and(|csum| {
            csum == (self.inode_bitmap_csum_lo as u32 | ((self.inode_bitmap_csum_hi as u32) << 16))
        })
    }

    pub fn update_inode_bitmap_csum(
        &mut self,
        seed: MetadataChecksumSeed,
        bytes: &[u8],
        len: usize,
    ) -> bool {
        let Some(csum) = Self::bitmap_csum(seed, bytes, len) else {
            return false;
        };
        self.inode_bitmap_csum_lo = csum as u16;
        self.inode_bitmap_csum_hi = (csum >> 16) as u16;
        true
    }

    pub fn verify_block_bitmap_csum(
        &self,
        seed: MetadataChecksumSeed,
        bytes: &[u8],
        len: usize,
    ) -> bool {
        Self::bitmap_csum(seed, bytes, len).is_some_and(|csum| {
            csum == (self.block_bitmap_csum_lo as u32 | ((self.block_bitmap_csum_hi as u32) << 16))
        })
    }

    pub fn update_block_bitmap_csum(
        &mut self,
        seed: MetadataChecksumSeed,
        bytes: &[u8],
        len: usize,
    ) -> bool {
        let Some(csum) = Self::bitmap_csum(seed, bytes, len) else {
            return false;
        };
        self.block_bitmap_csum_lo = csum as u16;
        self.block_bitmap_csum_hi = (csum >> 16) as u16;
        true
    }

    #[cfg(test)]
    pub(crate) fn validation_fixture() -> Self {
        let mut desc = Self::default();
        desc.block_bitmap_lo = 2;
        desc.inode_bitmap_lo = 3;
        desc.inode_table_first_block_lo = 4;
        desc
    }
}

/// A combination of a `BlockGroupDesc` and its id
#[derive(Debug)]
pub struct BlockGroupRef {
    /// The block group id
    pub id: BlockGroupId,
    /// The block group descriptor
    pub desc: BlockGroupDesc,
}

impl BlockGroupRef {
    pub fn new(id: BlockGroupId, desc: BlockGroupDesc) -> Self {
        Self { id, desc }
    }

    pub fn set_checksum(&mut self, seed: MetadataChecksumSeed) {
        // Same as inode checksum: clear the checksum field before calculation to avoid
        // including old value causing checksum mismatch.
        self.desc.checksum = 0;
        let mut checksum = seed.crc32c(&self.id.to_le_bytes());
        checksum = crc32(checksum, self.desc.to_bytes());
        self.desc.checksum = checksum as u16;
    }

    pub fn verify_checksum(&self, seed: MetadataChecksumSeed) -> bool {
        let expected = self.desc.checksum;
        let mut copy = BlockGroupRef::new(self.id, self.desc);
        copy.set_checksum(seed);
        copy.desc.checksum == expected
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn free_block_count_roundtrips_high_sixteen_bits() {
        let mut desc = BlockGroupDesc::default();
        for count in [0, 1, u16::MAX as u64, 0x1234_5678, u32::MAX as u64] {
            desc.set_free_blocks_count(count);
            assert_eq!(desc.get_free_blocks_count(), count);
        }
    }

    #[test]
    fn bitmap_checksum_covers_fixed_group_bytes_not_mutation_bounds() {
        let uuid = [0x5a; 16];
        let seed = MetadataChecksumSeed::from_uuid(&uuid);
        let mut desc = BlockGroupDesc::default();
        let mut bytes = [0u8; 32];
        // Model a partial last inode group: direct mutation may touch only 65
        // real inode bits, while Linux checksums the fixed 128-bit group bitmap.
        {
            let mut actual_inodes = crate::ext4_defs::Bitmap::new(&mut bytes, 65);
            actual_inodes.set_bit(64);
        }

        assert!(desc.update_block_bitmap_csum(seed, &bytes, 16));
        assert!(desc.verify_block_bitmap_csum(seed, &bytes, 16));

        // This byte can be beyond the actual blocks in a partial final group,
        // but Linux still includes it in clusters_per_group / 8.
        bytes[15] ^= 1;
        assert!(!desc.verify_block_bitmap_csum(seed, &bytes, 16));
        bytes[15] ^= 1;

        // Bytes beyond the fixed checksum length are outside the bitmap.
        bytes[16] ^= 1;
        assert!(desc.verify_block_bitmap_csum(seed, &bytes, 16));

        assert!(desc.update_inode_bitmap_csum(seed, &bytes, 16));
        bytes[15] ^= 1;
        assert!(!desc.verify_inode_bitmap_csum(seed, &bytes, 16));
        bytes[15] ^= 1;
        assert!(desc.verify_inode_bitmap_csum(seed, &bytes, 16));
    }

    #[test]
    fn group_descriptor_checksum_detects_corruption() {
        let uuid = [0xa5; 16];
        let seed = MetadataChecksumSeed::from_uuid(&uuid);
        let mut group = BlockGroupRef::new(7, BlockGroupDesc::default());
        group.set_checksum(seed);
        assert!(group.verify_checksum(seed));
        group.desc.set_free_inodes_count(1);
        assert!(!group.verify_checksum(seed));
    }

    #[test]
    fn explicit_metadata_seed_is_not_rederived_from_uuid() {
        let uuid_seed = MetadataChecksumSeed::from_uuid(&[0xa5; 16]);
        let explicit_seed = MetadataChecksumSeed::from_raw(0x1234_5678);
        let mut bytes = [0u8; 16];
        bytes[7] = 0x5a;
        let mut desc = BlockGroupDesc::default();

        assert!(desc.update_block_bitmap_csum(explicit_seed, &bytes, bytes.len()));
        assert!(desc.verify_block_bitmap_csum(explicit_seed, &bytes, bytes.len()));
        assert!(!desc.verify_block_bitmap_csum(uuid_seed, &bytes, bytes.len()));

        let mut group = BlockGroupRef::new(3, desc);
        group.set_checksum(explicit_seed);
        assert!(group.verify_checksum(explicit_seed));
        assert!(!group.verify_checksum(uuid_seed));
    }
}
