// src/software_uart/gpio_dma_uart_tx.rs
use embassy_executor::task;
use embassy_stm32::{
    dma::{Request, TransferOptions, WritableRingBuffer},
    peripherals::GPDMA1_CH0,
    Peri,
};
use embassy_futures::yield_now;
use defmt::info;

use embassy_sync::pipe::Pipe;
use crate::config::{TX_PIN_BIT, UART_CFG};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use crate::software_uart::uart_emulation::encode_uart_byte_cfg;

pub const TIM6_UP_REQ: Request = 4;

pub async fn encode_uart_frames<'a>(
    pin_bit: u8,
    bytes: &[u8],
    out_buf: &'a mut [u32],
) -> usize {
    let mut offset = 0;
    for &b in bytes {
        let mut frame = [0u32; 12];
        let used = encode_uart_byte_cfg(pin_bit, b, &UART_CFG, &mut frame);

        if offset + used <= out_buf.len() {
            out_buf[offset..offset + used].copy_from_slice(&frame[..used]);
            offset += used;
        } else {
            break;
        }

        // cooperative async yield
        yield_now().await;
    }
    offset
}

/// TX DMA task: encodes UART frames and sends them via DMA at TIM6 rate
#[task]
pub async fn tx_dma_task(
    ch: Peri<'static, GPDMA1_CH0>,
    register: *mut u32, // ODR/BSRR
    tx_ring_mem: &'static mut [u32],
    pipe_tx: &'static Pipe<CriticalSectionRawMutex, 1024>,
) {
    let mut tx_opts = TransferOptions::default();
    tx_opts.half_transfer_ir = true;
    tx_opts.complete_transfer_ir = true;

    // SAFETY: tx_ring is exclusive to this task
    let mut tx_ring = unsafe {
        WritableRingBuffer::new(
            ch,
            TIM6_UP_REQ,
            register,
            tx_ring_mem,
            tx_opts,
        )
    };

    tx_ring.start();
    info!("TX DMA ring started");

    let mut frame_buf = [0u32; 4096];
    let mut rx_buf = [0u8; 256];

    loop {
        let n = pipe_tx.read(&mut rx_buf).await;
        if n == 0 {
            yield_now().await;
            continue;
        }

        let used = encode_uart_frames(TX_PIN_BIT, &rx_buf[..n], &mut frame_buf).await;
        if used > 0 {
            let _ = tx_ring.write_exact(&frame_buf[..used]).await;
        }
        info!("tx_dma_task wrote {} words", used);
        yield_now().await;
    }
}
