use super::*;
use minidump_common::format::{MemoryProtection, MemoryState, MemoryType};
use procfs_core::{process::MMPermissions, FromRead};

/// Write a MemoryInfoListStream using information from procfs.
pub fn write(
    config: &mut MinidumpWriter,
    buffer: &mut DumpBuf,
) -> Result<MDRawDirectory, errors::SectionMemInfoListError> {
    let maps = procfs_core::process::MemoryMaps::from_file(std::path::PathBuf::from(format!(
        "/proc/{}/maps",
        config.blamed_thread
    )))?;

    let list_header = MemoryWriter::alloc_with_val(
        buffer,
        MDMemoryInfoList {
            size_of_header: std::mem::size_of::<MDMemoryInfoList>() as u32,
            size_of_entry: std::mem::size_of::<MDMemoryInfo>() as u32,
            number_of_entries: maps.len() as u64,
        },
    )?;

    let mut dirent = MDRawDirectory {
        stream_type: MDStreamType::MemoryInfoListStream as u32,
        location: list_header.location(),
    };

    let block_list = MemoryArrayWriter::<MDMemoryInfo>::alloc_from_iter(
        buffer,
        maps.iter().map(|mm| MDMemoryInfo {
            base_address: mm.address.0,
            allocation_base: mm.address.0,
            allocation_protection: get_memory_protection(mm.perms).bits(),
            __alignment1: 0,
            region_size: mm.address.1 - mm.address.0,
            state: MemoryState::MEM_COMMIT.bits(),
            protection: get_memory_protection(mm.perms).bits(),
            _type: if mm.perms.contains(MMPermissions::PRIVATE) {
                MemoryType::MEM_PRIVATE
            } else {
                MemoryType::MEM_MAPPED
            }
            .bits(),
            __alignment2: 0,
        }),
    )?;

    dirent.location.data_size += block_list.location().data_size;

    Ok(dirent)
}

fn get_memory_protection(permissions: MMPermissions) -> MemoryProtection {
    let read = permissions.contains(MMPermissions::READ);
    let write = permissions.contains(MMPermissions::WRITE);
    let exec = permissions.contains(MMPermissions::EXECUTE);
    match (read, write, exec) {
        (false, false, false) => MemoryProtection::PAGE_NOACCESS,
        (false, false, true) => MemoryProtection::PAGE_EXECUTE,
        (true, false, false) => MemoryProtection::PAGE_READONLY,
        (true, false, true) => MemoryProtection::PAGE_EXECUTE_READ,
        // No support for write-only
        (true | false, true, false) => MemoryProtection::PAGE_READWRITE,
        // No support for execute+write-only
        (true | false, true, true) => MemoryProtection::PAGE_EXECUTE_READWRITE,
    }
}
