/*
Simplified Embedded Rust: ESP Core Library Edition
The Embassy Framework - UART Echo Application Example
*/

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_sync::{
    blocking_mutex::raw::CriticalSectionRawMutex,
    pipe::Pipe,
};
use esp_backtrace as _;
use esp_hal::{
    gpio::Io,
    peripherals::UART0,
    timer::timg::TimerGroup,
    uart::{
        config::{AtCmdConfig, Config},
        Uart, UartRx, UartTx,
    },
    Async,
};

// Read Buffer Size
const READ_BUF_SIZE: usize = 64;

// End of Transmission Character (Carrige Return -> 13 or 0x0D in ASCII)
const AT_CMD: u8 = 0x0D;

// Declare Pipe sync primitive to share data among Tx and Rx tasks
static DATAPIPE: Pipe<
    CriticalSectionRawMutex,
    READ_BUF_SIZE,
> = Pipe::new();

#[embassy_executor::task]
async fn uart_writer(
    mut tx: UartTx<'static, UART0, Async>,
) {
    // Declare write buffer to store Tx characters
    let mut wbuf: [u8; READ_BUF_SIZE] =
        [0u8; READ_BUF_SIZE];
    loop {
        // Read characters from pipe into write buffer
        DATAPIPE.read(&mut wbuf).await;
        // Transmit/echo buffer contents over UART
        embedded_io_async::Write::write(&mut tx, &wbuf)
            .await
            .unwrap();
        // Transmit a new line
        embedded_io_async::Write::write(
            &mut tx,
            &[0x0D, 0x0A],
        )
        .await
        .unwrap();
        // Flush transmit buffer
        embedded_io_async::Write::flush(&mut tx)
            .await
            .unwrap();
    }
}

#[embassy_executor::task]
async fn uart_reader(
    mut rx: UartRx<'static, UART0, Async>,
) {
    // Declare read buffer to store Rx characters
    let mut rbuf: [u8; READ_BUF_SIZE] =
        [0u8; READ_BUF_SIZE];
    loop {
        // Read characters from UART into read buffer until EOT
        let r = embedded_io_async::Read::read(
            &mut rx,
            &mut rbuf[0..],
        )
        .await;
        match r {
            Ok(len) => {
                // If read succeeds then write recieved characters to pipe
                DATAPIPE.write_all(&rbuf[..len]).await;
            }
            Err(e) => {
                esp_println::println!("RX Error: {:?}", e)
            }
        }
    }
}

#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
    let peripherals =
        esp_hal::init(esp_hal::Config::default());

    // Instantiate GPIO pins for UART
    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
    let (tx_pin, rx_pin) =
        (io.pins.gpio21, io.pins.gpio20);

    // Initalize embassy executor
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_hal_embassy::init(timg0.timer0);

    // Initialize and configure UART0
    let config = Config::default()
        .rx_fifo_full_threshold(READ_BUF_SIZE as u16);
    let mut uart0 = Uart::new_async_with_config(
        peripherals.UART0,
        config,
        tx_pin,
        rx_pin,
    )
    .unwrap();
    uart0.set_at_cmd(AtCmdConfig::new(
        None, None, None, AT_CMD, None,
    ));

    // Split UART0 to create seperate Tx and Rx handles
    let (rx, tx) = uart0.split();

    // Spawn Tx and Rx tasks
    spawner.spawn(uart_reader(rx)).ok();
    spawner.spawn(uart_writer(tx)).ok();
}