use core::fmt;

use alloc::{collections::btree_map::BTreeMap, sync::Arc, vec::Vec};
use device_api::interrupt::MessageInterruptController;
use libk::error::Error;

use crate::{
    device::{PciInterrupt, PciInterruptRoute, PciMsiRoute},
    PciAddress,
};

#[derive(Debug)]
pub enum PciInterruptMap {
    Fixed(BTreeMap<PciInterrupt, PciInterruptRoute>),
    #[cfg(any(target_arch = "x86_64", rust_analyzer))]
    Acpi(alloc::string::String),
    Legacy,
}

// TODO device-tree also provides a "msi-base" value, which is ignored and assumed to be zero for
//      now
pub struct PciFixedMsiMapping {
    pub start_address: PciAddress,
    pub end_address: PciAddress,
    pub controller: Arc<dyn MessageInterruptController>,
}

pub struct PciFixedMsiMap {
    pub entries: Vec<PciFixedMsiMapping>,
}

pub enum PciMsiMap {
    Fixed(PciFixedMsiMap),
    Identity(Arc<dyn MessageInterruptController>),
    Legacy,
}

impl PciInterruptMap {
    pub fn map_interrupt(&self, interrupt: &PciInterrupt) -> Result<PciInterruptRoute, Error> {
        match self {
            Self::Fixed(map) => map.get(interrupt).cloned().ok_or(Error::DoesNotExist),
            #[cfg(any(target_arch = "x86_64", rust_analyzer))]
            Self::Acpi(aml_object_name) => {
                use device_api::interrupt::{IrqLevel, IrqOptions, IrqTrigger};

                use crate::device::PciInterruptPin;

                let aml_pin = match interrupt.pin {
                    PciInterruptPin::A => ygg_driver_acpi::PciPin::IntA,
                    PciInterruptPin::B => ygg_driver_acpi::PciPin::IntB,
                    PciInterruptPin::C => ygg_driver_acpi::PciPin::IntC,
                    PciInterruptPin::D => ygg_driver_acpi::PciPin::IntD,
                };
                let aml_route = ygg_driver_acpi::get_pci_route(
                    aml_object_name.as_str(),
                    interrupt.address.device as u16,
                    interrupt.address.function as u16,
                    aml_pin,
                )
                .or_else(|| {
                    ygg_driver_acpi::get_pci_route(
                        aml_object_name.as_str(),
                        interrupt.address.device as u16,
                        0xFFFF,
                        aml_pin,
                    )
                })
                .ok_or(Error::DoesNotExist)?;

                let trigger = match aml_route.trigger {
                    ygg_driver_acpi::InterruptTrigger::Edge => IrqTrigger::Edge,
                    ygg_driver_acpi::InterruptTrigger::Level => IrqTrigger::Level,
                };
                let level = match aml_route.polarity {
                    ygg_driver_acpi::InterruptPolarity::ActiveLow => IrqLevel::ActiveLow,
                    ygg_driver_acpi::InterruptPolarity::ActiveHigh => IrqLevel::ActiveHigh,
                };

                Ok(PciInterruptRoute {
                    options: IrqOptions { trigger, level },
                    number: aml_route.irq,
                })
            }
            Self::Legacy => todo!(),
        }
    }
}

impl PciMsiMap {
    pub fn map_msi(&self, address: PciAddress) -> Option<PciMsiRoute> {
        match self {
            Self::Fixed(map) => map.map_msi(address),
            Self::Identity(controller) => Some(PciMsiRoute {
                controller: controller.clone(),
            }),
            Self::Legacy => None,
        }
    }
}

impl fmt::Debug for PciMsiMap {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Legacy => f.debug_struct("Legacy").finish(),
            Self::Fixed(map) => f
                .debug_struct("Fixed")
                .field("entries", &map.entries)
                .finish(),
            Self::Identity(_) => f.debug_struct("Identity").finish(),
        }
    }
}

impl PciFixedMsiMap {
    pub fn map_msi(&self, address: PciAddress) -> Option<PciMsiRoute> {
        for entry in self.entries.iter() {
            if entry.contains(address) {
                let route = PciMsiRoute {
                    controller: entry.controller.clone(),
                };
                return Some(route);
            }
        }
        None
    }
}

impl PciFixedMsiMapping {
    pub fn contains(&self, address: PciAddress) -> bool {
        self.start_address <= address && self.end_address > address
    }
}

impl fmt::Debug for PciFixedMsiMapping {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PciFixedMsiMapping")
            .field("start_address", &self.start_address)
            .field("end_address", &self.end_address)
            .finish()
    }
}
