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

use crate::{ListenConnection, RequestReplyConnection, RequestReplyLayer};

pub type TcpRequestReplyConnection =
    dyn RequestReplyConnection<RequestData = Vec<u8>, ReplyData = Vec<u8>, Error = std::io::Error>;

pub struct TcpLayer {}

impl TcpLayer {
    pub fn new() -> Self {
        Self {}
    }
}

impl Default for TcpLayer {
    fn default() -> Self {
        Self::new()
    }
}

impl RequestReplyLayer for TcpLayer {
    type Address = SocketAddr;
    type RequestData = Vec<u8>;
    type ReplyData = Vec<u8>;
    type Error = std::io::Error;

    fn listen(
        &mut self,
        addr: Self::Address,
    ) -> Result<
        Box<
            dyn Iterator<
                Item = Result<
                    Box<
                        dyn crate::ListenConnection<
                                RequestData = Self::RequestData,
                                ReplyData = Self::ReplyData,
                                Error = Self::Error,
                            >,
                    >,
                    Self::Error,
                >,
            >,
        >,
        Self::Error,
    > {
        let incoming: Box<dyn Iterator<Item = Result<_, _>>> = Box::new(
            IntoIncoming {
                listener: TcpListener::bind(addr)?,
            }
            .map(|r| {
                r.map(|stream| {
                    let connection: Box<
                        dyn ListenConnection<
                                RequestData = Self::RequestData,
                                ReplyData = Self::ReplyData,
                                Error = Self::Error,
                            >,
                    > = Box::new(TcpConnection { stream });
                    connection
                })
            }),
        );
        Ok(incoming)
    }

    fn connect(
        &mut self,
        addr: Self::Address,
    ) -> Result<
        Box<
            dyn crate::RequestReplyConnection<
                    RequestData = Self::RequestData,
                    ReplyData = Self::ReplyData,
                    Error = Self::Error,
                >,
        >,
        Self::Error,
    > {
        TcpStream::connect(addr).map(|s| {
            let connection: Box<
                dyn RequestReplyConnection<
                        RequestData = Self::RequestData,
                        ReplyData = Self::ReplyData,
                        Error = Self::Error,
                    >,
            > = Box::new(TcpConnection { stream: s });
            connection
        })
    }
}

pub struct TcpConnection {
    pub stream: TcpStream,
}

impl ListenConnection for TcpConnection {
    type RequestData = Vec<u8>;
    type ReplyData = Vec<u8>;
    type Error = std::io::Error;

    fn handle_next(
        &mut self,
        handler: Box<dyn FnOnce(Self::RequestData) -> Result<Self::ReplyData, Self::Error>>,
    ) -> Result<(), Self::Error> {
        let request = self.receive()?;
        let reply = handler(request)?;
        self.send(&reply)?;
        Ok(())
    }
}

impl RequestReplyConnection for TcpConnection {
    type RequestData = Vec<u8>;
    type ReplyData = Vec<u8>;
    type Error = std::io::Error;

    fn request(&mut self, request: &Self::RequestData) -> Result<Self::ReplyData, Self::Error> {
        self.send(request)?;
        let reply = self.receive()?;

        Ok(reply)
    }
}

impl TcpConnection {
    pub fn send(&mut self, request: &[u8]) -> std::io::Result<()> {
        let len_raw = (request.len() as u64).to_le_bytes();
        self.stream.write_all(&len_raw)?;
        self.stream.write_all(request)?;
        Ok(())
    }

    pub fn receive(&mut self) -> std::io::Result<Vec<u8>> {
        let reply_len = {
            let mut raw = [0; 8];
            self.stream.read_exact(&mut raw)?;
            u64::from_le_bytes(raw) as usize
        };
        let mut reply = vec![0; reply_len];
        self.stream.read_exact(&mut reply)?;
        Ok(reply)
    }
}

// taken from `std::net::tcp` module (still unstable)
pub struct IntoIncoming {
    listener: TcpListener,
}

impl Iterator for IntoIncoming {
    type Item = std::io::Result<TcpStream>;
    fn next(&mut self) -> Option<std::io::Result<TcpStream>> {
        Some(self.listener.accept().map(|p| p.0))
    }
}

impl std::iter::FusedIterator for IntoIncoming {}
