// src/send.rs

use embassy_stm32::gpio::{Level, Output};
use embassy_sync::{
    blocking_mutex::raw::CriticalSectionRawMutex,
    channel::{Receiver, Sender},
};
use embassy_time::{Duration, Ticker};

use crate::{level_for_bit, Encoding};

const START: u8 = 0x7E;
const STOP: u8 = 0x81;
const BIT_PERIOD: Duration = Duration::from_millis(10);

pub type Tx = Sender<'static, CriticalSectionRawMutex, u8, 128>;

fn set_level(pin: &mut Output<'static>, is_high: bool) {
    pin.set_level(if is_high { Level::High } else { Level::Low });
}

async fn send_byte(byte: u8, tx: &Tx) {
    for i in (0..8).rev() {
        tx.send((byte >> i) & 1).await;
    }
}

/// [START] [LEN] [PAYLOAD ...] [CRC] [P] [STOP]
pub async fn msg_encode(payload: &[u8], tx: &Tx) {
    // start
    send_byte(START, tx).await;

    // len
    let len = payload.len() as u8;
    send_byte(len, tx).await;
    for &b in payload {
        send_byte(b, tx).await;
    }

    // crc
    let mut crc = crc8(0, len);
    for &b in payload {
        crc = crc8(crc, b);
    }
    send_byte(crc, tx).await;

    let mut ones = len.count_ones() + crc.count_ones();
    for &b in payload {
        ones += b.count_ones();
    }
    tx.send((ones as u8) & 1).await;

    send_byte(STOP, tx).await;

    // idle
    tx.send(1).await;
}

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
}

#[embassy_executor::task]
pub async fn bit_send(
    mut pin: Output<'static>,
    rx: Receiver<'static, CriticalSectionRawMutex, u8, 128>,
    encoding: Encoding,
) {
    let mut ticker = Ticker::every(BIT_PERIOD / 2);
    let mut is_high = level_for_bit(1);
    set_level(&mut pin, is_high);

    loop {
        let bit = rx.receive().await;
        match encoding {
            Encoding::Nrz => {
                is_high = level_for_bit(bit);
                set_level(&mut pin, is_high);
                ticker.next().await;
            }
            // toggle, ak sme v 1, inac sa nedeje nic
            Encoding::Nrzi => {
                if bit == 1 {
                    is_high = !is_high;
                    if is_high {
                        pin.set_high();
                    } else {
                        pin.set_low();
                    }
                }
                ticker.next().await;
            }
            // manchester xor tabulka
            Encoding::Manchester => {
                if bit == 1 {
                    // high -> low
                    set_level(&mut pin, level_for_bit(1));
                    ticker.next().await;
                    set_level(&mut pin, level_for_bit(0));
                    ticker.next().await;
                } else {
                    // low -> high
                    set_level(&mut pin, level_for_bit(0));
                    ticker.next().await;
                    set_level(&mut pin, level_for_bit(1));
                    ticker.next().await;
                }
            }
        };
    }
}
