pub mod doc;
pub mod exec;
pub mod utils;

use crate::exec::ExecutableFile;
use anyhow::Result;
use chrono::{DateTime, Utc};

/// MDB version
pub const MDB_VERSION: &str = env!("CARGO_PKG_VERSION");

pub trait TypeMagic {
    // Some file types have more than one possible magic number
    const MAGIC: &'static [&'static [u8]];
}

#[allow(clippy::large_enum_variant)]
pub enum KnownType<'a> {
    /// Linux, *BSD, Haiku, etc binaries
    #[cfg(feature = "elf")]
    ELF(exec::elf::Elf<'a>),

    /// Windows, DOS, OS/2 Executables. Anything ending with:
    /// * .cpl
    /// * .dll
    /// * .exe
    /// * .ocx
    /// * .sys
    #[cfg(feature = "pe32")]
    EXE(exec::pe32::EXE<'a>),

    /// Single architecture macOS (and derivatives) binaries
    #[cfg(feature = "macho")]
    MachO(exec::macho::Macho<'a>),

    /// Multiple architecture macOS (and derivatives) binaries
    #[cfg(feature = "macho")]
    FatMachO(exec::macho::fat::FatMacho<'a>),

    /// Classic Mac OS and BeOS
    #[cfg(feature = "pef")]
    PEF(exec::pef::Pef<'a>),

    /// Adobe PDF document
    #[cfg(feature = "pdf")]
    PDF(doc::pdf::PDF<'a>),

    /// Files for which we don't have an analytic or feature extractor
    Unknown,
}

impl<'a> KnownType<'a> {
    pub fn new(data: &'a [u8]) -> Result<Self> {
        // TODO: Replace the checking of byte arrays with a hashing mechanism for faster matching
        #[cfg(feature = "elf")]
        if data.starts_with(exec::elf::Elf::MAGIC[0]) {
            return Ok(Self::ELF(exec::elf::Elf::from(data)?));
        }

        #[cfg(feature = "pe32")]
        if data.starts_with(exec::pe32::EXE::MAGIC[0])
            || data.starts_with(exec::pe32::EXE::MAGIC[1])
        {
            return Ok(Self::EXE(exec::pe32::EXE::from(data)?));
        }

        #[cfg(feature = "macho")]
        for mach_magic in exec::macho::Macho::MAGIC {
            if data.starts_with(mach_magic) {
                return Ok(Self::MachO(exec::macho::Macho::from(data)?));
            }
        }

        #[cfg(feature = "macho")]
        for mach_magic in exec::macho::fat::FatMacho::MAGIC {
            if data.starts_with(mach_magic) {
                return Ok(Self::FatMachO(exec::macho::fat::FatMacho::from(data)?));
            }
        }

        #[cfg(feature = "pdf")]
        if data.starts_with(doc::pdf::PDF::MAGIC[0]) {
            return Ok(Self::PDF(doc::pdf::PDF::from(data)?));
        }

        #[cfg(feature = "pef")]
        if data.starts_with(exec::pef::Pef::MAGIC[0]) {
            return Ok(Self::PEF(exec::pef::Pef::from(data)?));
        }

        Ok(Self::Unknown)
    }

    pub fn is_exec(&self) -> bool {
        match self {
            #[cfg(feature = "elf")]
            KnownType::ELF(_) => true,

            #[cfg(feature = "pe32")]
            KnownType::EXE(_) => true,

            #[cfg(feature = "macho")]
            KnownType::MachO(_) => true,

            #[cfg(feature = "macho")]
            KnownType::FatMachO(_) => true,

            #[cfg(feature = "pef")]
            KnownType::PEF(_) => true,

            _ => false,
        }
    }

    pub fn is_doc(&self) -> bool {
        match self {
            #[cfg(feature = "pdf")]
            KnownType::PDF(_) => true,

            _ => false,
        }
    }

    pub fn created(&self) -> Option<DateTime<Utc>> {
        match self {
            #[cfg(feature = "pe32")]
            KnownType::EXE(e) => e.compiled_timestamp(),

            #[cfg(feature = "pef")]
            KnownType::PEF(p) => p.compiled_timestamp(),

            #[cfg(feature = "pdf")]
            KnownType::PDF(p) => p.creation_date,

            _ => None,
        }
    }

    pub fn exec(self) -> Option<Box<dyn ExecutableFile + Send + 'a>> {
        match self {
            #[cfg(feature = "elf")]
            KnownType::ELF(e) => Some(Box::new(e)),

            #[cfg(feature = "pe32")]
            KnownType::EXE(e) => Some(Box::new(e)),

            #[cfg(feature = "macho")]
            KnownType::MachO(m) => Some(Box::new(m)),

            #[cfg(feature = "macho")]
            KnownType::FatMachO(m) => Some(Box::new(m)),

            #[cfg(feature = "pef")]
            KnownType::PEF(p) => Some(Box::new(p)),
            _ => None,
        }
    }

    pub fn doc(self) -> Option<Box<doc::pdf::PDF<'a>>> {
        match self {
            KnownType::PDF(p) => Some(Box::new(p)),
            _ => None,
        }
    }
}
