use std::{
    io::{self, Read, Write},
    net::TcpStream,
};

#[derive(Debug)]
pub enum Connection {
    #[allow(dead_code)]
    Bluetooth,

    TcpStream(TcpStream),

    #[cfg(feature = "serial")]
    Serial(Box<dyn serialport::SerialPort>),

    #[cfg(test)]
    PartialReadTest {
        data: Vec<u8>,
        status: usize,
    },
}

impl Connection {
    pub fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
        match self {
            Connection::Bluetooth => unimplemented!(),
            Connection::TcpStream(stream) => stream.write_all(buf),

            #[cfg(feature = "serial")]
            Connection::Serial(port) => port.write_all(buf),

            #[cfg(test)]
            Connection::PartialReadTest { data: _, status: _ } => Ok(()),
        }
    }

    pub fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self {
            Connection::Bluetooth => todo!(),
            Connection::TcpStream(stream) => stream.read(buf),

            #[cfg(feature = "serial")]
            Connection::Serial(port) => port.read(buf),

            #[cfg(test)]
            Connection::PartialReadTest { data, status } => match status {
                0 => {
                    buf[0] = data[0];
                    *status = 1;
                    Ok(1)
                }
                1 => {
                    *status = 2;
                    Err(io::Error::new(io::ErrorKind::Interrupted, "oh no!"))
                }
                _ => {
                    buf[..data.len() - 1].copy_from_slice(&data[1..]);
                    Ok(data.len() - 1)
                }
            },
        }
    }
}

impl From<TcpStream> for Connection {
    fn from(stream: TcpStream) -> Self {
        Connection::TcpStream(stream)
    }
}

#[cfg(feature = "serial")]
impl From<Box<dyn serialport::SerialPort>> for Connection {
    fn from(port: Box<dyn serialport::SerialPort>) -> Self {
        Connection::Serial(port)
    }
}

#[cfg(test)]
mod test {

    use lwk_common::Network;
    use serde_cbor::Value;

    use crate::{
        protocol::{Request, Response},
        Jade,
    };

    use super::Connection;

    #[test]
    fn partial_read() {
        let text = Value::Text("Hello".to_string());

        let resp = Response {
            id: "0".to_string(),
            result: Some(text.clone()),
            error: None,
        };
        let mut data = Vec::new();
        serde_cbor::to_writer(&mut data, &resp).unwrap();

        let connection = Connection::PartialReadTest { data, status: 0 };

        let jade = Jade::new(connection, Network::LocaltestLiquid);
        let result: Value = jade.send(Request::Ping).unwrap();
        assert_eq!(result, text);
    }
}
