/*
    Hurry Curry! - a game about cooking
    Copyright 2024 metamuffin
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
*/
use anyhow::Result;
use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG, VERSION};
use log::{debug, info, warn};
use std::{collections::VecDeque, net::TcpStream};
use tungstenite::{
    client::{uri_mode, IntoClientRequest},
    client_tls_with_config,
    handshake::client::Request,
    stream::{MaybeTlsStream, Mode},
    util::NonBlockingError,
    Message, WebSocket,
};
pub struct Network {
    sock: WebSocket>,
    pub queue_in: VecDeque,
    pub queue_out: VecDeque,
    use_bincode: bool,
}
impl Network {
    pub fn connect(addr: &str) -> Result {
        let (parts, _) = addr.into_client_request()?.into_parts();
        let mut builder = Request::builder()
            .uri(parts.uri.clone().clone())
            .method(parts.method.clone())
            .version(parts.version);
        *builder.headers_mut().unwrap() = parts.headers.clone();
        let request = builder.body(())?;
        let host = request.uri().host().unwrap_or("127.0.0.1");
        let host = if host.starts_with('[') {
            &host[1..host.len() - 1]
        } else {
            host
        };
        let port = request
            .uri()
            .port_u16()
            .unwrap_or(match uri_mode(request.uri())? {
                Mode::Plain => 27032,
                Mode::Tls => 443,
            });
        info!("Connecting: host={host:?} port={port}");
        let stream = TcpStream::connect((host, port))?;
        stream.set_nodelay(true)?;
        let (mut sock, _) = client_tls_with_config(request, stream, None, None)?;
        match sock.get_mut() {
            MaybeTlsStream::Plain(s) => s.set_nonblocking(true)?,
            MaybeTlsStream::Rustls(s) => s.sock.set_nonblocking(true)?,
            _ => todo!(),
        };
        info!("Handshake complete.");
        Ok(Self {
            sock,
            use_bincode: false,
            queue_in: VecDeque::new(),
            queue_out: VecDeque::new(),
        })
    }
    pub fn poll(&mut self) -> anyhow::Result<()> {
        loop {
            self.queue_in.extend(match self.sock.read() {
                Ok(Message::Text(packet)) => match serde_json::from_str(&packet) {
                    Ok(packet) => {
                        debug!("<- {packet:?}");
                        if let PacketC::Version {
                            minor,
                            major,
                            supports_bincode,
                        } = &packet
                        {
                            if *minor == VERSION.0 && *major == VERSION.1 && *supports_bincode {
                                info!("Binary protocol format enabled.");
                                self.use_bincode = true;
                            }
                        }
                        Some(packet)
                    }
                    Err(e) => {
                        warn!("invalid json packet: {e:?}");
                        None
                    }
                },
                Ok(Message::Binary(packet)) => {
                    match bincode::decode_from_slice(&packet, BINCODE_CONFIG) {
                        Ok((packet, _)) => {
                            debug!("<- {packet:?}");
                            Some(packet)
                        }
                        Err(e) => {
                            warn!("invalid bincode packet: {e:?}");
                            None
                        }
                    }
                }
                Ok(_) => None,
                Err(e) => {
                    if let Some(e) = e.into_non_blocking() {
                        warn!("{e:?}");
                        None
                    } else {
                        break;
                    }
                }
            });
        }
        for packet in self.queue_out.drain(..) {
            debug!("-> {packet:?}");
            if self.use_bincode {
                self.sock.write(Message::Binary(bincode::encode_to_vec(
                    &packet,
                    BINCODE_CONFIG,
                )?))?;
            } else {
                self.sock
                    .write(Message::Text(serde_json::to_string(&packet)?))?;
            }
        }
        self.sock.flush()?;
        Ok(())
    }
}