use crate::descriptor::{Address, DescriptorExt, Port};
use defmt::{Format, debug};
use embedded_hal::digital::PinState;
use embedded_hal_async::i2c::I2c;

#[derive(Format, Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum PinMode {
    Gpio,
    Led,
}

#[derive(Format, Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum GpioDirection {
    Input,
    Output,
}

#[derive(Format, Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum Register {
    INPUT_P0 = 0x00,
    INPUT_P1 = 0x01,
    OUTPUT_P0 = 0x02,
    OUTPUT_P1 = 0x03,
    CONFIG_P0 = 0x04,
    CONFIG_P1 = 0x05,
    INT_P0 = 0x06,
    INT_P1 = 0x07,
    ID = 0x10,
    CTL = 0x11,
    LEDMS_P0 = 0x12,
    LEDMS_P1 = 0x13,
    DIM0_P10 = 0x20,
    DIM1_P11 = 0x21,
    DIM2_P12 = 0x22,
    DIM3_P13 = 0x23,
    DIM4_P00 = 0x24,
    DIM5_P01 = 0x25,
    DIM6_P02 = 0x26,
    DIM7_P03 = 0x27,
    DIM8_P04 = 0x28,
    DIM9_P05 = 0x29,
    DIM10_P06 = 0x2A,
    DIM11_P07 = 0x2B,
    DIM12_P14 = 0x2C,
    DIM13_P15 = 0x2D,
    DIM14_P16 = 0x2E,
    DIM15_P17 = 0x2F,
    SW_RSTN = 0x7F,
}

pub(crate) async fn write_register<I2C, E>(
    bus: &mut I2C,
    addr: u8,
    register: Register,
    value: u8,
) -> Result<(), E>
where
    I2C: I2c<Error = E>,
{
    bus.write(addr, &[register as u8, value]).await
}

pub(crate) async fn read_register<I2C, E>(
    bus: &mut I2C,
    addr: u8,
    register: Register,
) -> Result<u8, E>
where
    I2C: I2c<Error = E>,
{
    let mut val = [0u8; 1];
    bus.write_read(addr, &[register as u8], &mut val)
        .await
        .and(Ok(val[0]))
}

pub(crate) async fn set_pin_mode<I2C, E, PIN: DescriptorExt>(
    bus: &mut I2C,
    pin: &PIN,
    mode: PinMode,
) -> Result<(), E>
where
    I2C: I2c<Error = E>,
{
    let register = match pin.port() {
        Port::Port0 => Register::LEDMS_P0,
        Port::Port1 => Register::LEDMS_P1,
    };

    let value = read_register(bus, pin.address().address(), register).await?;

    let value = match mode {
        PinMode::Gpio => value | pin.pin().bit(),
        PinMode::Led => value & !pin.pin().bit(),
    };

    write_register(bus, pin.address().address(), register, value).await?;

    debug!(
        "Set mode of pin 0x{:x}/{}/{} to {}",
        pin.address(),
        pin.port(),
        pin.pin(),
        mode
    );
    Ok(())
}

pub(crate) async fn set_io_direction<I2C, E, PIN: DescriptorExt + ?Sized>(
    bus: &mut I2C,
    pin: &PIN,
    direction: GpioDirection,
) -> Result<(), E>
where
    I2C: I2c<Error = E>,
{
    let register = match pin.port() {
        Port::Port0 => Register::CONFIG_P0,
        Port::Port1 => Register::CONFIG_P1,
    };

    let value = read_register(bus, pin.address().address(), register).await?;

    let value = match direction {
        GpioDirection::Input => value | pin.pin().bit(),
        GpioDirection::Output => value & !pin.pin().bit(),
    };

    write_register(bus, pin.address().address(), register, value).await?;

    debug!(
        "Set IO direction of pin 0x{:x}/{}/{} to {}",
        pin.address(),
        pin.port(),
        pin.pin(),
        direction
    );
    Ok(())
}

pub(crate) async fn set_io_state<I2C, E, PIN: DescriptorExt + ?Sized>(
    bus: &mut I2C,
    pin: &PIN,
    state: PinState,
) -> Result<(), E>
where
    I2C: I2c<Error = E>,
{
    let register = match pin.port() {
        Port::Port0 => Register::OUTPUT_P0,
        Port::Port1 => Register::OUTPUT_P1,
    };

    let value = read_register(bus, pin.address().address(), register).await?;

    let value = match state {
        PinState::Low => value & !pin.pin().bit(),
        PinState::High => value | pin.pin().bit(),
    };

    write_register(bus, pin.address().address(), register, value).await?;

    debug!(
        "Set pin 0x{:x}/{}/{} to {}",
        pin.address(),
        pin.port(),
        pin.pin(),
        state
    );
    Ok(())
}

pub(crate) async fn read_input_registers<I2C, E>(
    bus: &mut I2C,
    addr: Address,
) -> Result<(u8, u8), E>
where
    I2C: I2c<Error = E>,
{
    let mut val = [0u8; 2];

    bus.write_read(
        addr.address(),
        &[Register::INPUT_P0 as u8, Register::INPUT_P1 as u8],
        &mut val,
    )
    .await?;

    Ok((val[0], val[1]))
}
