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

#[cfg(feature = "tls")]
use rustls::{ConnectionCommon, SideData};

pub trait HttpStream: Read + Write {
    fn set_non_blocking(&mut self) -> io::Result<()> {
        Ok(())
    }

    fn set_blocking(&mut self, timeout: Duration) -> io::Result<()> {
        let _ = timeout;
        Ok(())
    }
}

pub trait IntoHttpStream {
    type Stream: HttpStream + 'static;
    fn into_http_stream(self) -> Self::Stream;
}

#[derive(Debug)]
pub struct StringStream {
    input: Vec<u8>,
    offset: usize,
    output: Vec<u8>,
}

impl Read for StringStream {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let n = self.peek(buf)?;
        self.offset += n;
        Ok(n)
    }

    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
        const CHUNK_SIZE: usize = 1024;
        let mut chunk: [u8; CHUNK_SIZE] = [0; CHUNK_SIZE];
        let n = self.offset;
        while self.read(&mut chunk)? > 0 {
            buf.write_all(&chunk)?;
        }
        Ok(self.offset - n)
    }
}

impl Write for StringStream {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.output.extend_from_slice(buf);
        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl StringStream {
    pub fn peek(&self, buf: &mut [u8]) -> io::Result<usize> {
        let min = usize::min(buf.len(), self.input.len() - self.offset);
        buf[..min].copy_from_slice(&self.input[self.offset..self.offset + min]);
        Ok(min)
    }
}

impl HttpStream for StringStream {}

impl IntoHttpStream for String {
    type Stream = StringStream;

    fn into_http_stream(self) -> Self::Stream {
        let src_vec = self.into_bytes();
        StringStream {
            input: src_vec,
            offset: 0,
            output: Vec::new(),
        }
    }
}

impl IntoHttpStream for &str {
    type Stream = StringStream;

    fn into_http_stream(self) -> Self::Stream {
        self.to_string().into_http_stream()
    }
}

impl<S: HttpStream + 'static> IntoHttpStream for S {
    type Stream = S;

    fn into_http_stream(self) -> Self::Stream {
        self
    }
}

impl HttpStream for TcpStream {
    fn set_blocking(&mut self, timeout: Duration) -> io::Result<()> {
        self.set_read_timeout(Some(timeout))
    }

    fn set_non_blocking(&mut self) -> io::Result<()> {
        self.set_read_timeout(None)
    }
}

pub struct DummyStream;

impl Read for DummyStream {
    fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
        Ok(0)
    }
}

impl Write for DummyStream {
    fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
        Ok(0)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl HttpStream for DummyStream {}

pub fn dummy() -> Box<dyn HttpStream> {
    Box::new(DummyStream)
}

#[cfg(feature = "tls")]
impl<C, SD, S: HttpStream> HttpStream for rustls::StreamOwned<C, S>
where
    SD: SideData,
    C: core::ops::DerefMut<Target = ConnectionCommon<SD>>,
{
    fn set_non_blocking(&mut self) -> io::Result<()> {
        self.sock.set_non_blocking()
    }

    fn set_blocking(&mut self, timeout: Duration) -> io::Result<()> {
        self.sock.set_blocking(timeout)
    }
}