mod config;

use config::{hw_config, HardwareConfig};
use rstest::rstest;
use serialport::*;
use std::io::Read;
use std::thread;
use std::time::{Duration, Instant};

#[rstest]
#[case(1, Vec::from(b"abcdef"))]
#[case(
    20,
    Vec::from(b"0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")
)]
#[cfg_attr(feature = "ignore-hardware-tests", ignore)]
fn test_read_returns_available_data_before_timeout(
    hw_config: HardwareConfig,
    #[case] chunk_size: usize,
    #[case] message: Vec<u8>,
) {
    let send_period = Duration::from_millis(500);
    let receive_timeout = Duration::from_millis(3000);
    let marign = Duration::from_millis(100);

    let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap();
    let mut receiver = serialport::new(hw_config.port_2, 115200)
        .timeout(receive_timeout)
        .open()
        .unwrap();

    sender.clear(ClearBuffer::All).unwrap();
    receiver.clear(ClearBuffer::All).unwrap();

    let expected_message = message.clone();
    let receiver_thread = thread::spawn(move || {
        let chunk_timeout = send_period + marign;
        assert!(receiver.timeout() > 3 * chunk_timeout);

        let mut received_message = Vec::with_capacity(expected_message.len());

        loop {
            let chunk_start = Instant::now();
            let expected_chunk_until = chunk_start + chunk_timeout;

            let mut buffer = [0u8; 1024];
            assert!(buffer.len() > expected_message.len());

            // Try to read more data than we are expecting and expect some data to be available
            // after the send period (plus some margin).
            match receiver.read(&mut buffer) {
                Ok(read) => {
                    assert!(expected_chunk_until > Instant::now());
                    assert!(read > 0);
                    println!(
                        "receive: {} bytes after waiting {} ms",
                        read,
                        (Instant::now() - chunk_start).as_millis()
                    );
                    received_message.extend_from_slice(&buffer[..read]);
                }
                e => panic!("unexpected error {:?}", e),
            }

            if received_message.len() >= expected_message.len() {
                break;
            }
        }

        assert_eq!(expected_message, received_message);
    });

    let sender_thread = thread::spawn(move || {
        let mut next = Instant::now();

        for chunk in message.chunks(chunk_size) {
            sender.write_all(chunk).unwrap();
            sender.flush().unwrap();

            println!("send: {} bytes", chunk.len());

            next += send_period;
            thread::sleep(next - Instant::now());
        }
    });

    sender_thread.join().unwrap();
    receiver_thread.join().unwrap();
}

#[rstest]
#[case(b"a")]
#[case(b"0123456789")]
#[case(b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")]
#[cfg_attr(feature = "ignore-hardware-tests", ignore)]
fn test_timeout_zero(hw_config: HardwareConfig, #[case] message: &[u8]) {
    let timeout = Duration::ZERO;
    let margin = Duration::from_millis(100);

    let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap();
    let mut receiver = serialport::new(hw_config.port_2, 115200)
        .timeout(timeout)
        .open()
        .unwrap();
    let mut buffer: [u8; 1024] = [0xff; 1024];

    sender.clear(ClearBuffer::All).unwrap();
    receiver.clear(ClearBuffer::All).unwrap();

    sender.write_all(message).unwrap();
    sender.flush().unwrap();
    let flushed_at = Instant::now();

    let expected_until = flushed_at + timeout + margin;
    let mut timeouts = 0usize;

    loop {
        match receiver.read(&mut buffer) {
            Ok(read) => {
                assert!(read > 0);
                println!(
                    "read: {} bytes of {} after {} timeouts/{} ms",
                    read,
                    message.len(),
                    timeouts,
                    (Instant::now() - flushed_at).as_millis()
                );
                assert_eq!(message[..read], buffer[..read]);
                break;
            }
            Err(e) => {
                assert_eq!(e.kind(), std::io::ErrorKind::TimedOut);
                timeouts += 1;
            }
        }

        assert!(expected_until > Instant::now());
    }
}

#[rstest]
#[case(Duration::from_millis(10))]
#[case(Duration::from_millis(100))]
#[case(Duration::from_millis(1000))]
#[cfg_attr(feature = "ignore-hardware-tests", ignore)]
fn test_timeout_greater_zero(hw_config: HardwareConfig, #[case] timeout: Duration) {
    let margin = Duration::from_millis(100);

    let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap();
    let mut receiver = serialport::new(hw_config.port_2, 115200)
        .timeout(timeout)
        .open()
        .unwrap();

    let message =
        b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
    let mut buffer: [u8; 1024] = [0xff; 1024];

    sender.clear(ClearBuffer::All).unwrap();
    receiver.clear(ClearBuffer::All).unwrap();

    sender.write_all(message).unwrap();
    sender.flush().unwrap();

    let flushed_at = Instant::now();

    let read = receiver.read(&mut buffer).unwrap();
    let read_at = Instant::now();

    println!(
        "read: {} bytes of {} after {} ms",
        read,
        message.len(),
        (Instant::now() - flushed_at).as_millis()
    );

    assert!(read > 0);
    assert!(flushed_at + timeout + margin > read_at);
    assert_eq!(buffer[..read], message[..read]);
}

/// Checks that reading data with a timeout of `Duration::MAX` returns some data and no error. It
/// does not check the actual timeout for obvious reason.
#[rstest]
#[cfg_attr(feature = "ignore-hardware-tests", ignore)]
fn test_timeout_max(hw_config: HardwareConfig) {
    let sleep = Duration::from_millis(3000);
    let margin = Duration::from_millis(500);
    let mut sender = serialport::new(hw_config.port_1, 115200).open().unwrap();
    let mut receiver = serialport::new(hw_config.port_2, 115200)
        .timeout(Duration::MAX)
        .open()
        .unwrap();

    let message =
        b"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
    let mut buffer: [u8; 1024] = [0xff; 1024];

    sender.clear(ClearBuffer::All).unwrap();
    receiver.clear(ClearBuffer::All).unwrap();

    let started_at = Instant::now();

    let sender_thread = thread::spawn(move || {
        thread::sleep(sleep);

        sender.write_all(message).unwrap();
        sender.flush().unwrap();
    });

    let read = receiver.read(&mut buffer).unwrap();
    let read_at = Instant::now();

    println!(
        "read: {} bytes of {} after {} ms",
        read,
        message.len(),
        (Instant::now() - started_at).as_millis()
    );

    assert!(read > 0);
    assert!(read_at > started_at + sleep);
    assert!(read_at < started_at + sleep + margin);
    assert_eq!(buffer[..read], message[..read]);

    sender_thread.join().unwrap();
}
