use field_offset::offset_of;
use stm32l4xx_hal::prelude::_embedded_hal_blocking_i2c_Write;
use tock_registers::{
    interfaces::{ReadWriteable, Readable},
    register_bitfields, register_structs,
    registers::InMemoryRegister,
};

register_bitfields![
    u8,
    // Connection status register
    ConnStatus1 [
        // Indicates that active cable has been plugged in
        ACTIVE_CABLE_DETECTION OFFSET(0) NUMBITS(1) [],
        // Read by the application to determine if accessory was attached
        ACCESSORY_CONNECTED OFFSET(1) NUMBITS(3) [
            None = 0,
            Audio = 4,
            AudioCharged = 5,
            DebugDfp = 6,
            DebugUfp = 7,
        ],
        // Set in UFP mode with the detected current mode
        CURRENT_MODE_DETECT OFFSET(4) NUMBITS(2) [
            Default = 0,
            Medium = 1,
            ChargeThrough = 2,
            High = 3,
        ],
        // Programmed by application to change current advertisement
        CURRENT_MODE_ADVERTISE OFFSET(6) NUMBITS(2) [
            Default = 0,
            Mid = 1,
            High = 2,
            Reserved = 3,
        ],
    ],
    // Connection status and control register
    ConnStatus2 [
        // Setting this field will disable UFP accessory support
        DISABLE_UFP_ACCESSORY OFFSET(0) NUMBITS(1) [],
        // Percentage of time that a DRP advertised DFP during tDRP
        DRP_DUTY_CYCLE OFFSET(1) NUMBITS(2) [
            p30 = 0,
            p40 = 1,
            p50 = 2,
            p60 = 3,
        ],
        // Set when over-current limit is triggered
        VCONN_FAULT OFFSET(3) NUMBITS(1) [],
        // Pulled low when a control or status register changes
        INTERRUPT_STATUS OFFSET(4) NUMBITS(1) [
            Clear = 0,
            Changed = 1,
        ],
        // Cable orientation
        CABLE_DIR OFFSET(5) NUMBITS(1) [
            CC1 = 0,
            CC2 = 1,
        ],
        // Attached state (same as ID pin)
        ATTACHED_STATE OFFSET(6) NUMBITS(2) [
            NotAttached = 0,
            AttachedSrc_DFP = 1,
            AttachedSnk_UFP = 2,
            AttachedAccessory = 3,
        ],
    ],
    // General control register
    Control [
        // Disable CC pin termination
        DISABLE_TERM OFFSET(0) NUMBITS(1) [],
        // Control behavior when configured as DRP
        SOURCE_PERF OFFSET(1) NUMBITS(2) [
            StandardDrp = 0,
            DrpTrySnk = 1,
            Reserved = 2,
            DrpTrySrc = 3,
        ],
        // Soft reset internal logic (self-clearing)
        I2C_SOFT_RESET OFFSET(3) NUMBITS(1) [],
        // Configure device operation mode
        MODE_SELECT OFFSET(4) NUMBITS(2) [
            Drp = 0,
            Ufp = 1,
            Dfp = 2,
        ],
        // Debounce time on CC pins
        DEBOUNCE OFFSET(6) NUMBITS(2) [
            ms168 = 0,
            ms118 = 1,
            ms134 = 2,
            ms152 = 3,
        ],
    ],
];

register_structs! {
    Tusb322Registers {
        (0x00 => id:    [u8; 8]),
        (0x08 => stat1: InMemoryRegister<u8, ConnStatus1::Register>),
        (0x09 => stat2: InMemoryRegister<u8, ConnStatus2::Register>),
        (0x0A => ctrl:  InMemoryRegister<u8, Control::Register>),
        (0x0B => @END),
    }
}

pub fn init(address: u8, i2c: &mut impl _embedded_hal_blocking_i2c_Write) {

    let stat1: InMemoryRegister<u8, ConnStatus1::Register> = InMemoryRegister::new(0);
    let stat2: InMemoryRegister<u8, ConnStatus2::Register> = InMemoryRegister::new(0);
    let ctrl: InMemoryRegister<u8, Control::Register> = InMemoryRegister::new(0);

    // Disable UFP accessory support (otherwise IC stays in DRP mode)
    stat2.modify(ConnStatus2::DISABLE_UFP_ACCESSORY::SET);

    // Disable CC termination to change to UFP mode
    ctrl.modify(Control::DISABLE_TERM::SET + Control::MODE_SELECT::Ufp);

    let buffer: [u8; 4] = [
        offset_of!(Tusb322Registers => stat1).get_byte_offset() as u8,
        stat1.get(),
        stat2.get(),
        ctrl.get(),
    ];

    match i2c.write(address, &buffer) {
        Ok(_) => (),
        Err(_) => panic!(),
    }

    // Re-enable CC termination
    ctrl.modify(Control::DISABLE_TERM::CLEAR);

    let buffer: [u8; 4] = [
        offset_of!(Tusb322Registers => stat1).get_byte_offset() as u8,
        stat1.get(),
        stat2.get(),
        ctrl.get(),
    ];

    match i2c.write(address, &buffer) {
        Ok(_) => (),
        Err(_) => panic!(),
    }
}
