use algorithms::crc::{Crc, Crc32 as Crc32Rust, ffi::Crc32 as Crc32C};
use algorithms::hash::{ffi as sha2_ffi, sha2_256_oneshot};
use defmt::info;
use hal::{
    delay::Delay,
    gpio::{Output, Pin, PushPull},
    prelude::*,
};

pub type SomePin = Pin<Output<PushPull>>;

pub struct MeasurePins<'a> {
    pub c: &'a mut SomePin,
    pub rs: &'a mut SomePin,
    pub led: &'a mut SomePin,
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum MeasurementKind {
    Rust,
    C,
}

const RUNS: u32 = 5000;

impl MeasurePins<'_> {
    pub fn set_led(&mut self, state: PinState) {
        self.led.set_state(state).expect("could not set led state")
    }

    pub fn set_for_kind(&mut self, kind: MeasurementKind, state: PinState) {
        match kind {
            MeasurementKind::Rust => self.rs.set_state(state),
            MeasurementKind::C => self.c.set_state(state),
        }
        .expect("could not set rs or c pin")
    }
    pub fn set_all(&mut self, state: PinState) {
        self.led.set_state(state).expect("could not set led state");
        self.rs.set_state(state).expect("could not set rs state");
        self.c.set_state(state).expect("could not set c state");
    }
}

#[inline]
pub fn bench(pins: &mut MeasurePins, delay: &mut Delay) {
    bench_crc(pins, delay);
    bench_sha256(pins, delay);
}

fn time_exec<F>(
    id: &str,
    kind: MeasurementKind,
    pins: &mut MeasurePins,
    delay: &mut Delay,
    do_loop: bool,
    mut f: F,
) where
    F: FnMut(),
{
    if do_loop {
        info!("Running '{}' {} times", id, RUNS);
        pins.set_for_kind(kind, PinState::High);
        for _ in 0..RUNS {
            f();
        }
        pins.set_for_kind(kind, PinState::Low);
    } else {
        info!("Running '{}' 1 time", id);
        pins.set_for_kind(kind, PinState::High);
        f();
        pins.set_for_kind(kind, PinState::Low);
    }
    info!("Measurement of '{}' finished!", id);
    delay.delay_ms(100_u32);
}

fn bench_crc(pins: &mut MeasurePins, delay: &mut Delay) {
    const DATA: &[u8] = b"hello world AAAAAAAAAAAAAAAAAAAAAAA";
    time_exec("crc32_rs", MeasurementKind::Rust, pins, delay, true, || {
        Crc32Rust::checksum(DATA);
    });
    time_exec("crc32_c_ffi", MeasurementKind::C, pins, delay, true, || {
        Crc32C::checksum(DATA);
    });
    time_exec(
        "crc32_c_native",
        MeasurementKind::C,
        pins,
        delay,
        false,
        || {
            Crc32C::checksum_n_times(RUNS, DATA);
        },
    );
}

fn bench_sha256(pins: &mut MeasurePins, delay: &mut Delay) {
    const DATA: &[u8] = b"hello world AAAAAAAAAAAAAAAAAAAAAAA";
    time_exec(
        "sha256_rs",
        MeasurementKind::Rust,
        pins,
        delay,
        true,
        || {
            sha2_256_oneshot(DATA).unwrap();
        },
    );
    time_exec(
        "sha256_c_ffi",
        MeasurementKind::C,
        pins,
        delay,
        true,
        || {
            sha2_ffi::sha2_256_oneshot(DATA).unwrap();
        },
    );
    time_exec(
        "sha256_c_native",
        MeasurementKind::C,
        pins,
        delay,
        false,
        || {
            sha2_ffi::sha2_256_oneshot_n_times(RUNS, DATA);
        },
    );
}
