use rdif_intc::Intc;
use rdrive::Device;

mod v2;
mod v3;

use core::sync::atomic::{AtomicU8, Ordering};

#[derive(Clone, Copy, Eq, PartialEq)]
enum GicBackend {
    None = 0,
    V2   = 2,
    V3   = 3,
}

static GIC_BACKEND: AtomicU8 = AtomicU8::new(GicBackend::None as u8);

fn set_backend(backend: GicBackend) {
    GIC_BACKEND.store(backend as u8, Ordering::Release);
}

fn backend() -> GicBackend {
    match GIC_BACKEND.load(Ordering::Acquire) {
        2 => GicBackend::V2,
        3 => GicBackend::V3,
        _ => GicBackend::None,
    }
}

pub fn init_current_cpu() {
    let cpu_idx = crate::cpu::current_cpu_idx()
        .unwrap_or_else(|| panic!("current logical CPU index is not available for GIC init"));
    init_cpu(cpu_idx);
}

pub fn init_cpu(cpu_idx: usize) {
    match backend() {
        GicBackend::V2 => v2::init_cpu(),
        GicBackend::V3 => v3::init_cpu(cpu_idx),
        GicBackend::None => {
            if v3::is_support_icc() {
                v3::init_cpu(cpu_idx);
            } else {
                v2::init_cpu();
            }
        }
    }
}

fn get_gicd() -> Device<Intc> {
    rdrive::get_one().expect("no interrupt controller found")
}

pub fn irq_set_enable(irq: rdrive::IrqId, enable: bool) {
    let raw = irq.into();
    match backend() {
        GicBackend::V2 => v2::irq_set_enable(raw, enable),
        GicBackend::V3 => v3::irq_set_enable(raw, enable),
        GicBackend::None => {
            v2::irq_set_enable(raw, enable);
            v3::irq_set_enable(raw, enable);
        }
    }
}

pub fn irq_set_affinity(
    irq: rdrive::IrqId,
    affinity: crate::irq::IrqAffinity,
) -> Result<(), &'static str> {
    let raw = irq.into();
    match backend() {
        GicBackend::V2 => v2::irq_set_affinity(raw, affinity),
        GicBackend::V3 => v3::irq_set_affinity(raw, affinity),
        GicBackend::None => {
            if v3::is_support_icc() {
                v3::irq_set_affinity(raw, affinity)
            } else {
                v2::irq_set_affinity(raw, affinity)
            }
        }
    }
}

pub fn send_ipi(irq: rdrive::IrqId, target: crate::irq::IpiTarget) {
    let raw = irq.into();
    match backend() {
        GicBackend::V2 => v2::send_ipi(raw, target),
        GicBackend::V3 => v3::send_ipi(raw, target),
        GicBackend::None => {
            if v3::is_support_icc() {
                v3::send_ipi(raw, target);
            } else {
                v2::send_ipi(raw, target);
            }
        }
    }
}

fn hardware_cpu_id(cpu_idx: usize) -> usize {
    someboot::smp::cpu_idx_to_id(cpu_idx).unwrap_or(cpu_idx)
}

pub enum ActiveIrq {
    V2(v2::ActiveIrq),
    V3(v3::ActiveIrq),
}

impl ActiveIrq {
    pub fn id(&self) -> rdrive::IrqId {
        match self {
            Self::V2(active) => active.id(),
            Self::V3(active) => active.id(),
        }
    }
}

pub fn begin_irq() -> Option<ActiveIrq> {
    match backend() {
        GicBackend::V2 => v2::begin_irq().map(ActiveIrq::V2),
        GicBackend::V3 => v3::begin_irq().map(ActiveIrq::V3),
        GicBackend::None => {
            if v3::is_support_icc() {
                v3::begin_irq().map(ActiveIrq::V3)
            } else {
                v2::begin_irq().map(ActiveIrq::V2)
            }
        }
    }
}
