use std::path::PathBuf;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
use base::MappedRegion;
use base::MemoryMapping;
use base::MemoryMappingArena;
use base::MemoryMappingBuilder;
use base::Protection;
use base::SharedMemory;
use crate::arena::Arena;
use crate::arena::FileMappingInfo;
use crate::fs::Ext2;
use crate::BLOCK_SIZE;
pub struct Builder {
    pub blocks_per_group: u32,
    pub inodes_per_group: u32,
    pub size: u32,
    pub root_dir: Option<PathBuf>,
}
impl Default for Builder {
    fn default() -> Self {
        Self {
            blocks_per_group: 4096,
            inodes_per_group: 4096,
            size: 4096 * 4096,
            root_dir: None,
        }
    }
}
impl Builder {
    fn validate(&mut self) -> Result<()> {
        let block_group_size = BLOCK_SIZE as u32 * self.blocks_per_group;
        if self.size < block_group_size {
            bail!(
            "memory size {} is too small to have a block group: block_size={},  block_per_group={}",
            self.size,
            BLOCK_SIZE,
            block_group_size
        );
        }
        if self.size % block_group_size != 0 {
            self.size = self.size.next_multiple_of(block_group_size) - block_group_size
        };
        Ok(())
    }
    pub fn allocate_memory(mut self) -> Result<MemRegion> {
        self.validate()
            .context("failed to validate the ext2 config")?;
        let mem = MemoryMappingBuilder::new(self.size as usize)
            .build()
            .context("failed to allocate memory for ext2")?;
        Ok(MemRegion { cfg: self, mem })
    }
    pub fn build_on_shm(self, shm: &SharedMemory) -> Result<MemRegion> {
        let mem = MemoryMappingBuilder::new(shm.size() as usize)
            .from_shared_memory(shm)
            .build()
            .expect("failed to build MemoryMapping from shared memory");
        Ok(MemRegion { cfg: self, mem })
    }
}
pub struct MemRegion {
    cfg: Builder,
    mem: MemoryMapping,
}
impl MemRegion {
    pub fn build_mmap_info(mut self) -> Result<MemRegionWithMappingInfo> {
        let arena = Arena::new(BLOCK_SIZE, &mut self.mem).context("failed to allocate arena")?;
        let mut ext2 = Ext2::new(&self.cfg, &arena).context("failed to create Ext2 struct")?;
        if let Some(dir) = self.cfg.root_dir {
            ext2.copy_dirtree(&arena, dir)
                .context("failed to copy directory tree")?;
        }
        ext2.copy_backup_metadata(&arena)
            .context("failed to copy metadata for backup")?;
        let mapping_info = arena.into_mapping_info();
        self.mem
            .msync()
            .context("failed to msyn of ext2's memory region")?;
        Ok(MemRegionWithMappingInfo {
            mem: self.mem,
            mapping_info,
        })
    }
}
pub struct MemRegionWithMappingInfo {
    mem: MemoryMapping,
    pub mapping_info: Vec<FileMappingInfo>,
}
impl MemRegionWithMappingInfo {
    pub fn do_mmap(self) -> Result<MemoryMappingArena> {
        let mut mmap_arena = MemoryMappingArena::from(self.mem);
        for FileMappingInfo {
            mem_offset,
            file,
            length,
            file_offset,
        } in self.mapping_info
        {
            mmap_arena
                .add_fd_mapping(
                    mem_offset,
                    length,
                    &file,
                    file_offset as u64, Protection::read(),
                )
                .context("failed mmaping an fd for ext2")?;
        }
        Ok(mmap_arena)
    }
}