use std::collections::BTreeMap;
use anyhow::Result;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
use zerocopy::KnownLayout;
use crate::arena::Arena;
use crate::arena::BlockId;
use crate::bitmap::BitMap;
use crate::inode::Inode;
use crate::inode::InodeNum;
use crate::superblock::SuperBlock;
pub const BLOCK_SIZE: usize = 4096;
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
pub(crate) struct BlockGroupDescriptor {
    pub block_bitmap: u32,
    pub inode_bitmap: u32,
    pub inode_table: u32,
    pub free_blocks_count: u16,
    pub free_inodes_count: u16,
    pub used_dirs_count: u16,
    pad: u16,
    reserved: [u8; 12],
}
pub(crate) struct GroupMetaData<'a> {
    pub group_desc: &'a mut BlockGroupDescriptor,
    pub block_bitmap: BitMap<'a>,
    pub inode_bitmap: BitMap<'a>,
    pub inode_table: BTreeMap<InodeNum, &'a mut Inode>,
    pub first_free_block: u32,
    pub first_free_inode: u32,
}
impl<'a> GroupMetaData<'a> {
    pub fn new(arena: &'a Arena<'a>, sb: &mut SuperBlock, group_id: u16) -> Result<Self> {
        let gd_size = std::mem::size_of::<BlockGroupDescriptor>() as u32;
        let num_blocks_for_gds = (gd_size * sb.num_groups() as u32).div_ceil(BLOCK_SIZE as u32);
        let inodes_per_block = BLOCK_SIZE as u64 / sb.inode_size as u64;
        let num_blocks_for_inode_table =
            (sb.inodes_per_group as usize).div_ceil(inodes_per_block as usize);
        let group_desc = arena.allocate::<BlockGroupDescriptor>(
            BlockId::from(1),
            std::mem::size_of::<BlockGroupDescriptor>() * group_id as usize,
        )?;
        let super_block_id = group_id as u32 * sb.blocks_per_group;
        let group_desc_id = super_block_id + 1;
        group_desc.block_bitmap = group_desc_id + num_blocks_for_gds;
        group_desc.inode_bitmap = group_desc.block_bitmap + 1;
        group_desc.inode_table = group_desc.inode_bitmap + 1;
        let first_free_block = group_desc.inode_table + num_blocks_for_inode_table as u32;
        group_desc.free_blocks_count =
            (sb.blocks_per_group * (group_id as u32 + 1) - first_free_block) as u16;
        sb.free_blocks_count += group_desc.free_blocks_count as u32;
        let reserved_inode = if group_id == 0 { 10 } else { 0 };
        let first_free_inode = group_id as u32 * sb.inodes_per_group + reserved_inode + 1;
        group_desc.free_inodes_count = sb.inodes_per_group as u16 - reserved_inode as u16;
        sb.free_inodes_count -= reserved_inode;
        let bmap = arena.allocate::<[u8; BLOCK_SIZE]>(BlockId::from(group_desc.block_bitmap), 0)?;
        let valid_bmap_bytes = (sb.blocks_per_group / 8) as usize;
        bmap[valid_bmap_bytes..].iter_mut().for_each(|x| *x = 0xff);
        let mut block_bitmap = BitMap::from_slice_mut(&mut bmap[..valid_bmap_bytes]);
        block_bitmap.mark_first_elems(
            (first_free_block - group_id as u32 * sb.blocks_per_group) as usize,
            true,
        );
        let imap = arena.allocate::<[u8; BLOCK_SIZE]>(BlockId::from(group_desc.inode_bitmap), 0)?;
        let valid_imap_bytes = (sb.inodes_per_group / 8) as usize;
        imap[valid_imap_bytes..].iter_mut().for_each(|x| *x = 0xff);
        let mut inode_bitmap =
            BitMap::from_slice_mut(&mut imap[..(sb.inodes_per_group / 8) as usize]);
        inode_bitmap.mark_first_elems(reserved_inode as usize, true);
        Ok(GroupMetaData {
            group_desc,
            block_bitmap,
            inode_bitmap,
            inode_table: BTreeMap::new(),
            first_free_block,
            first_free_inode,
        })
    }
}
#[cfg(test)]
mod test {
    use base::MemoryMappingBuilder;
    use super::*;
    use crate::Builder;
    #[test]
    fn test_group_metadata_with_one_block_group() {
        let blocks_per_group = 1024;
        let num_groups = 1;
        let size = BLOCK_SIZE as u32 * blocks_per_group * num_groups;
        let mut mem = MemoryMappingBuilder::new(size as usize).build().unwrap();
        let arena = Arena::new(BLOCK_SIZE, &mut mem).unwrap();
        let sb = SuperBlock::new(
            &arena,
            &Builder {
                inodes_per_group: 1024,
                blocks_per_group,
                size,
                root_dir: None,
            },
        )
        .unwrap();
        let group = GroupMetaData::new(&arena, sb, 0).unwrap();
        assert_eq!(sb.block_group_nr, 1);
        assert_eq!(group.group_desc.block_bitmap, 2);
        assert_eq!(group.group_desc.inode_bitmap, 3);
        assert_eq!(group.group_desc.inode_table, 4);
        assert_eq!(
            group.group_desc.free_blocks_count as u32,
            sb.free_blocks_count
        );
        assert_eq!(
            group.group_desc.free_inodes_count as u32,
            sb.free_inodes_count
        );
        assert_eq!(group.block_bitmap.len(), sb.blocks_per_group as usize);
        assert_eq!(
            group.block_bitmap.count_zeros(),
            group.group_desc.free_blocks_count as usize,
        );
        assert_eq!(
            group.inode_bitmap.count_zeros(),
            group.group_desc.free_inodes_count as usize,
        );
    }
    #[test]
    fn test_group_metadata_with_multiple_block_groups() {
        let blocks_per_group = 1024u32;
        let num_groups = 10u32;
        let mem_size = BLOCK_SIZE as u32 * blocks_per_group * num_groups;
        let mut mem = MemoryMappingBuilder::new(mem_size as usize)
            .build()
            .unwrap();
        let arena = Arena::new(BLOCK_SIZE, &mut mem).unwrap();
        let sb = SuperBlock::new(
            &arena,
            &Builder {
                inodes_per_group: 512,
                blocks_per_group,
                size: mem_size,
                root_dir: None,
            },
        )
        .unwrap();
        let groups = (0..num_groups)
            .map(|group_id| GroupMetaData::new(&arena, sb, group_id as u16).unwrap())
            .collect::<Vec<_>>();
        assert_eq!(
            groups
                .iter()
                .map(|gd| gd.group_desc.free_blocks_count as u32)
                .sum::<u32>(),
            sb.free_blocks_count
        );
        assert_eq!(
            groups
                .iter()
                .map(|gd| gd.group_desc.free_inodes_count as u32)
                .sum::<u32>(),
            sb.free_inodes_count
        );
    }
}