use crate::Elf64Word;

/// indicates the segment is unused program header table entry.
pub const ELF_PROGRAM_HEADER_TYPE_NULL: Elf64Word = 0;
/// indicates the segment is loadable.
pub const ELF_PROGRAM_HEADER_TYPE_LOAD: Elf64Word = 1;
/// indicates the segment holds dynamic linking information.
pub const ELF_PROGRAM_HEADER_TYPE_DYNAMIC: Elf64Word = 2;
/// indicates the segment references program interpreter.
pub const ELF_PROGRAM_HEADER_TYPE_INTERP: Elf64Word = 3;
/// indicates the segment holds auxiliary information.
pub const ELF_PROGRAM_HEADER_TYPE_NOTE: Elf64Word = 4;
/// indicates the segment is reserved.
pub const ELF_PROGRAM_HEADER_TYPE_SHLIB: Elf64Word = 5;
/// indicates the segment is the entry for header table itself.
pub const ELF_PROGRAM_HEADER_TYPE_PHDR: Elf64Word = 6;
/// indicates the segment is thread-local segment.
pub const ELF_PROGRAM_HEADER_TYPE_TLS: Elf64Word = 7;
/// the number of pre-defined values.
pub const ELF_PROGRAM_HEADER_TYPE_NUM: Elf64Word = 8;
/// indicates the segment is GCC .eh_frame_hdr segment.
pub const ELF_PROGRAM_HEADER_TYPE_GNU_EH_FRAME: Elf64Word = 0x6474e550;
/// indicates stack executability.
pub const ELF_PROGRAM_HEADER_TYPE_GNU_STACK: Elf64Word = 0x6474e551;
/// indicates the segment will be read-only after relocation.
pub const ELF_PROGRAM_HEADER_TYPE_GNU_RELRO: Elf64Word = 0x6474e552;
/// indicates the segment holds GNU property.
pub const ELF_PROGRAM_HEADER_TYPE_GNU_PROPERTY: Elf64Word = 0x6474e553;

/// the type of the program header.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ProgramHeaderType {
    /// unused program header table entry
    Null,
    /// Loadable program segment
    Load,
    /// Dynamic linking information
    Dynamic,
    /// Program interpreter
    Interp,
    /// Auxiliary information
    Note,
    /// Reserved
    ShLib,
    /// Entry for header table itself
    Phdr,
    /// Thread-local storage segment
    Tls,
    /// Number of defined types
    Num,
    /// GCC .sh_frame_hdr segment
    GnuEhFrame,
    /// Indicates stack executability
    GnuStack,
    /// Read-only after relocation
    GnuRelRo,
    /// GNU property
    GnuProperty,
    /// Custom values
    Any(Elf64Word),
}
impl Default for ProgramHeaderType {
    fn default() -> Self {
        Self::Null
    }
}

impl std::fmt::Display for ProgramHeaderType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            Self::Null => "Null".to_string(),
            Self::Load => "Load".to_string(),
            Self::Dynamic => "Dynamic".to_string(),
            Self::Interp => "Interp".to_string(),
            Self::Note => "Note".to_string(),
            Self::ShLib => "ShLib".to_string(),
            Self::Phdr => "Phdr".to_string(),
            Self::Tls => "Tls".to_string(),
            Self::GnuEhFrame => "GNU_EH_FRAME".to_string(),
            Self::GnuStack => "GNU_STACK".to_string(),
            Self::GnuRelRo => "GNU_RELRO".to_string(),
            Self::GnuProperty => "GNU_PROPERTY".to_string(),
            Self::Num => format!("Unknown!({})", ELF_PROGRAM_HEADER_TYPE_NUM),
            Self::Any(v) => format!("Unknown!({})", v),
        };

        write!(f, "{}", s)
    }
}

impl From<Elf64Word> for ProgramHeaderType {
    fn from(value: Elf64Word) -> Self {
        match value {
            ELF_PROGRAM_HEADER_TYPE_NULL => Self::Null,
            ELF_PROGRAM_HEADER_TYPE_LOAD => Self::Load,
            ELF_PROGRAM_HEADER_TYPE_DYNAMIC => Self::Dynamic,
            ELF_PROGRAM_HEADER_TYPE_INTERP => Self::Interp,
            ELF_PROGRAM_HEADER_TYPE_NOTE => Self::Note,
            ELF_PROGRAM_HEADER_TYPE_SHLIB => Self::ShLib,
            ELF_PROGRAM_HEADER_TYPE_PHDR => Self::Phdr,
            ELF_PROGRAM_HEADER_TYPE_TLS => Self::Tls,
            ELF_PROGRAM_HEADER_TYPE_NUM => Self::Num,
            ELF_PROGRAM_HEADER_TYPE_GNU_EH_FRAME => Self::GnuEhFrame,
            ELF_PROGRAM_HEADER_TYPE_GNU_STACK => Self::GnuStack,
            ELF_PROGRAM_HEADER_TYPE_GNU_RELRO => Self::GnuRelRo,
            ELF_PROGRAM_HEADER_TYPE_GNU_PROPERTY => Self::GnuProperty,
            _ => Self::Any(value),
        }
    }
}
