use libk::dma::BusAddress;
use tock_registers::{
    interfaces::{Readable, Writeable},
    register_bitfields, register_structs,
    registers::ReadWrite,
};

use crate::ring::{EventRing, EventRingSegmentTable, GenericRing};

register_bitfields! {
    u32,
    pub IMAN [
        IP      OFFSET(0)   NUMBITS(1)  [],
        IE      OFFSET(1)   NUMBITS(1)  [],
    ],
    pub IMOD [
        IMODI   OFFSET(0)   NUMBITS(16) [],
        IMODC   OFFSET(16)  NUMBITS(16) [],
    ],
}

register_structs! {
    #[allow(non_snake_case)]
    pub InterrupterRegs {
        (0x00 => pub IMAN: ReadWrite<u32, IMAN::Register>),
        (0x04 => pub IMOD: ReadWrite<u32, IMOD::Register>),
        (0x08 => pub ERSTSZ: ReadWrite<u32>),
        (0x0C => _0),
        (0x10 => pub ERSTBA: ReadWrite<u64>),
        (0x18 => pub ERDP: ReadWrite<u64>),
        (0x20 => @END),
    }
}

register_structs! {
    #[allow(non_snake_case)]
    pub RuntimeRegs {
        (0x0000 => pub MFINDEX: ReadWrite<u32>),
        (0x0004 => _0),
        // NOTE there're 1024 of them, but I'm not going to use all of those anyway, so use 1023
        // for a nice round number
        (0x0020 => pub IRn: [InterrupterRegs; 1023]),
        (0x8000 => @END),
    }
}

impl RuntimeRegs {
    pub fn configure_interrupter(
        &self,
        index: usize,
        event_ring: &EventRing,
        erst: &EventRingSegmentTable,
    ) {
        let interrupter = &self.IRn[index];

        let erdp = event_ring.bus_base().into_u64();
        let erstba = erst.bus_address().into_u64();

        interrupter.ERSTSZ.set(erst.capacity() as u32);
        interrupter.ERSTBA.set(erstba);
        interrupter.ERDP.set(erdp);
        interrupter.IMAN.write(IMAN::IE::SET + IMAN::IP::SET);
    }

    pub fn set_interrupter_dequeue_pointer(&self, index: usize, erdp: BusAddress) {
        let _ = self.IRn[index].ERDP.get();
        self.IRn[index].ERDP.set(erdp.into_u64() | (1 << 3));
    }
}
