//! x86-64 I/O APIC driver implementation
use ::acpi::platform::interrupt::{Apic as AcpiApic, Polarity, TriggerMode};
use abi::error::Error;
use alloc::sync::Arc;
use device_api::{
    device::{Device, DeviceInitContext},
    interrupt::{
        ExternalInterruptController, FixedInterruptTable, InterruptHandler, InterruptTable, Irq,
        IrqLevel, IrqOptions, IrqTrigger, IrqVector,
    },
};
use kernel_arch_x86::ISA_IRQ_OFFSET;
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock};
use tock_registers::{
    interfaces::{Readable, Writeable},
    register_structs,
    registers::{ReadWrite, WriteOnly},
};
use ygg_driver_acpi::AcpiAllocator;

use crate::arch::x86_64::apic::local::BSP_APIC_ID;

use super::{APIC_EXTERNAL_OFFSET, POPULATED_EXTERNAL_VECTORS};

// IRQ 0 is timer, IRQ 1 reserved (for now?), +32 offset for exception entries
const IO_APIC_VECTOR_OFFSET: u32 = 32 + APIC_EXTERNAL_OFFSET;

const REG_IOAPIC_VERSION: u32 = 0x01;
const REG_REDIRECTION_BASE: u32 = 0x10;

const ENTRY_LOW_MASK: u32 = 1 << 16;
const ENTRY_LOW_TRIGGER_LEVEL: u32 = 1 << 15;
const ENTRY_LOW_POLARITY_LOW: u32 = 1 << 13;
const ENTRY_LOW_DESTINATION_LOGICAL: u32 = 1 << 11;

const ENTRY_HIGH_APIC_ID_SHIFT: u32 = 24;

#[allow(dead_code)]
#[derive(Clone, Copy)]
struct IsaRedirection {
    gsi_index: u32,
    level: IrqLevel,
    trigger: IrqTrigger,
}

register_structs! {
    #[allow(non_snake_case)]
    Regs {
        (0x00 => Index: WriteOnly<u32>),
        (0x04 => _0),
        (0x10 => Data: ReadWrite<u32>),
        (0x14 => @END),
    }
}

struct Inner {
    regs: DeviceMemoryIo<'static, Regs>,
    max_gsi: u32,
}

/// I/O APIC interface. Provides a way to route and control how interrupts from external devices
/// are handled.
pub struct IoApic {
    inner: IrqSafeSpinlock<Inner>,
    isa_redirections: [Option<IsaRedirection>; 16],
    table: IrqSafeRwLock<FixedInterruptTable<{ POPULATED_EXTERNAL_VECTORS as usize }>>,
}

impl Regs {
    #[inline]
    fn read(&self, reg: u32) -> u32 {
        self.Index.set(reg);
        self.Data.get()
    }

    #[inline]
    fn write(&self, reg: u32, value: u32) {
        self.Index.set(reg);
        self.Data.set(value);
    }
}

impl Inner {
    fn map_gsi(&mut self, gsi: u32, vector: u32, apic_id: u32) -> Result<(), Error> {
        assert!(gsi <= self.max_gsi);
        assert!(vector < 0x100);

        log::info!("map_irq gsi{}, vec{}, apic{}", gsi, vector, apic_id);

        let mut low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);
        let mut high = self.regs.read(REG_REDIRECTION_BASE + gsi * 2 + 1);

        // Vector
        low &= !0xFF;
        low |= vector;
        // Destination - physical
        low &= !ENTRY_LOW_DESTINATION_LOGICAL;
        // Clear delivery mode
        low &= !(0x7 << 8);

        // Destination APIC ID
        high &= !(0xFF << ENTRY_HIGH_APIC_ID_SHIFT);
        high |= apic_id << ENTRY_HIGH_APIC_ID_SHIFT;

        self.regs.write(REG_REDIRECTION_BASE + gsi * 2, low);
        self.regs.write(REG_REDIRECTION_BASE + gsi * 2 + 1, high);

        Ok(())
    }

    fn configure_gsi(&mut self, gsi: u32, level: IrqLevel, trigger: IrqTrigger) {
        assert!(gsi <= self.max_gsi);

        let mut low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);

        match level {
            IrqLevel::Default => (),
            IrqLevel::ActiveLow => {
                low |= ENTRY_LOW_POLARITY_LOW;
            }
            IrqLevel::ActiveHigh => {
                low &= !ENTRY_LOW_POLARITY_LOW;
            }
        }

        match trigger {
            IrqTrigger::Default => (),
            IrqTrigger::Level => {
                low |= ENTRY_LOW_TRIGGER_LEVEL;
            }
            IrqTrigger::Edge => {
                low &= !ENTRY_LOW_TRIGGER_LEVEL;
            }
        }

        self.regs.write(REG_REDIRECTION_BASE + gsi * 2, low);
    }

    fn set_gsi_enabled(&mut self, gsi: u32, enabled: bool) {
        assert!(gsi <= self.max_gsi);

        let low = self.regs.read(REG_REDIRECTION_BASE + gsi * 2);
        if enabled {
            log::debug!("Unmask GSI #{}", gsi);
            self.regs
                .write(REG_REDIRECTION_BASE + gsi * 2, low & !ENTRY_LOW_MASK)
        } else {
            self.regs
                .write(REG_REDIRECTION_BASE + gsi * 2, low | ENTRY_LOW_MASK);
        }
    }
}

impl Device for IoApic {
    fn display_name(&self) -> &'static str {
        "I/O APIC"
    }

    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        unreachable!()
    }
}

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

        let gsi_target_vector = (table_vector as u32) + IO_APIC_VECTOR_OFFSET;
        let bsp_apic = *BSP_APIC_ID.get();

        log::info!(
            "Binding {:?} ({}) to {}:{}",
            irq,
            handler.display_name(),
            bsp_apic,
            table_vector
        );

        let (gsi, level, trigger) = match irq {
            Irq::External(irq) if irq >= ISA_IRQ_OFFSET => {
                if let Some(redir) = self.isa_redirections[(irq - ISA_IRQ_OFFSET) as usize].as_ref()
                {
                    // Mapped to a (possibly different) GSI, but also with possibly different options
                    (
                        redir.gsi_index,
                        options.level.override_default(redir.level),
                        options.trigger.override_default(redir.trigger),
                    )
                } else {
                    // Directly mapped to a GSI
                    (irq - ISA_IRQ_OFFSET, options.level, options.trigger)
                }
            }
            Irq::External(irq) => (irq, options.level, options.trigger),
            Irq::Private(_) => unimplemented!(),
        };

        inner.configure_gsi(gsi, level, trigger);
        inner.map_gsi(gsi, gsi_target_vector, bsp_apic)?;

        Ok(())
    }

    fn enable_irq(&self, irq: Irq) -> Result<(), Error> {
        let mut inner = self.inner.lock();
        let gsi = self.translate_irq(irq);
        inner.set_gsi_enabled(gsi, true);
        Ok(())
    }

    fn handle_specific_irq(&self, gsi: usize) {
        let table = self.table.read();
        let vector = IrqVector::Irq(Irq::External(gsi as u32));

        if let Some(handler) = table.handler(gsi) {
            handler.clone().handle_irq(vector);
        } else {
            log::warn!("No handler set for GSI #{}", gsi);
        }
    }
}

impl IoApic {
    /// Creates an I/O APIC instance from its ACPI definition
    pub fn from_acpi(info: &AcpiApic<AcpiAllocator>) -> Result<Arc<Self>, Error> {
        if info.io_apics.len() != 1 {
            log::warn!(
                "The system has multiple I/O APICs, but the kernel only knows how to use one"
            );
        }
        let ioapic = info.io_apics.first().unwrap();

        log::info!("I/O APIC at {:#x}", ioapic.address);

        let mut isa_redirections = [None; 16];

        for redir in info.interrupt_source_overrides.iter() {
            let index = redir.isa_source as usize;
            let trigger = match redir.trigger_mode {
                // ISA IRQs are edge-triggered by default
                TriggerMode::SameAsBus => IrqTrigger::Edge,
                TriggerMode::Edge => IrqTrigger::Edge,
                TriggerMode::Level => IrqTrigger::Level,
            };
            let level = match redir.polarity {
                // Level-triggered ISA IRQs are ActiveLow by default
                Polarity::SameAsBus => {
                    if trigger == IrqTrigger::Level {
                        IrqLevel::ActiveLow
                    } else {
                        IrqLevel::ActiveHigh
                    }
                }
                Polarity::ActiveLow => IrqLevel::ActiveLow,
                Polarity::ActiveHigh => IrqLevel::ActiveHigh,
            };

            log::debug!(
                "ISA IRQ #{} -> GSI #{}",
                index,
                redir.global_system_interrupt
            );
            isa_redirections[index].replace(IsaRedirection {
                gsi_index: redir.global_system_interrupt,
                level,
                trigger,
            });
        }

        let regs = unsafe {
            DeviceMemoryIo::<'_, Regs>::map(
                PhysicalAddress::from_u64(ioapic.address as u64),
                Default::default(),
            )?
        };

        let max_gsi = (regs.read(REG_IOAPIC_VERSION) >> 16) & 0xFF;

        log::info!("Maximum GSI number: {}", max_gsi);

        let mut inner = Inner { regs, max_gsi };

        // Mask all GSIs
        for gsi in 0..max_gsi {
            inner.set_gsi_enabled(gsi, false);
        }

        Ok(Arc::new(Self {
            isa_redirections,
            inner: IrqSafeSpinlock::new(inner),
            table: IrqSafeRwLock::new(FixedInterruptTable::new()),
        }))
    }

    fn translate_irq(&self, irq: Irq) -> u32 {
        let redir = &self.isa_redirections;
        match irq {
            Irq::External(irq) if irq >= ISA_IRQ_OFFSET => {
                let isa = irq - ISA_IRQ_OFFSET;
                redir[isa as usize].map(|t| t.gsi_index).unwrap_or(isa)
            }
            Irq::External(irq) => irq,
            Irq::Private(_) => unreachable!(),
        }
    }
}
