use crate::serial_core::codec::Codec;
use crate::serial_core::codecs::tactile_a::TactileACodec;
use crate::serial_core::frame::TactileAFrame;
use crate::serial_core::utils::elapsed_millis;
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use std::io::{Read, Write};
use std::time::{Duration, Instant};

const POLL_INTERVAL_MS: u64 = 10;

/// Runs the serial polling loop on the calling (background) thread.
/// Sends decoded pressure matrix data (Vec<i32>) to the output channel.
pub fn run_serial_loop(
    port: &mut dyn ReadWrite,
    rows: usize,
    cols: usize,
    cancel_rx: &Receiver<()>,
    sample_tx: &Sender<Vec<i32>>,
) {
    let session_started_at = Instant::now();
    let mut codec = TactileACodec::new(cols, rows);
    let req_frame = TactileACodec::build_req_frame(cols, rows);
    let mut buffer = [0u8; 1024];
    let mut poll_interval = Duration::from_millis(POLL_INTERVAL_MS);

    loop {
        // Check cancel
        if cancel_rx.try_recv().is_ok() {
            break;
        }

        // Send poll request
        if let Ok(req_bytes) = codec.encode(&req_frame) {
            let _ = port.write_all(&req_bytes);
        }

        // Read response with poll interval
        let deadline = Instant::now() + poll_interval;
        loop {
            if Instant::now() >= deadline {
                break;
            }

            match port.read(&mut buffer) {
                Ok(n) if n > 0 => {
                    if let Ok(frames) = codec.decode(&buffer[..n], session_started_at) {
                        for frame in frames {
                            if let TactileAFrame::Rep(rep) = frame {
                                if let Ok(vals) = TactileACodec::parse_data_frame(&rep.payload) {
                                    let _ = sample_tx.try_send(vals);
                                }
                            }
                        }
                    }
                }
                Ok(_) => {
                    std::thread::sleep(Duration::from_millis(1));
                }
                Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => {
                    continue;
                }
                Err(e) => {
                    eprintln!("[serial] read error: {e}");
                    return;
                }
            }
        }
    }
}

/// Trait abstracting read+write for the serial port
pub trait ReadWrite: Send {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()>;
}

/// Wrapper for serialport's Box<dyn SerialPort>
pub struct SerialPortReadWrite {
    inner: Box<dyn serialport::SerialPort>,
}

impl SerialPortReadWrite {
    pub fn new(port: Box<dyn serialport::SerialPort>) -> Self {
        Self { inner: port }
    }
}

impl ReadWrite for SerialPortReadWrite {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.inner.read(buf)
    }

    fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
        self.inner.write_all(buf)
    }
}
