use libk::{dma::BusAddress, error::Error};
use tock_registers::{
    fields::FieldValue,
    interfaces::{Readable, Writeable},
    register_bitfields, register_structs,
    registers::{ReadOnly, ReadWrite},
};

register_bitfields! {
    u32,
    pub USBCMD [
        RS                  OFFSET(0)   NUMBITS(1)  [],
        HCRST               OFFSET(1)   NUMBITS(1)  [],
        INTE                OFFSET(2)   NUMBITS(1)  [],
        HSEE                OFFSET(3)   NUMBITS(1)  [],
        LHCRST              OFFSET(7)   NUMBITS(1)  [],
        CSS                 OFFSET(8)   NUMBITS(1)  [],
        CRS                 OFFSET(9)   NUMBITS(1)  [],
        EWE                 OFFSET(10)  NUMBITS(1)  [],
        EU3S                OFFSET(11)  NUMBITS(1)  [],
        CME                 OFFSET(13)  NUMBITS(1)  [],
        ETE                 OFFSET(14)  NUMBITS(1)  [],
        TSC_EN              OFFSET(15)  NUMBITS(1)  [],
        VTIOE               OFFSET(16)  NUMBITS(1)  [],
    ],
    pub USBSTS [
        HCH                 OFFSET(0)   NUMBITS(1)  [],
        HSE                 OFFSET(2)   NUMBITS(1)  [],
        EINT                OFFSET(3)   NUMBITS(1)  [],
        PCD                 OFFSET(4)   NUMBITS(1)  [],
        SSS                 OFFSET(8)   NUMBITS(1)  [],
        RSS                 OFFSET(9)   NUMBITS(1)  [],
        SRE                 OFFSET(10)  NUMBITS(1)  [],
        CNR                 OFFSET(11)  NUMBITS(1)  [],
        HCE                 OFFSET(12)  NUMBITS(1)  [],
    ],
    pub CONFIG [
        MaxSlotsEn          OFFSET(0)   NUMBITS(8)  [],
        U3E                 OFFSET(8)   NUMBITS(1)  [],
        CIE                 OFFSET(9)   NUMBITS(1)  [],
    ],

    // Port registers
    pub PORTSC [
        CCS                 OFFSET(0)   NUMBITS(1)  [],
        PED                 OFFSET(1)   NUMBITS(1)  [],
        OCA                 OFFSET(3)   NUMBITS(1)  [],
        PR                  OFFSET(4)   NUMBITS(1)  [],
        PLS                 OFFSET(5)   NUMBITS(4)  [],
        PP                  OFFSET(9)   NUMBITS(1)  [],
        PS                  OFFSET(10)  NUMBITS(4)  [],
        PIC                 OFFSET(14)  NUMBITS(2)  [],
        LWS                 OFFSET(16)  NUMBITS(1)  [],
        CSC                 OFFSET(17)  NUMBITS(1)  [],
        PEC                 OFFSET(18)  NUMBITS(1)  [],
        WRC                 OFFSET(19)  NUMBITS(1)  [],
        OCC                 OFFSET(20)  NUMBITS(1)  [],
        PRC                 OFFSET(21)  NUMBITS(1)  [],
        PLC                 OFFSET(22)  NUMBITS(1)  [],
        CEC                 OFFSET(23)  NUMBITS(1)  [],
    ],
    pub PORTLI [
        LEC                 OFFSET(0)   NUMBITS(16) [],
        RLC                 OFFSET(16)  NUMBITS(4)  [],
        TLC                 OFFSET(20)  NUMBITS(4)  [],
    ],
}

register_structs! {
    #[allow(non_snake_case)]
    pub OperationalRegs {
        (0x000 => pub USBCMD: ReadWrite<u32, USBCMD::Register>),
        (0x004 => pub USBSTS: ReadWrite<u32, USBSTS::Register>),
        (0x008 => pub PAGESIZE: ReadOnly<u32>),
        (0x00C => _0),
        (0x014 => pub DNCTRL: ReadWrite<u32>),
        (0x018 => pub CRCR: ReadWrite<u64>),
        // (0x018 => pub CRCR_LO: ReadWrite<u32>),
        // (0x01C => pub CRCR_HI: ReadWrite<u32>),
        (0x020 => _1),
        (0x030 => pub DCBAAP: ReadWrite<u64>),
        // (0x030 => pub DCBAAP_LO: ReadWrite<u32>),
        // (0x034 => pub DCBAAP_HI: ReadWrite<u32>),
        (0x038 => pub CONFIG: ReadWrite<u32, CONFIG::Register>),
        (0x03C => _2),
        (0x400 => @END),
    }
}

register_structs! {
    #[allow(non_snake_case)]
    pub PortRegs {
        (0x00 => pub PORTSC: ReadWrite<u32, PORTSC::Register>),
        (0x04 => pub PORTPMSC: ReadWrite<u32>),
        (0x08 => pub PORTLI: ReadWrite<u32, PORTLI::Register>),
        (0x0C => pub PORTHLPMC: ReadWrite<u32>),
        (0x10 => @END),
    }
}

impl OperationalRegs {
    pub fn wait_usbsts_bit(
        &self,
        bit: FieldValue<u32, USBSTS::Register>,
        mut timeout: u64,
    ) -> Result<(), Error> {
        while timeout > 0 && !self.USBSTS.matches_all(bit) {
            core::hint::spin_loop();
            timeout -= 1;
        }
        if timeout > 0 {
            Ok(())
        } else {
            Err(Error::TimedOut)
        }
    }

    pub fn set_dcbaap(&self, value: BusAddress) {
        let value = value.into_u64();
        self.DCBAAP.set(value);
    }

    pub fn set_crcr(&self, value: BusAddress, dcs: bool) {
        let mut value = value.into_u64();
        if dcs {
            value |= 1;
        }
        self.CRCR.set(value);
    }
}

impl PortRegs {
    pub fn modify_portsc_preserving(&self, value: FieldValue<u32, PORTSC::Register>) {
        let neutral = super::portsc_to_neutral(self.PORTSC.extract());
        self.PORTSC.set(neutral.get() | value.value);
    }
}

unsafe impl Sync for PortRegs {}
