use super::ElfParser;
use crate::program_header;

impl ElfParser {
    pub(super) fn parse_elf64_program_header_table<'a>(
        &'a self,
        b: &'a [u8],
        encoding: u8,
        phoff: usize,
        phnum: usize,
    ) -> nom::IResult<&'a [u8], program_header::Elf64ProgramHeaderTable> {
        let mut phdrs = vec![program_header::Elf64ProgramHeader::default(); phnum];

        for (i, phdr) in phdrs.iter_mut().enumerate().take(phnum) {
            let start = phoff + (std::mem::size_of::<program_header::RawElf64ProgramHeader>() * i);
            let (_b, v) = self.parse_elf64_program_header(&b[start..], encoding)?;
            *phdr = v;
        }

        let end = phoff + (std::mem::size_of::<program_header::RawElf64ProgramHeader>() * phnum);
        Ok((
            &b[end..],
            program_header::Elf64ProgramHeaderTable::new(phdrs),
        ))
    }

    fn parse_elf64_program_header<'a>(
        &'a self,
        b: &'a [u8],
        encoding: u8,
    ) -> nom::IResult<&'a [u8], program_header::Elf64ProgramHeader> {
        self.parse_raw_elf64_program_header(b, encoding)
            .map(|(b, raw)| (b, program_header::Elf64ProgramHeader::from(raw)))
    }

    pub(super) fn parse_raw_elf64_program_header_table<'a>(
        &'a self,
        b: &'a [u8],
        encoding: u8,
        phoff: usize,
        phnum: usize,
    ) -> nom::IResult<&'a [u8], program_header::RawElf64ProgramHeaderTable> {
        let mut phdrs = vec![program_header::RawElf64ProgramHeader::default(); phnum];

        for (i, phdr) in phdrs.iter_mut().enumerate().take(phnum) {
            let start = phoff + (std::mem::size_of::<program_header::RawElf64ProgramHeader>() * i);
            let (_b, v) = self.parse_raw_elf64_program_header(&b[start..], encoding)?;
            *phdr = v;
        }

        let end = phoff + (std::mem::size_of::<program_header::RawElf64ProgramHeader>() * phnum);
        Ok((
            &b[end..],
            program_header::RawElf64ProgramHeaderTable::new(phdrs),
        ))
    }

    fn parse_raw_elf64_program_header<'a>(
        &'a self,
        b: &'a [u8],
        encoding: u8,
    ) -> nom::IResult<&'a [u8], program_header::RawElf64ProgramHeader> {
        let (b, p_type) = self.parse_elf64_word(encoding, b)?;
        let (b, p_flags) = self.parse_elf64_word(encoding, b)?;
        let (b, p_offset) = self.parse_elf64_offset(encoding, b)?;
        let (b, p_vaddr) = self.parse_elf64_address(encoding, b)?;
        let (b, p_paddr) = self.parse_elf64_address(encoding, b)?;
        let (b, p_filesz) = self.parse_elf64_xword(encoding, b)?;
        let (b, p_memsz) = self.parse_elf64_xword(encoding, b)?;
        let (b, p_align) = self.parse_elf64_xword(encoding, b)?;

        let phdr = program_header::RawElf64ProgramHeaderBuilder::default()
            .header_type(p_type)
            .flags(p_flags)
            .offset(p_offset)
            .vaddr(p_vaddr)
            .paddr(p_paddr)
            .filesz(p_filesz)
            .memsz(p_memsz)
            .align(p_align)
            .build();

        Ok((b, phdr))
    }
}

#[cfg(test)]
mod tests {
    use crate::{header, parser::ElfParserConfig, program_header};

    #[test]
    fn test_parse_elf64_program_header_table() {
        let input = vec![
            0x06, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
            0x03, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
        ];

        let p = ElfParserConfig::new().build();
        let result = p.parse_elf64_program_header_table(&input, header::ELF_IDENT_DATA_2LSB, 0, 2);
        assert!(result.is_ok());

        let (b, phdrs) = result.unwrap();
        assert!(b.is_empty());

        let mut it = phdrs.iter();
        let phdr0 = it.next();
        assert!(phdr0.is_some());

        let phdr0 = phdr0.unwrap();
        assert_eq!(program_header::ProgramHeaderType::Phdr, phdr0.header_type);
        assert_eq!(0x40, phdr0.offset);
        assert_eq!(0x40, phdr0.vaddr);
        assert_eq!(0x40, phdr0.paddr);
        assert_eq!(0x2d8, phdr0.filesz);
        assert_eq!(0x2d8, phdr0.memsz);
        assert!(phdr0
            .flags
            .contains(program_header::ProgramHeaderFlag::Readable));
        assert_eq!(0x8, phdr0.align);

        let phdr1 = it.next();
        assert!(phdr1.is_some());

        let phdr1 = phdr1.unwrap();
        assert_eq!(0x318, phdr1.offset);
        assert_eq!(0x318, phdr1.vaddr);
        assert_eq!(0x318, phdr1.paddr);
        assert_eq!(0x1c, phdr1.filesz);
        assert_eq!(0x1c, phdr1.memsz);
        assert_eq!(0x1, phdr1.align);
        assert_eq!(program_header::ProgramHeaderType::Interp, phdr1.header_type);
        assert!(phdr1
            .flags
            .contains(program_header::ProgramHeaderFlag::Readable));
    }

    #[test]
    fn test_parse_elf64_program_header() {
        let input = vec![
            0x06, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
        ];

        let p = ElfParserConfig::new().build();
        let result = p.parse_elf64_program_header(&input, header::ELF_IDENT_DATA_2LSB);
        assert!(result.is_ok());

        let (b, phdr) = result.unwrap();
        assert!(b.is_empty());

        assert_eq!(program_header::ProgramHeaderType::Phdr, phdr.header_type);
        assert_eq!(0x40, phdr.offset);
        assert_eq!(0x40, phdr.vaddr);
        assert_eq!(0x40, phdr.paddr);
        assert_eq!(0x2d8, phdr.filesz);
        assert_eq!(0x2d8, phdr.memsz);
        assert!(phdr
            .flags
            .contains(program_header::ProgramHeaderFlag::Readable));
        assert_eq!(0x8, phdr.align);
    }

    #[test]
    fn test_parse_raw_elf64_program_header_table() {
        let input = vec![
            0x06, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
            0x03, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
        ];

        let p = ElfParserConfig::new().build();
        let result =
            p.parse_raw_elf64_program_header_table(&input, header::ELF_IDENT_DATA_2LSB, 0, 2);
        assert!(result.is_ok());

        let (b, phdrs) = result.unwrap();
        assert!(b.is_empty());

        let mut it = phdrs.iter();
        let phdr0 = it.next();
        assert!(phdr0.is_some());

        let phdr0 = phdr0.unwrap();
        assert_eq!(0x6, phdr0.p_type);
        assert_eq!(0x40, phdr0.p_offset);
        assert_eq!(0x40, phdr0.p_vaddr);
        assert_eq!(0x40, phdr0.p_paddr);
        assert_eq!(0x2d8, phdr0.p_filesz);
        assert_eq!(0x2d8, phdr0.p_memsz);
        assert_eq!(0b100, phdr0.p_flags);
        assert_eq!(0x8, phdr0.p_align);

        let phdr1 = it.next();
        assert!(phdr1.is_some());

        let phdr1 = phdr1.unwrap();
        assert_eq!(0x3, phdr1.p_type);
        assert_eq!(0x318, phdr1.p_offset);
        assert_eq!(0x318, phdr1.p_vaddr);
        assert_eq!(0x318, phdr1.p_paddr);
        assert_eq!(0x1c, phdr1.p_filesz);
        assert_eq!(0x1c, phdr1.p_memsz);
        assert_eq!(0b100, phdr1.p_flags);
        assert_eq!(0x1, phdr1.p_align);
    }

    #[test]
    fn test_parse_raw_elf64_program_header() {
        let input = vec![
            0x06, 0x00, 0x00, 0x00, // p_type
            0x04, 0x00, 0x00, 0x00, // p_flags
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_offset
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_vaddr
            0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_paddr
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_filesz
            0xd8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_memsz
            0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // p_align
        ];

        let p = ElfParserConfig::new().build();
        let result = p.parse_raw_elf64_program_header(&input, header::ELF_IDENT_DATA_2LSB);
        assert!(result.is_ok());

        let (b, phdr) = result.unwrap();
        assert!(b.is_empty());

        assert_eq!(0x6, phdr.p_type);
        assert_eq!(0x40, phdr.p_offset);
        assert_eq!(0x40, phdr.p_vaddr);
        assert_eq!(0x40, phdr.p_paddr);
        assert_eq!(0x2d8, phdr.p_filesz);
        assert_eq!(0x2d8, phdr.p_memsz);
        assert_eq!(
            program_header::ELF_PROGRAM_HEADER_FLAG_READABLE,
            phdr.p_flags
        );
        assert_eq!(0x8, phdr.p_align);
    }
}
