use core::{
    cell::RefCell,
    future::Future,
    ops::{Deref, RangeInclusive},
    task::Poll,
};

use fugit::{HertzU32, RateExtU32};
use futures::FutureExt;
use heapless::Vec;

use rp2040_hal::{
    self as hal,
    clocks::init_clocks_and_plls,
    gpio::{FunctionI2C, Pin, PullUp},
    i2c::{Error, ValidAddress},
    pac,
    watchdog::Watchdog,
    Clock,
};

use super::{Controller, FIFOBuffer, Generator, Target, TargetState};

pub struct State {
    controller: Option<Controller>,
    target: Option<Target>,
    resets: hal::pac::RESETS,
    ref_clock_freq: HertzU32,
    payload: RefCell<TargetState>,
}

pub fn run_test(f: impl Future) {
    super::test_executor::execute(f);
}
async fn wait_with(payload: &RefCell<TargetState>, mut f: impl FnMut(&TargetState) -> bool) {
    while f(payload.borrow().deref()) {
        let mut done = false;
        core::future::poll_fn(|cx| {
            cx.waker().wake_by_ref();
            if !done {
                done = true;
                Poll::Pending
            } else {
                Poll::Ready(())
            }
        })
        .await;
    }
}

pub fn setup<T: ValidAddress>(xtal_freq_hz: u32, addr: T) -> State {
    unsafe {
        hal::sio::spinlock_reset();
    }
    let mut pac = pac::Peripherals::take().unwrap();
    let mut watchdog = Watchdog::new(pac.WATCHDOG);

    let clocks = init_clocks_and_plls(
        xtal_freq_hz,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // The single-cycle I/O block controls our GPIO pins
    let sio = hal::Sio::new(pac.SIO);
    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );

    // Configure two pins as being I²C, not GPIO
    let ctrl_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio0.reconfigure();
    let ctrl_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio1.reconfigure();

    let trg_sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio2.reconfigure();
    let trg_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio3.reconfigure();

    let i2c_ctrl = hal::I2C::new_controller(
        pac.I2C0,
        ctrl_sda_pin,
        ctrl_scl_pin,
        400.kHz(),
        &mut pac.RESETS,
        clocks.system_clock.freq(),
    );
    let i2c_target = hal::I2C::new_peripheral_event_iterator(
        pac.I2C1,
        trg_sda_pin,
        trg_scl_pin,
        &mut pac.RESETS,
        addr,
    );

    unsafe {
        pac::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ);
        pac::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ);
        pac::NVIC::unpend(hal::pac::Interrupt::I2C1_IRQ);
        pac::NVIC::unmask(hal::pac::Interrupt::I2C1_IRQ);
    }

    State {
        controller: Some(i2c_ctrl),
        target: Some(i2c_target),
        resets: pac.RESETS,
        ref_clock_freq: clocks.system_clock.freq(),
        payload: RefCell::new(TargetState::new()),
    }
}

pub fn reset<T: ValidAddress>(state: &mut State, addr: T) {
    // reset controller
    let (i2c, (sda, scl)) = state
        .controller
        .take()
        .expect("controller's missing.")
        .free(&mut state.resets);

    let (i2c_t, (sda_t, scl_t)) = state
        .target
        .take()
        .expect("target's missing")
        .free(&mut state.resets);

    state.payload.replace(Default::default());

    state.target = Some(hal::I2C::new_peripheral_event_iterator(
        i2c_t,
        sda_t,
        scl_t,
        &mut state.resets,
        addr,
    ));

    state.controller = Some(hal::I2C::new_controller(
        i2c,
        sda,
        scl,
        400.kHz(),
        &mut state.resets,
        state.ref_clock_freq,
    ));
}

pub async fn target_handler(payload: &RefCell<TargetState>, target: &mut Target) -> (u32, u32) {
    loop {
        let evt = target.wait_next().await;

        super::target_handler(target, evt, &mut *payload.borrow_mut(), false);
    }
}

async fn embedded_hal_case<A: ValidAddress>(
    controller: &mut Controller,
    addr: A,
    v: &mut ([u8; 25], [u8; 25], [u8; 25], [u8; 14], [u8; 14]),
    payload: &RefCell<TargetState>,
) -> Result<(), Error> {
    use embedded_hal_async::i2c::I2c;
    let sample1: FIFOBuffer = Generator::seq().take(25).collect();
    let sample2: FIFOBuffer = Generator::fib().take(14).collect();

    // we need to wait for stop to be registered between each operations otherwise we have no
    // way to know when the Target side has finished processing the last request.
    controller.write(addr, &sample1).await?;
    wait_with(payload, |p| p.stop_cnt != 1).await;

    controller.read(addr, &mut v.0).await?;
    wait_with(payload, |p| p.stop_cnt != 2).await;

    controller.read(addr, &mut v.1).await?;
    wait_with(payload, |p| p.stop_cnt != 3).await;

    controller.read(addr, &mut v.2).await?;
    wait_with(payload, |p| p.stop_cnt != 4).await;

    controller.write_read(addr, &sample2, &mut v.3).await?;
    wait_with(payload, |p| p.stop_cnt != 5).await;

    controller.write(addr, &sample2).await?;
    wait_with(payload, |p| p.stop_cnt != 6).await;

    controller.write(addr, &sample1).await?;
    wait_with(payload, |p| p.stop_cnt != 7).await;

    controller.write_read(addr, &sample1, &mut v.4).await?;
    wait_with(payload, |p| p.stop_cnt != 8).await;
    Ok::<(), Error>(())
}
pub async fn embedded_hal<T: ValidAddress>(
    state: &mut State,
    addr: T,
    restart_count: RangeInclusive<u32>,
) {
    // Throttling is important for this test as it also ensures that the Target implementation
    // does not "waste" bytes that would be discarded otherwise.
    //
    // One down side of this is that the Target implementation is unable to detect restarts
    // between consicutive write operations
    reset(state, addr);

    // Test
    let mut v = Default::default();
    let ctrl = embedded_hal_case(
        state.controller.as_mut().expect("controller's missing."),
        addr,
        &mut v,
        &state.payload,
    );
    let trgt = target_handler(
        &state.payload,
        state.target.as_mut().take().expect("target’s missing"),
    );
    futures::select_biased! {
        r = ctrl.fuse() => r.expect("Controller test success"),
        _ = trgt.fuse() => {}
    }

    // Validate

    // There are 14restarts in this sequence but because of latency in the target handling, it
    // may only detect 7.
    let actual_restart_count = state.payload.borrow().restart_cnt;
    assert!(
        restart_count.contains(&actual_restart_count),
        "restart count out of range {} ∉ {:?}",
        actual_restart_count,
        restart_count
    );

    // assert writes
    let sample1: FIFOBuffer = Generator::seq().take(25).collect();
    let sample2: FIFOBuffer = Generator::fib().take(14).collect();
    let e: FIFOBuffer = itertools::chain!(
        sample1.iter(),
        sample2.iter(),
        sample2.iter(),
        sample1.iter(),
        sample1.iter(),
    )
    .cloned()
    .collect();
    assert_eq!(state.payload.borrow().vec, e);
    // assert reads
    let g: FIFOBuffer = itertools::chain!(
        Generator::fib().take(25),
        Generator::fib().skip(25 + 7).take(25),
        Generator::fib().skip(2 * (25 + 7)).take(25),
        Generator::seq().take(14),
        Generator::fib().take(14)
    )
    .collect();
    let h: FIFOBuffer = itertools::chain!(
        v.0.into_iter(),
        v.1.into_iter(),
        v.2.into_iter(),
        v.3.into_iter(),
        v.4.into_iter()
    )
    .collect();
    assert_eq!(g, h);
}

pub async fn transaction<A: ValidAddress>(
    state: &mut State,
    addr: A,
    restart_count: RangeInclusive<u32>,
) {
    use embedded_hal::i2c::Operation;
    use embedded_hal_async::i2c::I2c;
    reset(state, addr);

    // Throttling is important for this test as it also ensures that the Target implementation
    // does not "waste" bytes that would be discarded otherwise.
    //
    // One down side of this is that the Target implementation is unable to detect restarts
    // between consicutive write operations
    let sample1: Vec<u8, 25> = Generator::seq().take(25).collect();
    let sample2: Vec<u8, 14> = Generator::fib().take(14).collect();

    // Test
    let mut v: ([u8; 25], [u8; 25], [u8; 25], [u8; 14], [u8; 14]) = Default::default();
    let mut ops = [
        Operation::Write(&sample1), // goes to v2
        Operation::Read(&mut v.0),
        Operation::Read(&mut v.1),
        Operation::Read(&mut v.2),
        Operation::Write(&sample2), // goes to v3
        Operation::Read(&mut v.3),
        Operation::Write(&sample2), // goes to v4
        Operation::Write(&sample1), // remains in buffer
        Operation::Write(&sample1), // remains in buffer
        Operation::Read(&mut v.4),
    ];

    let case = async {
        state
            .controller
            .as_mut()
            .expect("controller's missing.")
            .transaction(addr, &mut ops)
            .await
            .expect("Controller test success");
        wait_with(&state.payload, |p| p.stop_cnt != 1).await;
    };
    futures::select_biased! {
        _ = case.fuse() => {}
        _ = target_handler(
            &state.payload,
            state.target.as_mut().take().expect("target’s missing"),
        ).fuse() => {}
    }

    // Validate

    // There are 14restarts in this sequence but because of latency in the target handling, it
    // may only detect 7.
    let actual_restart_count = state.payload.borrow().restart_cnt;
    assert!(
        restart_count.contains(&actual_restart_count),
        "restart count out of range {} ∉ {:?}",
        actual_restart_count,
        restart_count
    );
    // assert writes
    let e: FIFOBuffer = itertools::chain!(
        Generator::seq().take(25),
        Generator::fib().take(14),
        Generator::fib().take(14),
        Generator::seq().take(25),
        Generator::seq().take(25),
    )
    .collect();
    assert_eq!(e, state.payload.borrow().vec);
    // assert reads
    let g: FIFOBuffer = itertools::chain!(
        Generator::fib().take(25),
        Generator::fib().skip(32).take(25),
        Generator::fib().skip(64).take(25),
        Generator::fib().skip(96).take(14),
        Generator::fib().skip(112).take(14),
    )
    .collect();
    let h: FIFOBuffer = itertools::chain!(
        v.0.into_iter(),
        v.1.into_iter(),
        v.2.into_iter(),
        v.3.into_iter(),
        v.4.into_iter()
    )
    .collect();
    assert_eq!(g, h);
}

pub async fn transaction_iter<A: ValidAddress>(state: &mut State, addr: A) {
    use i2c_write_iter::non_blocking::I2cIter;
    reset(state, addr);

    let samples: FIFOBuffer = Generator::seq().take(25).collect();
    let controller = state.controller.as_mut().expect("controller's missing.");
    let case = async {
        controller
            .transaction_iter(
                addr,
                [i2c_write_iter::Operation::WriteIter(
                    samples.iter().cloned(),
                )],
            )
            .await
            .expect("Successful write_iter");
        wait_with(&state.payload, |p| p.stop_cnt != 1).await;
    };

    futures::select_biased! {
        _ = case.fuse() => {}
        _ = target_handler(
            &state.payload,
            state.target.as_mut().take().expect("target’s missing"),
        ).fuse() => {}
    }

    assert_eq!(samples, state.payload.borrow().vec);
}
