// src/bin/main.rs
#![no_std]
#![no_main]

use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::dma::Request;
use embassy_stm32::gpio::{Input, Pull};
use dma_gpio::software_uart::{
    dma_timer::{init_tim6_for_uart, init_tim7_for_uart},
    debug::dump_tim6_regs,
};
use dma_gpio::config::{BAUD, RX_OVERSAMPLE, TX_OVERSAMPLE};
use dma_gpio::config::{TX_RING_BYTES, RX_RING_BYTES};
use static_cell::StaticCell;
use embassy_futures::yield_now;
use dma_gpio::hw_uart_pc::usart1;
use dma_gpio::hw_uart_pc::driver::uart_task;
use embassy_stm32::usart::{BufferedUart, Config, BufferedInterruptHandler};
use embassy_stm32::peripherals;
use embassy_stm32::bind_interrupts;
use dma_gpio::config::{PIPE_HW_TX, PIPE_HW_RX};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
use dma_gpio::hw_uart_internal::usart2;
use dma_gpio::hw_uart_internal::driver::uart_task as uart_task_internal;
use dma_gpio::config::{PIPE_INT_TX, PIPE_INT_RX};
use embassy_stm32::pac;
use embassy_stm32::interrupt;
use embassy_time::{Duration, Timer};
use embassy_sync::channel::Channel;
use {defmt_rtt as _, panic_probe as _};

use cortex_m::interrupt::Mutex;
use core::cell::RefCell;

static RX_PIN_GLOBAL: Mutex<RefCell<Option<&'static mut Input<'static>>>> = Mutex::new(RefCell::new(None));

bind_interrupts!(struct Irqs {
    USART1 => BufferedInterruptHandler<peripherals::USART1>;
});
bind_interrupts!(struct Irqs2 {
    USART2 => BufferedInterruptHandler<peripherals::USART2>;
});

// Software uart
pub const TIM6_UP_REQ: Request = 4;
static SW_TX_RING: StaticCell<[u32; TX_RING_BYTES]> = StaticCell::new();
static SW_RX_RING: StaticCell<[u8; RX_RING_BYTES]> = StaticCell::new();
static RAW_BITS_CHANNEL: Channel<CriticalSectionRawMutex, u8, 4096> = Channel::new();

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    info!("boot");
    let p = embassy_stm32::init(Default::default());
    info!("init m8");
    // HARDWARE UART to the PC
    let mut cfg = Config::default();
    cfg.baudrate = BAUD;
    static TX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
    static RX_BUF: StaticCell<[u8; 256]> = StaticCell::new();
    let uart = BufferedUart::new(
        p.USART1,
        p.PA10, // RX pin
        p.PA9,  // TX pin
        TX_BUF.init([0; 256]),
        RX_BUF.init([0; 256]),
        Irqs,
        cfg,
    ).unwrap();
    let _ = usart1::setup_and_spawn(BAUD);
    spawner.spawn(uart_task(uart, &PIPE_HW_TX, &PIPE_HW_RX).unwrap());
    // END OF HARDWARE UART to the PC

    // INTERNAL HARDWARE UART (USART2)
    let mut cfg2 = Config::default();
    cfg2.baudrate = BAUD;
    static TX_BUF2: StaticCell<[u8; 256]> = StaticCell::new();
    static RX_BUF2: StaticCell<[u8; 256]> = StaticCell::new();

    let uart2 = BufferedUart::new(
        p.USART2,
        p.PA3, // RX
        p.PA2, // TX
        TX_BUF2.init([0; 256]),
        RX_BUF2.init([0; 256]),
        Irqs2,
        cfg2,
    ).unwrap();
    let _ = usart2::setup_and_spawn(BAUD);
    spawner.spawn(uart_task_internal(uart2, &PIPE_INT_TX, &PIPE_INT_RX).unwrap());
    info!("USART2 ready");
    // END OF INTERNAL HARDWARE UART (USART2)

    // USART1 <-> USART2 bridge
    spawner.spawn(bridge_usart1_rx_to_usart2_tx(&PIPE_HW_RX, &PIPE_INT_TX).unwrap());
    spawner.spawn(bridge_usart2_rx_to_usart1_tx(&PIPE_INT_RX, &PIPE_HW_TX).unwrap());
    info!("USART1 <-> USART2 bridge active");
    // END OF USART1 <-> USART2 bridge

    // SOFTWARE UART CONFIG
    // We initialize the Input here to ensure GPIO is configured (PullUp, etc.)
    let mut rx_pin = Input::new(p.PD6, Pull::Up);

	use cortex_m::interrupt::free;
	let rx_pin_ref: &'static mut Input<'static> = unsafe { core::mem::transmute(&mut rx_pin) };
	free(|cs| RX_PIN_GLOBAL.borrow(cs).replace(Some(rx_pin_ref)));
    
    init_tim6_for_uart(p.TIM6, BAUD, TX_OVERSAMPLE);
    
    // Init TIM7 with Interrupt enabled (see dma_timer.rs)
    init_tim7_for_uart(p.TIM7, BAUD, RX_OVERSAMPLE);
    dump_tim6_regs();

    // Enable TIM7 Interrupt in NVIC
    unsafe {
        cortex_m::peripheral::NVIC::unmask(pac::Interrupt::TIM7);
    }
    info!("TIM7 Interrupt enabled");
    // END OF SOFTWARE UART
    


    let mut last_val = 0;
    let mut count = 0u32;
    
    loop {
        // Receive next bit from ISR (yields if empty)
        let val = RAW_BITS_CHANNEL.receive().await;

        if val != last_val {
            info!("PD6 bit changed: {} (after {} stable ticks)", val, count);
            last_val = val;
            count = 0;
        } else {
            count += 1;
        }

        // CRITICAL: Yield periodically.
        // The ISR produces 153,600 samples/s. Processing them one-by-one 
        // will starve the UART Bridge tasks if we don't yield explicitly.
        if count % 128 == 0 {
            yield_now().await;
        }
    }
}

#[interrupt]
fn TIM7() {
    let tim = unsafe { pac::TIM7 };

    if tim.sr().read().uif() {
        tim.sr().modify(|w| w.set_uif(false));

        cortex_m::interrupt::free(|cs| {
            if let Some(pin) = RX_PIN_GLOBAL.borrow(cs).borrow_mut().as_mut() {
                let val = if pin.is_high() { 1u8 } else { 0u8 };
                let _ = RAW_BITS_CHANNEL.try_send(val);
            }
        });
    }
}

#[embassy_executor::task]
pub async fn bridge_usart1_rx_to_usart2_tx(
    usart1_rx: &'static Pipe<CriticalSectionRawMutex, 1024>,
    usart2_tx: &'static Pipe<CriticalSectionRawMutex, 1024>,
) {
    let mut buf = [0u8; 64];
    loop {
        let n = usart1_rx.read(&mut buf).await;
        if n > 0 {
            let _ = usart2_tx.write(&buf[..n]).await;
            info!("Buffer USART1 -> USART2 bytes: {:?}", &buf[..n]);
        }
        yield_now().await;
    }
}

#[embassy_executor::task]
pub async fn bridge_usart2_rx_to_usart1_tx(
    usart2_rx: &'static Pipe<CriticalSectionRawMutex, 1024>,
    usart1_tx: &'static Pipe<CriticalSectionRawMutex, 1024>,
) {
    let mut buf = [0u8; 64];
    loop {
        let n = usart2_rx.read(&mut buf).await;
        if n > 0 {
            let _ = usart1_tx.write(&buf[..n]).await;
            // info!("Buffer USART2 -> USART1 bytes: {:?}", &buf[..n]);
        }
        yield_now().await;
    }
}
