/* Legal values for p_type (segment type).  */

/// Programg header table entry unused
pub const PT_NULL: u32 = 0;
/// Loadable program segment
pub const PT_LOAD: u32 = 1;
/// Dynamic linking information
pub const PT_DYNAMIC: u32 = 2;
/// Program interpreter
pub const PT_INTERP: u32 = 3;
/// Auxiliary information
pub const PT_NOTE: u32 = 4;
/// Reserved
pub const PT_SHLIB: u32 = 5;
/// Entry for header table itself
pub const PT_PHDR: u32 = 6;
/// Thread-local storage segment
pub const PT_TLS: u32 = 7;
/// Number of defined types
pub const PT_NUM: u32 = 8;
/// Start of OS-specific
pub const PT_LOOS: u32 = 0x6000_0000;
/// GCC .eh_frame_hdr segment
pub const PT_GNU_EH_FRAME: u32 = 0x6474_e550;
/// Indicates stack executability
pub const PT_GNU_STACK: u32 = 0x6474_e551;
/// Read-only after relocation
pub const PT_GNU_RELRO: u32 = 0x6474_e552;
/// Sun Specific segment
pub const PT_LOSUNW: u32 = 0x6fff_fffa;
/// Sun Specific segment
pub const PT_SUNWBSS: u32 = 0x6fff_fffa;
/// Stack segment
pub const PT_SUNWSTACK: u32 = 0x6fff_fffb;
/// End of OS-specific
pub const PT_HISUNW: u32 = 0x6fff_ffff;
/// End of OS-specific
pub const PT_HIOS: u32 = 0x6fff_ffff;
/// Start of processor-specific
pub const PT_LOPROC: u32 = 0x7000_0000;
/// ARM unwind segment
pub const PT_ARM_EXIDX: u32 = 0x7000_0001;
/// End of processor-specific
pub const PT_HIPROC: u32 = 0x7fff_ffff;

/* Legal values for p_flags (segment flags).  */

/// Segment is executable
pub const PF_X: u32 = 1;
/// Segment is writable
pub const PF_W: u32 = 1 << 1;
/// Segment is readable
pub const PF_R: u32 = 1 << 2;
/// Bits reserved for OS-specific usage
pub const PF_MASKOS: u32 = 0x0ff0_0000;
/// Bits reserved for processor-specific usage
pub const PF_MASKPROC: u32 = 0xf000_0000;

pub fn pt_to_str(pt: u32) -> &'static str {
    match pt {
        PT_NULL => "PT_NULL",
        PT_LOAD => "PT_LOAD",
        PT_DYNAMIC => "PT_DYNAMIC",
        PT_INTERP => "PT_INTERP",
        PT_NOTE => "PT_NOTE",
        PT_SHLIB => "PT_SHLIB",
        PT_PHDR => "PT_PHDR",
        PT_TLS => "PT_TLS",
        PT_NUM => "PT_NUM",
        PT_LOOS => "PT_LOOS",
        PT_GNU_EH_FRAME => "PT_GNU_EH_FRAME",
        PT_GNU_STACK => "PT_GNU_STACK",
        PT_GNU_RELRO => "PT_GNU_RELRO",
        PT_SUNWBSS => "PT_SUNWBSS",
        PT_SUNWSTACK => "PT_SUNWSTACK",
        PT_HIOS => "PT_HIOS",
        PT_LOPROC => "PT_LOPROC",
        PT_HIPROC => "PT_HIPROC",
        PT_ARM_EXIDX => "PT_ARM_EXIDX",
        _ => "UNKNOWN_PT",
    }
}

if_alloc! {
    use core::fmt;
    use scroll::ctx;
    use core::result;
    use core::ops::Range;
    use crate::container::{Ctx, Container};
    use alloc::vec::Vec;

    #[derive(Default, PartialEq, Clone)]
    /// A unified ProgramHeader - convertable to and from 32-bit and 64-bit variants
    pub struct ProgramHeader {
        pub p_type  : u32,
        pub p_flags : u32,
        pub p_offset: u64,
        pub p_vaddr : u64,
        pub p_paddr : u64,
        pub p_filesz: u64,
        pub p_memsz : u64,
        pub p_align : u64,
    }

    impl ProgramHeader {
        /// Return the size of the underlying program header, given a `Ctx`
        #[inline]
        pub fn size(ctx: Ctx) -> usize {
            use scroll::ctx::SizeWith;
            Self::size_with(&ctx)
        }
        /// Create a new `PT_LOAD` ELF program header
        pub fn new() -> Self {
            ProgramHeader {
                p_type  : PT_LOAD,
                p_flags : 0,
                p_offset: 0,
                p_vaddr : 0,
                p_paddr : 0,
                p_filesz: 0,
                p_memsz : 0,
                //TODO: check if this is true for 32-bit pt_load
                p_align : 2 << 20,
            }
        }
        /// Returns this program header's file offset range
        pub fn file_range(&self) -> Range<usize> {
            self.p_offset as usize..self.p_offset.saturating_add(self.p_filesz) as usize
        }
        /// Returns this program header's virtual memory range
        pub fn vm_range(&self) -> Range<usize> {
            self.p_vaddr as usize..self.p_vaddr.saturating_add(self.p_memsz) as usize
        }
        /// Sets the executable flag
        pub fn executable(&mut self) {
            self.p_flags |= PF_X;
        }
        /// Sets the write flag
        pub fn write(&mut self) {
            self.p_flags |= PF_W;
        }
        /// Sets the read flag
        pub fn read(&mut self) {
            self.p_flags |= PF_R;
        }
        /// Whether this program header is executable
        pub fn is_executable(&self) -> bool {
            self.p_flags & PF_X != 0
        }
        /// Whether this program header is readable
        pub fn is_read(&self) -> bool {
            self.p_flags & PF_R != 0
        }
        /// Whether this program header is writable
        pub fn is_write(&self) -> bool {
            self.p_flags & PF_W != 0
        }
        #[cfg(feature = "endian_fd")]
        pub fn parse(bytes: &[u8], mut offset: usize, count: usize, ctx: Ctx) -> crate::error::Result<Vec<ProgramHeader>> {
            use scroll::Pread;
            // Sanity check to avoid OOM
            if count > bytes.len() / Self::size(ctx) {
                return Err(crate::error::Error::BufferTooShort(count, "program headers"));
            }
            let mut program_headers = Vec::with_capacity(count);
            for _ in 0..count {
                let phdr = bytes.gread_with(&mut offset, ctx)?;
                program_headers.push(phdr);
            }
            Ok(program_headers)
        }
    }

    impl fmt::Debug for ProgramHeader {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            f.debug_struct("ProgramHeader")
                .field("p_type", &pt_to_str(self.p_type))
                .field("p_flags", &format_args!("0x{:x}", self.p_flags))
                .field("p_offset", &format_args!("0x{:x}", self.p_offset))
                .field("p_vaddr", &format_args!("0x{:x}", self.p_vaddr))
                .field("p_paddr", &format_args!("0x{:x}", self.p_paddr))
                .field("p_filesz", &format_args!("0x{:x}", self.p_filesz))
                .field("p_memsz", &format_args!("0x{:x}", self.p_memsz))
                .field("p_align", &self.p_align)
                .finish()
        }
    }

    impl ctx::SizeWith<Ctx> for ProgramHeader {
        fn size_with(ctx: &Ctx) -> usize {
            match ctx.container {
                Container::Little => {
                    program_header32::SIZEOF_PHDR
                },
                Container::Big => {
                    program_header64::SIZEOF_PHDR
                },
            }
        }
    }

    impl<'a> ctx::TryFromCtx<'a, Ctx> for ProgramHeader {
        type Error = crate::error::Error;
        fn try_from_ctx(bytes: &'a [u8], Ctx { container, le}: Ctx) -> result::Result<(Self, usize), Self::Error> {
            use scroll::Pread;
            let res = match container {
                Container::Little => {
                    (bytes.pread_with::<program_header32::ProgramHeader>(0, le)?.into(), program_header32::SIZEOF_PHDR)
                },
                Container::Big => {
                    (bytes.pread_with::<program_header64::ProgramHeader>(0, le)?.into(), program_header64::SIZEOF_PHDR)
                }
            };
            Ok(res)
        }
    }

    impl ctx::TryIntoCtx<Ctx> for ProgramHeader {
        type Error = crate::error::Error;
        fn try_into_ctx(self, bytes: &mut [u8], Ctx {container, le}: Ctx) -> result::Result<usize, Self::Error> {
            use scroll::Pwrite;
            match container {
                Container::Little => {
                    let phdr: program_header32::ProgramHeader = self.into();
                    Ok(bytes.pwrite_with(phdr, 0, le)?)
                },
                Container::Big => {
                    let phdr: program_header64::ProgramHeader = self.into();
                    Ok(bytes.pwrite_with(phdr, 0, le)?)
                }
            }
        }
    }
} // end if_alloc

macro_rules! elf_program_header_std_impl {
    ($size:ty) => {
        #[cfg(test)]
        mod tests {
            use super::*;
            #[test]
            fn size_of() {
                assert_eq!(::std::mem::size_of::<ProgramHeader>(), SIZEOF_PHDR);
            }
        }

        if_alloc! {


            use crate::elf::program_header::ProgramHeader as ElfProgramHeader;
            #[cfg(any(feature = "std", feature = "endian_fd"))]
            use crate::error::Result;

            use plain::Plain;

            if_std! {
                use std::fs::File;
                use std::io::{Seek, Read};
                use std::io::SeekFrom::Start;
            }

            impl From<ProgramHeader> for ElfProgramHeader {
                fn from(ph: ProgramHeader) -> Self {
                    ElfProgramHeader {
                        p_type   : ph.p_type,
                        p_flags  : ph.p_flags,
                        p_offset : u64::from(ph.p_offset),
                        p_vaddr  : u64::from(ph.p_vaddr),
                        p_paddr  : u64::from(ph.p_paddr),
                        p_filesz : u64::from(ph.p_filesz),
                        p_memsz  : u64::from(ph.p_memsz),
                        p_align  : u64::from(ph.p_align),
                    }
                }
            }

            impl From<ElfProgramHeader> for ProgramHeader {
                fn from(ph: ElfProgramHeader) -> Self {
                    ProgramHeader {
                        p_type   : ph.p_type,
                        p_flags  : ph.p_flags,
                        p_offset : ph.p_offset as $size,
                        p_vaddr  : ph.p_vaddr  as $size,
                        p_paddr  : ph.p_paddr  as $size,
                        p_filesz : ph.p_filesz as $size,
                        p_memsz  : ph.p_memsz  as $size,
                        p_align  : ph.p_align  as $size,
                    }
                }
            }
        } // end if_alloc

        use core::fmt;
        use core::slice;

        impl fmt::Debug for ProgramHeader {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                f.debug_struct("ProgramHeader")
                    .field("p_type", &pt_to_str(self.p_type))
                    .field("p_flags", &format_args!("0x{:x}", self.p_flags))
                    .field("p_offset", &format_args!("0x{:x}", self.p_offset))
                    .field("p_vaddr", &format_args!("0x{:x}", self.p_vaddr))
                    .field("p_paddr", &format_args!("0x{:x}", self.p_paddr))
                    .field("p_filesz", &format_args!("0x{:x}", self.p_filesz))
                    .field("p_memsz", &format_args!("0x{:x}", self.p_memsz))
                    .field("p_align", &self.p_align)
                    .finish()
            }
        }

        impl ProgramHeader {
            #[cfg(feature = "endian_fd")]
            pub fn parse(
                bytes: &[u8],
                mut offset: usize,
                count: usize,
                ctx: ::scroll::Endian,
            ) -> Result<Vec<ProgramHeader>> {
                use scroll::Pread;
                let mut program_headers = vec![ProgramHeader::default(); count];
                let offset = &mut offset;
                bytes.gread_inout_with(offset, &mut program_headers, ctx)?;
                Ok(program_headers)
            }

            #[cfg(feature = "alloc")]
            pub fn from_bytes(bytes: &[u8], phnum: usize) -> Vec<ProgramHeader> {
                let mut phdrs = vec![ProgramHeader::default(); phnum];
                phdrs
                    .copy_from_bytes(bytes)
                    .expect("buffer is too short for given number of entries");
                phdrs
            }

            /// # Safety
            ///
            /// This function creates a `ProgramHeader` directly from a raw pointer
            pub unsafe fn from_raw_parts<'a>(
                phdrp: *const ProgramHeader,
                phnum: usize,
            ) -> &'a [ProgramHeader] {
                slice::from_raw_parts(phdrp, phnum)
            }

            #[cfg(feature = "std")]
            pub fn from_fd(fd: &mut File, offset: u64, count: usize) -> Result<Vec<ProgramHeader>> {
                let mut phdrs = vec![ProgramHeader::default(); count];
                fd.seek(Start(offset))?;
                unsafe {
                    fd.read_exact(plain::as_mut_bytes(&mut *phdrs))?;
                }
                Ok(phdrs)
            }
        }
    };
}

#[cfg(feature = "alloc")]
use scroll::{Pread, Pwrite, SizeWith};

pub mod program_header32 {
    pub use crate::elf::program_header::*;

    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Default)]
    #[cfg_attr(feature = "alloc", derive(Pread, Pwrite, SizeWith))]
    /// A 32-bit ProgramHeader typically specifies how to map executable and data segments into memory
    pub struct ProgramHeader {
        /// Segment type
        pub p_type: u32,
        /// Segment file offset
        pub p_offset: u32,
        /// Segment virtual address
        pub p_vaddr: u32,
        /// Segment physical address
        pub p_paddr: u32,
        /// Segment size in file
        pub p_filesz: u32,
        /// Segment size in memory
        pub p_memsz: u32,
        /// Segment flags
        pub p_flags: u32,
        /// Segment alignment
        pub p_align: u32,
    }

    pub const SIZEOF_PHDR: usize = 32;

    // Declare that this is a plain type.
    unsafe impl plain::Plain for ProgramHeader {}

    elf_program_header_std_impl!(u32);
}

pub mod program_header64 {
    pub use crate::elf::program_header::*;

    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Default)]
    #[cfg_attr(feature = "alloc", derive(Pread, Pwrite, SizeWith))]
    /// A 64-bit ProgramHeader typically specifies how to map executable and data segments into memory
    pub struct ProgramHeader {
        /// Segment type
        pub p_type: u32,
        /// Segment flags
        pub p_flags: u32,
        /// Segment file offset
        pub p_offset: u64,
        /// Segment virtual address
        pub p_vaddr: u64,
        /// Segment physical address
        pub p_paddr: u64,
        /// Segment size in file
        pub p_filesz: u64,
        /// Segment size in memory
        pub p_memsz: u64,
        /// Segment alignment
        pub p_align: u64,
    }

    pub const SIZEOF_PHDR: usize = 56;

    // Declare that this is a plain type.
    unsafe impl plain::Plain for ProgramHeader {}

    elf_program_header_std_impl!(u64);
}
