macro_rules! elf_compression_header {
    () => {
        use plain;
        // Declare that this is a plain type.
        unsafe impl plain::Plain for CompressionHeader {}

        impl ::core::fmt::Debug for CompressionHeader {
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                f.debug_struct("CompressionHeader")
                    .field("ch_type", &self.ch_type)
                    .field("ch_size", &format_args!("0x{:x}", self.ch_size))
                    .field("ch_addralign", &format_args!("0x{:x}", self.ch_addralign))
                    .finish()
            }
        }
    };
}

/// ZLIB/DEFLATE algorithm.
pub const ELFCOMPRESS_ZLIB: u32 = 1;
/// Zstandard algorithm.
pub const ELFCOMPRESS_ZSTD: u32 = 2;
/// Start of OS-specific.
pub const ELFCOMPRESS_LOOS: u32 = 0x6000_0000;
/// End of OS-specific.
pub const ELFCOMPRESS_HIOS: u32 = 0x6fff_ffff;
/// Start of processor-specific.
pub const ELFCOMPRESS_LOPROC: u32 = 0x7000_0000;
/// End of processor-specific.
pub const ELFCOMPRESS_HIPROC: u32 = 0x7fff_ffff;

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

        if_alloc! {
            use crate::elf::compression_header::CompressionHeader as ElfCompressionHeader;

            use plain::Plain;

            if_std! {
                use crate::error::Result;

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

            impl From<CompressionHeader> for ElfCompressionHeader {
                fn from(ch: CompressionHeader) -> Self {
                    ElfCompressionHeader {
                        ch_type: ch.ch_type,
                        ch_size: u64::from(ch.ch_size),
                        ch_addralign: u64::from(ch.ch_addralign),
                    }
                }
            }

            impl CompressionHeader {
                pub fn from_bytes(bytes: &[u8]) -> CompressionHeader {
                    let mut chdr = CompressionHeader::default();
                    chdr.copy_from_bytes(bytes).expect("buffer is too short for header");
                    chdr
                }

                #[cfg(feature = "std")]
                pub fn from_fd(fd: &mut File, offset: u64) -> Result<CompressionHeader> {
                    let mut chdr = CompressionHeader::default();
                    fd.seek(Start(offset))?;
                    unsafe {
                        fd.read_exact(plain::as_mut_bytes(&mut chdr))?;
                    }
                    Ok(chdr)
                }
            }
        } // end if_alloc
    };
}

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

pub mod compression_header32 {
    pub use crate::elf::compression_header::*;

    #[repr(C)]
    #[derive(Copy, Clone, Eq, PartialEq, Default)]
    #[cfg_attr(feature = "alloc", derive(Pread, Pwrite, SizeWith))]
    /// The compression header is used at the start of SHF_COMPRESSED sections
    pub struct CompressionHeader {
        /// Compression format
        pub ch_type: u32,
        /// Uncompressed data size
        pub ch_size: u32,
        /// Uncompressed data alignment
        pub ch_addralign: u32,
    }

    elf_compression_header!();

    pub const SIZEOF_CHDR: usize = 12;

    elf_compression_header_std_impl!(u32);

    if_alloc! {
        impl From<ElfCompressionHeader> for CompressionHeader {
            fn from(ch: ElfCompressionHeader) -> Self {
                CompressionHeader {
                    ch_type: ch.ch_type,
                    ch_size: ch.ch_size as u32,
                    ch_addralign: ch.ch_addralign as u32,
                }
            }
        }
    }
}

pub mod compression_header64 {
    pub use crate::elf::compression_header::*;

    #[repr(C)]
    #[derive(Copy, Clone, Eq, PartialEq, Default)]
    #[cfg_attr(feature = "alloc", derive(Pread, Pwrite, SizeWith))]
    /// The compression header is used at the start of SHF_COMPRESSED sections
    pub struct CompressionHeader {
        /// Compression format
        pub ch_type: u32,
        pub ch_reserved: u32,
        /// Uncompressed data size
        pub ch_size: u64,
        /// Uncompressed data alignment
        pub ch_addralign: u64,
    }

    elf_compression_header!();

    pub const SIZEOF_CHDR: usize = 24;

    elf_compression_header_std_impl!(u64);

    if_alloc! {
        impl From<ElfCompressionHeader> for CompressionHeader {
            fn from(ch: ElfCompressionHeader) -> Self {
                CompressionHeader {
                    ch_type: ch.ch_type,
                    ch_reserved: 0,
                    ch_size: ch.ch_size as u64,
                    ch_addralign: ch.ch_addralign as u64,
                }
            }
        }
    }
}

///////////////////////////////
// Std/analysis/Unified Structs
///////////////////////////////

if_alloc! {
    #[cfg(feature = "endian_fd")]
    use crate::error;
    use core::fmt;
    use core::result;
    use scroll::ctx;
    use crate::container::{Container, Ctx};

    #[derive(Default, PartialEq, Clone)]
    /// A unified CompressionHeader - convertable to and from 32-bit and 64-bit variants
    pub struct CompressionHeader {
        /// Compression format
        pub ch_type: u32,
        /// Uncompressed data size
        pub ch_size: u64,
        /// Uncompressed data alignment
        pub ch_addralign: u64,
    }

    impl CompressionHeader {
        /// Return the size of the underlying compression header, given a `container`
        #[inline]
        pub fn size(ctx: Ctx) -> usize {
            use scroll::ctx::SizeWith;
            Self::size_with(&ctx)
        }
        pub fn new() -> Self {
            CompressionHeader {
                ch_type: 0,
                ch_size: 0,
                ch_addralign: 2 << 8,
            }
        }
        /// Parse a compression header from `bytes` at `offset`, using the given `ctx`
        #[cfg(feature = "endian_fd")]
        pub fn parse(bytes: &[u8], mut offset: usize, ctx: Ctx) -> error::Result<CompressionHeader> {
            use scroll::Pread;
            bytes.gread_with(&mut offset, ctx)
        }
    }

    impl fmt::Debug for CompressionHeader {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            f.debug_struct("CompressionHeader")
                .field("ch_type", &self.ch_type)
                .field("ch_size", &format_args!("0x{:x}", self.ch_size))
                .field("ch_addralign", &format_args!("0x{:x}", self.ch_addralign))
                .finish()
        }
    }

    impl ctx::SizeWith<Ctx> for CompressionHeader {
        fn size_with( &Ctx { container, .. }: &Ctx) -> usize {
            match container {
                Container::Little => {
                    compression_header32::SIZEOF_CHDR
                },
                Container::Big => {
                    compression_header64::SIZEOF_CHDR
                },
            }
        }
    }

    impl<'a> ctx::TryFromCtx<'a, Ctx> for CompressionHeader {
        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::<compression_header32::CompressionHeader>(0, le)?.into(), compression_header32::SIZEOF_CHDR)
                },
                Container::Big => {
                    (bytes.pread_with::<compression_header64::CompressionHeader>(0, le)?.into(), compression_header64::SIZEOF_CHDR)
                }
            };
            Ok(res)
        }
    }

    impl ctx::TryIntoCtx<Ctx> for CompressionHeader {
        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 chdr: compression_header32::CompressionHeader = self.into();
                    Ok(bytes.pwrite_with(chdr, 0, le)?)
                },
                Container::Big => {
                    let chdr: compression_header64::CompressionHeader = self.into();
                    Ok(bytes.pwrite_with(chdr, 0, le)?)
                }
            }
        }
    }
    impl ctx::IntoCtx<Ctx> for CompressionHeader {
        fn into_ctx(self, bytes: &mut [u8], Ctx {container, le}: Ctx) {
            use scroll::Pwrite;
            match container {
                Container::Little => {
                    let chdr: compression_header32::CompressionHeader = self.into();
                    bytes.pwrite_with(chdr, 0, le).unwrap();
                },
                Container::Big => {
                    let chdr: compression_header64::CompressionHeader = self.into();
                    bytes.pwrite_with(chdr, 0, le).unwrap();
                }
            }
        }
    }
} // end if_alloc
