use defmt::info;
use embassy_stm32::exti::ExtiInput;
use embassy_time::{Ticker, Timer};

use crate::{Encoding, bit_for_level, level_for_bit};

const START: u8 = 0x7E;
const STOP: u8 = 0x81;
const MAX_PAYLOAD: usize = 64;

fn crc8(crc: u8, byte: u8) -> u8 {
    let mut c = crc ^ byte;
    for _ in 0..8 {
        c = if c & 0x80 != 0 {
            (c << 1) ^ 0x07
        } else {
            c << 1
        };
    }
    c
}

async fn sample_byte(
    pin: &mut ExtiInput<'static>,
    encoding: Encoding,
    ticker: &mut Ticker,
    last_physical_level: &mut bool,
    bit_time: embassy_time::Duration,
) -> u8 {
    let mut byte = 0u8;
    for _ in 0..8 {
        let current_level = pin.is_high();
        let bit = match encoding {
            Encoding::Nrz => {
                let bit = bit_for_level(pin.is_high());
                ticker.next().await;
                bit
            }
            Encoding::Nrzi => {
                let bit = if current_level != *last_physical_level {
                    1
                } else {
                    0
                };
                *last_physical_level = current_level;
                ticker.next().await;
                bit
            }
            Encoding::Manchester => {
                Timer::after(bit_time / 4).await;
                let first_half = pin.is_high();

                Timer::after(bit_time / 2).await;
                let second_half = pin.is_high();

                Timer::after(bit_time / 4).await;

                if first_half == level_for_bit(1) && second_half == level_for_bit(0) {
                    1
                } else {
                    0
                }
            }
        };
        byte = (byte << 1) | bit;
    }
    byte
}

#[embassy_executor::task]
pub async fn bit_receive_and_decode(mut pin: ExtiInput<'static>, encoding: Encoding) {
    loop {
        pin.wait_for_falling_edge().await;
        let t1 = embassy_time::Instant::now();

        pin.wait_for_rising_edge().await;
        let bit_time = embassy_time::Instant::now().duration_since(t1);

        // stred data bitu
        if encoding != Encoding::Manchester {
            Timer::after(bit_time / 2).await;
        }

        let mut ticker = Ticker::every(bit_time);
        let mut last_level = level_for_bit(1);

        let mut start_byte = 0u8;
        for _ in 0..7 {
            let bit = match encoding {
                Encoding::Nrz => {
                    let bit = bit_for_level(pin.is_high());
                    ticker.next().await;
                    bit
                }
                Encoding::Nrzi => {
                    let current_level = pin.is_high();
                    let b = if current_level != last_level { 1 } else { 0 };
                    last_level = current_level;
                    ticker.next().await;
                    b
                }
                Encoding::Manchester => {
                    Timer::after(bit_time / 4).await;
                    let first_pol = pin.is_high();
                    Timer::after(bit_time / 2).await;
                    let second_pol = pin.is_high();
                    Timer::after(bit_time / 4).await;
                    if first_pol == level_for_bit(1) && second_pol == level_for_bit(0) {
                        1
                    } else {
                        0
                    }
                }
            };
            start_byte = (start_byte << 1) | bit;
        }

        if start_byte != START {
            info!("start fail: 0x{:02X}", start_byte);
            continue;
        }

        // len
        let len = sample_byte(&mut pin, encoding, &mut ticker, &mut last_level, bit_time).await;
        if len as usize > MAX_PAYLOAD {
            info!("bad len {}", len);
            continue;
        }

        // payload
        let mut payload = [0u8; MAX_PAYLOAD];
        for i in 0..(len as usize) {
            payload[i] =
                sample_byte(&mut pin, encoding, &mut ticker, &mut last_level, bit_time).await;
        }

        // crc
        let crc_recv =
            sample_byte(&mut pin, encoding, &mut ticker, &mut last_level, bit_time).await;

        // parita
        let parity_recv = match encoding {
            Encoding::Nrz => {
                let bit = bit_for_level(pin.is_high());
                ticker.next().await;
                bit
            }
            Encoding::Nrzi => {
                let current_p = pin.is_high();
                let b = if current_p != last_level { 1 } else { 0 };
                last_level = current_p;
                ticker.next().await;
                b
            }
            Encoding::Manchester => {
                Timer::after(bit_time / 4).await;
                let f = pin.is_high();
                Timer::after(bit_time / 2).await;
                let s = pin.is_high();
                Timer::after(bit_time / 4).await;
                if f == level_for_bit(1) && s == level_for_bit(0) {
                    1
                } else {
                    0
                }
            }
        };

        // stop
        let stop = sample_byte(&mut pin, encoding, &mut ticker, &mut last_level, bit_time).await;
        if stop != STOP {
            info!("bad stop 0x{:02X}", stop);
            continue;
        }

        // verify CRC
        let mut crc_calc = crc8(0, len);
        for i in 0..(len as usize) {
            crc_calc = crc8(crc_calc, payload[i]);
        }
        if crc_calc != crc_recv {
            info!("crc nesedi 0x{:02X} ma byt 0x{:02X}", crc_recv, crc_calc);
            continue;
        }

        // verify parity
        let mut ones = len.count_ones() + crc_recv.count_ones();
        for i in 0..(len as usize) {
            ones += payload[i].count_ones();
        }
        if (ones as u8 & 1) != parity_recv {
            info!("nesedi parita");
            continue;
        }

        if let Ok(s) = core::str::from_utf8(&payload[..len as usize]) {
            info!("RX OK: \"{}\"", s);
        } else {
            info!("RX OK: {} bytes", len);
        }
    }
}
