//! ARM GICv2 Distributor registers
use device_api::interrupt::{IpiDeliveryTarget, IrqOptions, IrqTrigger};
use libk_mm::device::DeviceMemoryIo;
use libk_util::sync::Spinlock;
use tock_registers::{
    interfaces::{ReadWriteable, Readable, Writeable},
    register_bitfields, register_structs,
    registers::{ReadOnly, ReadWrite, WriteOnly},
};

register_bitfields! {
    u32,
    CTLR [
        EnableGrp0      OFFSET(0)   NUMBITS(1) [],
        EnableGrp1NS    OFFSET(1)   NUMBITS(1) [],
        ARE_NS          OFFSET(5)   NUMBITS(1) [],
    ],
    TYPER [
        ITLinesNumber OFFSET(0) NUMBITS(5) []
    ],
    ITARGETSR [
        Offset3 OFFSET(24) NUMBITS(8) [],
        Offset2 OFFSET(16) NUMBITS(8) [],
        Offset1 OFFSET(8) NUMBITS(8) [],
        Offset0 OFFSET(0) NUMBITS(8) []
    ],
    SGIR [
        TargetListFilter OFFSET(24) NUMBITS(2) [
            SpecifiedOnly = 0,
            AllExceptLocal = 1,
            LocalOnly = 2,
        ],
        CPUTargetList OFFSET(16) NUMBITS(8) [],
        INTID OFFSET(0) NUMBITS(4) []
    ],
}

register_structs! {
    #[allow(non_snake_case)]
    pub(super) GicdSharedRegs {
        (0x000 => CTLR: ReadWrite<u32, CTLR::Register>),
        (0x004 => TYPER: ReadWrite<u32, TYPER::Register>),
        (0x008 => _0),
        (0x040 => SETSPI_NSR: WriteOnly<u32>),
        (0x044 => _7),
        (0x084 => IGROUPRn: [ReadWrite<u32>; 31]),
        (0x100 => _5),
        (0x104 => ISENABLER: [ReadWrite<u32>; 31]),
        (0x180 => _1),
        (0x420 => IPRIORITYRn: [ReadWrite<u32>; 248]),
        (0x800 => _6),
        (0x820 => ITARGETSR: [ReadWrite<u32, ITARGETSR::Register>; 248]),
        (0xC00 => _2),
        (0xC08 => ICFGR: [ReadWrite<u32>; 62]),
        (0xD00 => _3),
        (0xF00 => SGIR: WriteOnly<u32, SGIR::Register>),
        (0xF04 => @END),
    }
}

register_structs! {
    #[allow(non_snake_case)]
    pub(super) GicdBankedRegs {
        (0x000 => _0),
        (0x100 => ISENABLER: ReadWrite<u32>),
        (0x104 => _1),
        (0x800 => ITARGETSR: [ReadOnly<u32, ITARGETSR::Register>; 8]),
        (0x820 => _2),
        (0xC00 => ICFGR: [ReadWrite<u32>; 2]),
        (0xC08 => @END),
    }
}

pub(super) struct Gicd {
    shared_regs: Spinlock<DeviceMemoryIo<'static, GicdSharedRegs>>,
    banked_regs: DeviceMemoryIo<'static, GicdBankedRegs>,
}

impl GicdSharedRegs {
    #[inline(always)]
    fn num_irqs(&self) -> usize {
        ((self.TYPER.read(TYPER::ITLinesNumber) as usize) + 1) * 32
    }

    #[inline(always)]
    fn itargets_slice(&self) -> &[ReadWrite<u32, ITARGETSR::Register>] {
        &self.ITARGETSR[..]
    }
}

impl Gicd {
    pub const fn new(
        shared_regs: DeviceMemoryIo<'static, GicdSharedRegs>,
        banked_regs: DeviceMemoryIo<'static, GicdBankedRegs>,
    ) -> Self {
        let shared_regs = Spinlock::new(shared_regs);
        Self {
            shared_regs,
            banked_regs,
        }
    }

    #[allow(dead_code)]
    pub unsafe fn set_sgir(&self, target: IpiDeliveryTarget, interrupt_id: u64) {
        assert_eq!(interrupt_id & !0xF, 0);
        let value = match target {
            IpiDeliveryTarget::OtherCpus => SGIR::TargetListFilter::AllExceptLocal,
            IpiDeliveryTarget::Specific(mask) => {
                assert_eq!(mask & !0xFFFF, 0);
                SGIR::TargetListFilter::SpecifiedOnly + SGIR::CPUTargetList.val(mask as _)
            }
            IpiDeliveryTarget::ThisCpu => SGIR::TargetListFilter::LocalOnly,
        } + SGIR::INTID.val(interrupt_id as u32);

        self.shared_regs.lock().SGIR.write(value);
    }

    fn local_gic_target_mask(&self) -> u32 {
        self.banked_regs.ITARGETSR[0].read(ITARGETSR::Offset0)
    }

    pub fn configure_irq(&self, irq: usize, options: IrqOptions) {
        // 2 bits per IRQ, 16 entries per register
        let reg = irq / 16;
        let shift = (irq % 16) * 2;
        let cfgr_value = match options.trigger {
            IrqTrigger::Edge | IrqTrigger::Default => 0b10,
            IrqTrigger::Level => 0b00,
        };

        match reg {
            // Private IRQs
            0..=1 => {
                unimplemented!()
            }
            // Shared IRQs
            _ => {
                let regs = self.shared_regs.lock();
                let reg = &regs.ICFGR[reg - 2];

                let v = reg.get() & !(0x3 << shift);
                reg.set(v | (cfgr_value << shift));
            }
        }
    }

    pub fn enable_irq(&self, irq: usize) {
        let reg = irq >> 5;
        let bit = 1u32 << (irq & 0x1F);

        match reg {
            // Private IRQs
            0 => {
                let reg = &self.banked_regs.ISENABLER;

                reg.set(reg.get() | bit);
            }
            // Shared IRQs
            _ => {
                let regs = self.shared_regs.lock();
                let reg = &regs.ISENABLER[reg - 1];

                reg.set(reg.get() | bit);
            }
        }
    }

    pub unsafe fn init(&self) {
        let mask = self.local_gic_target_mask();
        let regs = self.shared_regs.lock();

        log::debug!("Enabling GICv2 GICD, max IRQ number: {}", regs.num_irqs());

        regs.CTLR.modify(CTLR::EnableGrp0::SET);

        for reg in regs.itargets_slice().iter() {
            // Redirect all IRQs to cpu0 (this CPU)
            reg.write(
                ITARGETSR::Offset0.val(mask)
                    + ITARGETSR::Offset1.val(mask)
                    + ITARGETSR::Offset2.val(mask)
                    + ITARGETSR::Offset3.val(mask),
            );
        }
    }
}
