use std::{env::args, fs::File, io::Read};

fn main() {
    let args: Vec<String> = args().collect();
    if args.len() != 2 {
        panic!("usage: demo <elf-filepath>")
    }

    let mut f = File::open(&args[1]).expect("failed to open elf-file");

    let mut buf = Vec::with_capacity(4096);
    let _ = f.read_to_end(&mut buf).expect("failed to read elf-file");

    let parser = pelf::parser::ElfParserConfig::new().build();
    let elf = parser
        .parse_raw_elf_file(&buf)
        .expect("failed to parse elf-file");

    match elf {
        pelf::file::RawElfFile::Elf64(elf64) => {
            dump_elf64_file(elf64);
        }
        _ => unimplemented!(),
    }
}

fn dump_elf64_file(elf: pelf::file::RawElf64File) {
    dump_elf64_header(&elf.header);
    dump_elf64_program_header_table(&elf.program_headers);
    dump_elf64_section_header_table(&elf.section_headers);

    println!("Program Header Table:");
    for (i, phdr) in elf.program_headers.iter().enumerate() {
        println!("    [{}]:", i);
        println!("        Type: 0x{:x}", phdr.p_type);
        println!("        Flags: 0b{:b}", phdr.p_flags);
        println!("        Offset: 0x{:x}", phdr.p_offset);
        println!("        Virtual Address: 0x{:x}", phdr.p_vaddr);
        println!("        Physical Address: 0x{:x}", phdr.p_paddr);
        println!("        Size in File: 0x{:x}", phdr.p_filesz);
        println!("        Size in Memory: 0x{:x}", phdr.p_memsz);
        println!("        Alignment: 0x{:x}", phdr.p_align);
    }
}

fn dump_elf64_header(header: &pelf::header::RawElf64Header) {
    println!("ELF Header:");
    println!("    Class: 0x{:x}", header.e_ident.e_class);
    println!("    Data: 0x{:x}", header.e_ident.e_data);
    println!("    File Version: 0x{:x}", header.e_ident.e_version);
    println!("    Type: 0x{:x}", header.e_type);
    println!("    Machine: 0x{:x}", header.e_machine);
    println!("    Version: {}", header.e_version);
    println!("    Entry: 0x{:x}", header.e_entry);
    println!("    PHT Offset: 0x{:x}", header.e_phoff);
    println!("    SHT Offset: 0x{:x}", header.e_shoff);
    println!("    Flags: 0b{:b}", header.e_flags);
    println!("    PHT Entry Size: 0x{:x}", header.e_phentsize);
    println!("    PHT Entries: {}", header.e_phnum);
    println!("    SHT Entry Size: 0x{:x}", header.e_shentsize);
    println!("    SHT Entries: {}", header.e_shnum);
    println!(
        "    Section Header String Table Index: {}",
        header.e_shstrndx
    );
}

fn dump_elf64_program_header_table(phdrs: &pelf::program_header::RawElf64ProgramHeaderTable) {
    if phdrs.len() == 0 {
        println!("there is no program header table");
        return;
    }

    println!("Program Header Table:");
    for (i, phdr) in phdrs.iter().enumerate() {
        println!("    [{}]:", i);
        println!("        Type: 0x{:x}", phdr.p_type);
        println!("        Flags: 0b{:b}", phdr.p_flags);
        println!("        Offset: 0x{:x}", phdr.p_offset);
        println!("        Virtual Address: 0x{:x}", phdr.p_vaddr);
        println!("        Physical Address: 0x{:x}", phdr.p_paddr);
        println!("        Size in File: 0x{:x}", phdr.p_filesz);
        println!("        Size in Memory: 0x{:x}", phdr.p_memsz);
        println!("        Alignment: 0x{:x}", phdr.p_align);
    }
}

fn dump_elf64_section_header_table(shdrs: &pelf::section::RawElf64SectionHeaderTable) {
    if shdrs.len() == 0 {
        println!("there is no section header table");
        return;
    }

    println!("Section Header Table:");
    for (i, shdr) in shdrs.iter().enumerate() {
        println!("    [{}]:", i);
        println!("        Name Index: 0x{:x}", shdr.sh_name);
        println!("        Type: 0b{:b}", shdr.sh_type);
        println!("        Flags: 0b{:b}", shdr.sh_flags);
        println!("        Address: 0x{:x}", shdr.sh_addr);
        println!("        Offset: 0x{:x}", shdr.sh_offset);
        println!("        Size: 0x{:x}", shdr.sh_size);
        println!("        Link: 0x{:x}", shdr.sh_link);
        println!("        Info: 0x{:x}", shdr.sh_info);
        println!("        Address Alignment: 0x{:x}", shdr.sh_addralign);
        println!("        Entry Size: 0x{:x}", shdr.sh_entsize);
    }
}
