//! ARM Generic Interrupt Controller v2 driver

use core::sync::atomic::Ordering;

use aarch64_cpu::asm::barrier;
use abi::error::Error;
use alloc::sync::Arc;
use device_api::{
    device::{Device, DeviceInitContext},
    interrupt::{
        ExternalInterruptController, FixedInterruptTable, InterruptHandler, InterruptTable,
        IpiDeliveryTarget, IpiMessage, Irq, IrqHandle, IrqLevel, IrqOptions, IrqTrigger, IrqVector,
        LocalInterruptController,
    },
};
use device_tree::{
    driver::{device_tree_driver, DeviceTreeInterruptController, Node, ProbeContext},
    DeviceTreePropertyRead, TProp,
};
use kernel_arch_aarch64::{GicInterface, CPU_COUNT};
use libk::{arch::Cpu, device::register_external_interrupt_controller, task::cpu_index};
use libk_mm::{
    address::PhysicalAddress,
    device::{DeviceMemoryIo, RawDeviceMemoryMapping},
};
use libk_util::sync::spin_rwlock::IrqSafeRwLock;

use self::{gicc::Gicc, gicd::Gicd};

use super::AArch64;

const MAX_IRQ: usize = 300;
const IPI_VECTOR: u64 = 1;

const GIC_PPI_START: u32 = 16;
const GIC_SPI_START: u32 = 32;

pub mod gicc;
pub mod gicd;
pub mod gicv2m;

/// ARM Generic Interrupt Controller v2
pub struct Gic {
    gicc: Gicc,
    gicd: Gicd,
    table: IrqSafeRwLock<FixedInterruptTable<MAX_IRQ>>,
}

unsafe impl Sync for Gic {}

/// Per-CPU GIC information
pub struct GicPerCpu {}

impl GicInterface for Gic {}

impl Device for Gic {
    fn display_name(&self) -> &str {
        "ARM Generic Interrupt Controller v2"
    }

    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        register_external_interrupt_controller(self.clone());
        AArch64::set_gic(self.clone());
        Ok(())
    }
}

impl ExternalInterruptController for Gic {
    fn register_irq(
        &self,
        irq: Irq,
        options: IrqOptions,
        handler: Arc<dyn InterruptHandler>,
    ) -> Result<(), Error> {
        let mut table = self.table.write();

        let index = match irq {
            Irq::External(i) => i + GIC_SPI_START,
            Irq::Private(i) => i + GIC_PPI_START,
        } as usize;

        log::debug!(
            "Bound irq{} to {:?} {:?} {:?}",
            index,
            handler.display_name(),
            options.trigger,
            options.level
        );
        if index >= GIC_SPI_START as usize {
            self.gicd.configure_irq(index, options);
        }
        table.insert(index, handler)?;

        Ok(())
    }

    fn enable_irq(&self, irq: Irq) -> Result<(), Error> {
        let index = match irq {
            Irq::External(i) => i + GIC_SPI_START,
            Irq::Private(i) => i + GIC_PPI_START,
        } as usize;
        log::debug!("Enable irq{index} ({irq:?})");
        self.gicd.enable_irq(index);
        Ok(())
    }

    fn handle_pending_irqs(&self) {
        let irq_number = self.gicc.pending_irq_number();
        if irq_number >= MAX_IRQ {
            return;
        }

        self.gicc.clear_irq(irq_number);

        if irq_number == IPI_VECTOR as usize {
            let ipi = Cpu::local().get_ipi();
            AArch64::handle_ipi(ipi);
            return;
        }

        if irq_number < GIC_PPI_START as usize {
            return;
        }

        let irq = if irq_number < GIC_SPI_START as usize {
            Irq::Private(irq_number as u32 - GIC_PPI_START)
        } else {
            Irq::External(irq_number as u32 - GIC_SPI_START)
        };

        {
            let table = self.table.read();
            let entry = match table.handler(irq_number) {
                Some(handler) => handler.clone(),
                None => {
                    log::warn!("No handler for irq{}", irq_number);
                    return;
                }
            };

            entry.handle_irq(IrqVector::Irq(irq));
        }
    }
}

impl LocalInterruptController for Gic {
    fn send_ipi(&self, target: IpiDeliveryTarget, msg: IpiMessage) -> Result<(), Error> {
        let local = cpu_index();
        let mask = match target {
            IpiDeliveryTarget::OtherCpus => usize::MAX & !(1 << local),
            IpiDeliveryTarget::Specific(mask) => mask,
            IpiDeliveryTarget::ThisCpu => 1 << local,
        };

        for i in 0..CPU_COUNT.load(Ordering::Acquire) {
            if mask & (1 << i) != 0 {
                Cpu::push_ipi_queue(i as _, msg);
            }
        }

        // Issue a memory barrier
        barrier::dsb(barrier::ISH);
        barrier::isb(barrier::SY);

        unsafe {
            self.gicd.set_sgir(target, IPI_VECTOR);
        }

        Ok(())
    }

    unsafe fn init_ap(&self) -> Result<(), Error> {
        self.gicc.init();
        Ok(())
    }
}

impl DeviceTreeInterruptController for Gic {
    fn map_interrupt(self: Arc<Self>, property: &TProp, offset: usize) -> Option<IrqHandle> {
        // IRQ_TYPE_NONE - 0
        // IRQ_TYPE_EDGE_RISING - 1
        // IRQ_TYPE_EDGE_FALLING - 2
        // IRQ_TYPE_EDGE_BOTH - 3
        // IRQ_TYPE_LEVEL_HIGH - 4
        // IRQ_TYPE_LEVEL_LOW - 8

        let sizes = (1, 1, 1);
        let (kind, number, options) = property.read_cells_at(offset, sizes)?;
        let number = number as u32;

        let (trigger, level) = match options & 0xF {
            0x01 => (IrqTrigger::Edge, IrqLevel::ActiveHigh),
            0x02 => (IrqTrigger::Edge, IrqLevel::ActiveLow),
            0x03 => return None, // TODO
            0x04 => (IrqTrigger::Level, IrqLevel::ActiveHigh),
            0x08 => (IrqTrigger::Level, IrqLevel::ActiveLow),
            _ => return None,
        };
        let irq = match kind {
            0x00 => Irq::External(number),
            0x01 => Irq::Private(number),
            _ => return None,
        };

        Some(IrqHandle {
            irq,
            options: IrqOptions { trigger, level },
            intc: self.clone(),
        })
    }

    fn as_interrupt_controller(self: Arc<Self>) -> Arc<dyn ExternalInterruptController> {
        self
    }
}

impl Gic {
    /// Constructs an instance of GICv2.
    ///
    /// # Safety
    ///
    /// The caller must ensure the addresses actually point to the GIC components.
    pub unsafe fn new(
        gicd_base: PhysicalAddress,
        gicc_base: PhysicalAddress,
    ) -> Result<Self, Error> {
        log::debug!("Init GIC: gicd={:#x}, gicc={:#x}", gicd_base, gicc_base);
        let gicd_mmio = Arc::new(RawDeviceMemoryMapping::map(
            gicd_base.into_u64(),
            0x1000,
            Default::default(),
        )?);
        let gicd_mmio_shared = DeviceMemoryIo::from_raw(gicd_mmio.clone())?;
        let gicd_mmio_banked = DeviceMemoryIo::from_raw(gicd_mmio)?;
        let gicc_mmio = DeviceMemoryIo::map(gicc_base, Default::default())?;

        let gicd = Gicd::new(gicd_mmio_shared, gicd_mmio_banked);
        let gicc = Gicc::new(gicc_mmio);

        gicd.init();
        gicc.init();

        // self.gicd.init(gicd);
        // self.gicc.init(gicc);

        Ok(Self {
            gicd,
            gicc,
            table: IrqSafeRwLock::new(FixedInterruptTable::new()),
        })
    }
}

impl GicPerCpu {
    /// Constructs per-CPU GIC data structure
    pub fn new() -> Self {
        Self {}
    }
}

device_tree_driver! {
    compatible: ["arm,cortex-a15-gic", "arm,gic-400"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let gicd_range = node.map_range(context, 0)?;
            let gicc_range = node.map_range(context, 1)?;

            let gicd_base = PhysicalAddress::from_u64(gicd_range.start);
            let gicc_base = PhysicalAddress::from_u64(gicc_range.start);

            let gic = Arc::new(unsafe {
                Gic::new(gicd_base, gicc_base)
                    .inspect_err(|e| log::error!("GIC probe/init error: {e:?}"))
                    .ok()?
            });

            // Register device-tree interrupt controller
            node.make_interrupt_controller(gic.clone());

            Some(gic)
        }
    }
}
