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

use crate::irq;

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().unwrap();
        #[cfg(feature = "hv")]
        cpu.set_eoi_mode(true);
    });
}

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

    let irq_num = ack.to_u32() as usize;

    // let cpu_id = current_cpu();
    // info!("[{cpu_id}] IRQ {}", irq_num);
    if !IRQ_HANDLER_TABLE.handle(irq_num) {
        warn!("Unhandled IRQ {ack:?}");
    }

    TRAP.eoi1(ack);
    if TRAP.eoi_mode() {
        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 c = c.lock();
            c.set_irq_enable(id, enabled);

            if let Some(t) = trigger {
                c.set_cfg(id, t);
            }
        });
    } else {
        use_gicd(|gic| {
            gic.set_irq_enable(id, enabled);
            gic.set_target_cpu(id, Some(Affinity::current()));
            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) {
    arm_gic_driver::v3::send_sgi(
        IntId::sgi(id as _),
        match target {
            axplat::irq::IpiTarget::Current { cpu_id: _ } => {
                SGITarget::List(TargetList::new([Affinity::current()]))
            }
            axplat::irq::IpiTarget::Other { cpu_id } => {
                #[cfg(feature = "smp")]
                {
                    let hw_id = crate::smp::cpu_idx_to_id(cpu_id);
                    SGITarget::List(TargetList::new([Affinity::from_mpidr(hw_id as _)]))
                }
                #[cfg(not(feature = "smp"))]
                {
                    return;
                }
            }
            axplat::irq::IpiTarget::AllExceptCurrent { cpu_id, cpu_num } => {
                #[cfg(feature = "smp")]
                {
                    let mut list = alloc::vec::Vec::new();
                    for i in 0..cpu_num {
                        if i != cpu_id {
                            let hw_id = crate::smp::cpu_idx_to_id(i);
                            list.push(Affinity::from_mpidr(hw_id as _));
                        }
                    }
                    SGITarget::List(TargetList::new(&list))
                }
                #[cfg(not(feature = "smp"))]
                {
                    return;
                }
            }
        },
    );
}
