use crate::{AcpiError, AcpiHandler, AcpiResult, AcpiTable, PhysicalMapping};
use core::{fmt, mem::MaybeUninit, str};

/// Represents a field which may or may not be present within an ACPI structure, depending on the version of ACPI
/// that a system supports. If the field is not present, it is not safe to treat the data as initialised.
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct ExtendedField<T: Copy, const MIN_REVISION: u8>(MaybeUninit<T>);

impl<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
    /// Access the field if it's present for the given revision of the table.
    ///
    /// ### Safety
    /// If a bogus ACPI version is passed, this function may access uninitialised data.
    pub unsafe fn access(&self, revision: u8) -> Option<T> {
        if revision >= MIN_REVISION {
            Some(unsafe { self.0.assume_init() })
        } else {
            None
        }
    }
}

/// All SDTs share the same header, and are `length` bytes long. The signature tells us which SDT
/// this is.
///
/// The ACPI Spec (Version 6.4) defines the following SDT signatures:
///
/// * APIC - Multiple APIC Description Table (MADT)
/// * BERT - Boot Error Record Table
/// * BGRT - Boot Graphics Resource Table
/// * CPEP - Corrected Platform Error Polling Table
/// * DSDT - Differentiated System Description Table (DSDT)
/// * ECDT - Embedded Controller Boot Resources Table
/// * EINJ - Error Injection Table
/// * ERST - Error Record Serialization Table
/// * FACP - Fixed ACPI Description Table (FADT)
/// * FACS - Firmware ACPI Control Structure
/// * FPDT - Firmware Performance Data Table
/// * GTDT - Generic Timer Description Table
/// * HEST - Hardware Error Source Table
/// * MSCT - Maximum System Characteristics Table
/// * MPST - Memory Power StateTable
/// * NFIT - NVDIMM Firmware Interface Table
/// * OEMx - OEM Specific Information Tables
/// * PCCT - Platform Communications Channel Table
/// * PHAT - Platform Health Assessment Table
/// * PMTT - Platform Memory Topology Table
/// * PSDT - Persistent System Description Table
/// * RASF - ACPI RAS Feature Table
/// * RSDT - Root System Description Table
/// * SBST - Smart Battery Specification Table
/// * SDEV - Secure DEVices Table
/// * SLIT - System Locality Distance Information Table
/// * SRAT - System Resource Affinity Table
/// * SSDT - Secondary System Description Table
/// * XSDT - Extended System Description Table
///
/// Acpi reserves the following signatures and the specifications for them can be found [here](https://uefi.org/acpi):
///
/// * AEST - ARM Error Source Table
/// * BDAT - BIOS Data ACPI Table
/// * CDIT - Component Distance Information Table
/// * CEDT - CXL Early Discovery Table
/// * CRAT - Component Resource Attribute Table
/// * CSRT - Core System Resource Table
/// * DBGP - Debug Port Table
/// * DBG2 - Debug Port Table 2 (note: ACPI 6.4 defines this as "DBPG2" but this is incorrect)
/// * DMAR - DMA Remapping Table
/// * DRTM -Dynamic Root of Trust for Measurement Table
/// * ETDT - Event Timer Description Table (obsolete, superseeded by HPET)
/// * HPET - IA-PC High Precision Event Timer Table
/// * IBFT - iSCSI Boot Firmware Table
/// * IORT - I/O Remapping Table
/// * IVRS - I/O Virtualization Reporting Structure
/// * LPIT - Low Power Idle Table
/// * MCFG - PCI Express Memory-mapped Configuration Space base address description table
/// * MCHI - Management Controller Host Interface table
/// * MPAM - ARM Memory Partitioning And Monitoring table
/// * MSDM - Microsoft Data Management Table
/// * PRMT - Platform Runtime Mechanism Table
/// * RGRT - Regulatory Graphics Resource Table
/// * SDEI - Software Delegated Exceptions Interface table
/// * SLIC - Microsoft Software Licensing table
/// * SPCR - Microsoft Serial Port Console Redirection table
/// * SPMI - Server Platform Management Interface table
/// * STAO - _STA Override table
/// * SVKL - Storage Volume Key Data table (Intel TDX only)
/// * TCPA - Trusted Computing Platform Alliance Capabilities Table
/// * TPM2 - Trusted Platform Module 2 Table
/// * UEFI - Unified Extensible Firmware Interface Specification table
/// * WAET - Windows ACPI Emulated Devices Table
/// * WDAT - Watch Dog Action Table
/// * WDRT - Watchdog Resource Table
/// * WPBT - Windows Platform Binary Table
/// * WSMT - Windows Security Mitigations Table
/// * XENV - Xen Project
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct SdtHeader {
    pub signature: Signature,
    pub length: u32,
    pub revision: u8,
    pub checksum: u8,
    pub oem_id: [u8; 6],
    pub oem_table_id: [u8; 8],
    pub oem_revision: u32,
    pub creator_id: u32,
    pub creator_revision: u32,
}

impl SdtHeader {
    /// Whether values of header fields are permitted.
    fn validate_header_fields(&self, signature: Signature) -> AcpiResult<()> {
        // Check the signature
        if self.signature != signature || str::from_utf8(&self.signature.0).is_err() {
            return Err(AcpiError::SdtInvalidSignature(signature));
        }

        // Check the OEM id
        if str::from_utf8(&self.oem_id).is_err() {
            return Err(AcpiError::SdtInvalidOemId(signature));
        }

        // Check the OEM table id
        if str::from_utf8(&self.oem_table_id).is_err() {
            return Err(AcpiError::SdtInvalidTableId(signature));
        }

        Ok(())
    }

    /// Whether table is valid according to checksum.
    fn validate_checksum(&self, signature: Signature) -> AcpiResult<()> {
        // SAFETY: Entire table is mapped.
        let table_bytes =
            unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::<u8>(), self.length as usize) };
        let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));

        if sum == 0 {
            Ok(())
        } else {
            Err(AcpiError::SdtInvalidChecksum(signature))
        }
    }

    /// Checks that:
    ///
    /// 1. The signature matches the one given.
    /// 2. The values of various fields in the header are allowed.
    /// 3. The checksum of the SDT is valid.
    ///
    /// This assumes that the whole SDT is mapped.
    pub fn validate(&self, signature: Signature) -> AcpiResult<()> {
        self.validate_header_fields(signature)?;
        self.validate_checksum(signature)?;

        Ok(())
    }

    /// Validates header, proceeding with checking entire table and returning a [`PhysicalMapping`] to it if
    /// successful.
    ///
    /// The same checks are performed as [`SdtHeader::validate`], but `header_mapping` does not have to map the
    /// entire table when calling. This is useful to avoid completely mapping a table that will be immediately
    /// unmapped if it does not have a particular signature or has an invalid header.
    pub(crate) fn validate_lazy<H: AcpiHandler, T: AcpiTable>(
        header_mapping: PhysicalMapping<H, Self>,
        handler: H,
    ) -> AcpiResult<PhysicalMapping<H, T>> {
        header_mapping.validate_header_fields(T::SIGNATURE)?;

        // Reuse `header_mapping` to access the rest of the table if the latter is already mapped entirely
        let table_length = header_mapping.length as usize;
        let table_mapping = if header_mapping.mapped_length() >= table_length {
            // Avoid requesting table unmap twice (from both `header_mapping` and `table_mapping`)
            let header_mapping = core::mem::ManuallyDrop::new(header_mapping);

            // SAFETY: `header_mapping` maps entire table.
            unsafe {
                PhysicalMapping::new(
                    header_mapping.physical_start(),
                    header_mapping.virtual_start().cast::<T>(),
                    table_length,
                    header_mapping.mapped_length(),
                    handler,
                )
            }
        } else {
            // Unmap header as soon as possible
            let table_phys_start = header_mapping.physical_start();
            drop(header_mapping);

            // SAFETY: `table_phys_start` is the physical address of the header and the rest of the table.
            unsafe { handler.map_physical_region(table_phys_start, table_length) }
        };

        // This is usually redundant compared to simply calling `validate_checksum` but respects custom
        // `AcpiTable::validate` implementations.
        table_mapping.validate()?;

        Ok(table_mapping)
    }

    pub fn oem_id(&self) -> &str {
        // Safe to unwrap because checked in `validate`
        str::from_utf8(&self.oem_id).unwrap()
    }

    pub fn oem_table_id(&self) -> &str {
        // Safe to unwrap because checked in `validate`
        str::from_utf8(&self.oem_table_id).unwrap()
    }
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Signature([u8; 4]);

impl Signature {
    pub const RSDT: Signature = Signature(*b"RSDT");
    pub const XSDT: Signature = Signature(*b"XSDT");
    pub const FADT: Signature = Signature(*b"FACP");
    pub const HPET: Signature = Signature(*b"HPET");
    pub const MADT: Signature = Signature(*b"APIC");
    pub const MCFG: Signature = Signature(*b"MCFG");
    pub const SSDT: Signature = Signature(*b"SSDT");
    pub const BERT: Signature = Signature(*b"BERT");
    pub const BGRT: Signature = Signature(*b"BGRT");
    pub const CPEP: Signature = Signature(*b"CPEP");
    pub const DSDT: Signature = Signature(*b"DSDT");
    pub const ECDT: Signature = Signature(*b"ECDT");
    pub const EINJ: Signature = Signature(*b"EINJ");
    pub const ERST: Signature = Signature(*b"ERST");
    pub const FACS: Signature = Signature(*b"FACS");
    pub const FPDT: Signature = Signature(*b"FPDT");
    pub const GTDT: Signature = Signature(*b"GTDT");
    pub const HEST: Signature = Signature(*b"HEST");
    pub const MSCT: Signature = Signature(*b"MSCT");
    pub const MPST: Signature = Signature(*b"MPST");
    pub const NFIT: Signature = Signature(*b"NFIT");
    pub const PCCT: Signature = Signature(*b"PCCT");
    pub const PHAT: Signature = Signature(*b"PHAT");
    pub const PMTT: Signature = Signature(*b"PMTT");
    pub const PSDT: Signature = Signature(*b"PSDT");
    pub const RASF: Signature = Signature(*b"RASF");
    pub const SBST: Signature = Signature(*b"SBST");
    pub const SDEV: Signature = Signature(*b"SDEV");
    pub const SLIT: Signature = Signature(*b"SLIT");
    pub const SRAT: Signature = Signature(*b"SRAT");
    pub const AEST: Signature = Signature(*b"AEST");
    pub const BDAT: Signature = Signature(*b"BDAT");
    pub const CDIT: Signature = Signature(*b"CDIT");
    pub const CEDT: Signature = Signature(*b"CEDT");
    pub const CRAT: Signature = Signature(*b"CRAT");
    pub const CSRT: Signature = Signature(*b"CSRT");
    pub const DBGP: Signature = Signature(*b"DBGP");
    pub const DBG2: Signature = Signature(*b"DBG2");
    pub const DMAR: Signature = Signature(*b"DMAR");
    pub const DRTM: Signature = Signature(*b"DRTM");
    pub const ETDT: Signature = Signature(*b"ETDT");
    pub const IBFT: Signature = Signature(*b"IBFT");
    pub const IORT: Signature = Signature(*b"IORT");
    pub const IVRS: Signature = Signature(*b"IVRS");
    pub const LPIT: Signature = Signature(*b"LPIT");
    pub const MCHI: Signature = Signature(*b"MCHI");
    pub const MPAM: Signature = Signature(*b"MPAM");
    pub const MSDM: Signature = Signature(*b"MSDM");
    pub const PRMT: Signature = Signature(*b"PRMT");
    pub const RGRT: Signature = Signature(*b"RGRT");
    pub const SDEI: Signature = Signature(*b"SDEI");
    pub const SLIC: Signature = Signature(*b"SLIC");
    pub const SPCR: Signature = Signature(*b"SPCR");
    pub const SPMI: Signature = Signature(*b"SPMI");
    pub const STAO: Signature = Signature(*b"STAO");
    pub const SVKL: Signature = Signature(*b"SVKL");
    pub const TCPA: Signature = Signature(*b"TCPA");
    pub const TPM2: Signature = Signature(*b"TPM2");
    pub const UEFI: Signature = Signature(*b"UEFI");
    pub const WAET: Signature = Signature(*b"WAET");
    pub const WDAT: Signature = Signature(*b"WDAT");
    pub const WDRT: Signature = Signature(*b"WDRT");
    pub const WPBT: Signature = Signature(*b"WPBT");
    pub const WSMT: Signature = Signature(*b"WSMT");
    pub const XENV: Signature = Signature(*b"XENV");

    pub fn as_str(&self) -> &str {
        str::from_utf8(&self.0).unwrap()
    }
}

impl fmt::Display for Signature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl fmt::Debug for Signature {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\"{}\"", self.as_str())
    }
}