use alloc::{format, string::String};
pub use arm_gic_driver::v2::Gic;
use arm_gic_driver::v2::*;
use lazyinit::LazyInit;
use log::*;
use spin::Mutex;

use crate::irq::{self, current_cpu};

use super::IRQ_HANDLER_TABLE;

#[percpu::def_percpu]
pub static CPU_IF: LazyInit<Mutex<CpuInterface>> = LazyInit::new();

pub static TRAP: LazyInit<TrapOp> = LazyInit::new();

fn use_gicd(f: impl FnOnce(&mut Gic)) {
    let mut gic = irq::get_gicd().lock().unwrap();
    f(gic.typed_mut::<Gic>().expect("GICD is not initialized"));
}

pub fn init_current_cpu() {
    CPU_IF.with_current(|c| {
        let mut cpu = c.lock();
        cpu.init_current_cpu();
        #[cfg(feature = "hv")]
        cpu.set_eoi_mode_ns(true);
    })
}

pub fn handle(_unused: usize) -> Option<usize> {
    let ack = TRAP.ack();
    if ack.is_special() {
        return None;
    }

    let irq_num = match ack {
        Ack::Other(intid) => intid,
        Ack::SGI { intid, cpu_id: _ } => intid,
    }
    .to_u32() as usize;

    if !IRQ_HANDLER_TABLE.handle(irq_num) {
        warn!("Unhandled IRQ {ack:?}");
    }

    TRAP.eoi(ack);
    if TRAP.eoi_mode_ns() {
        TRAP.dir(ack);
    }

    Some(irq_num)
}

pub(crate) fn set_enable(irq_raw: usize, trigger: Option<Trigger>, enabled: bool) {
    debug!(
        "IRQ({:#x}) set enable: {}, {}",
        irq_raw,
        enabled,
        match trigger {
            Some(t) => format!("trigger: {t:?}"),
            None => String::new(),
        }
    );
    let id = unsafe { IntId::raw(irq_raw as _) };
    if id.is_private() {
        CPU_IF.with_current(|c| {
            let cpu = c.lock();
            cpu.set_irq_enable(id, enabled);

            if let Some(t) = trigger {
                cpu.set_cfg(id, t);
            }
        });
    } else {
        use_gicd(|gic| {
            gic.set_irq_enable(id, enabled);
            gic.set_target_cpu(id, TargetList::new([current_cpu()].into_iter()));
            if let Some(t) = trigger {
                gic.set_cfg(id, t);
            }
        });
    }
    debug!("IRQ({irq_raw:#x}) set enable done");
}

pub fn send_ipi(id: usize, target: axplat::irq::IpiTarget) {
    use_gicd(|gic| {
        gic.send_sgi(
            IntId::sgi(id as _),
            match target {
                axplat::irq::IpiTarget::Current { cpu_id: _ } => SGITarget::Current,
                axplat::irq::IpiTarget::Other { cpu_id } => {
                    SGITarget::TargetList(TargetList::new([cpu_id].into_iter()))
                }
                axplat::irq::IpiTarget::AllExceptCurrent {
                    cpu_id: _,
                    cpu_num: _,
                } => SGITarget::AllOther,
            },
        );
    });
}
