/*
    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};
use log::{debug, 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,
}
impl Network {
    pub fn connect(addr: &str) -> Result {
        let (parts, _) = addr.into_client_request().unwrap().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(()).unwrap();
        let host = request.uri().host().unwrap();
        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,
            });
        let stream = TcpStream::connect((host, port))?;
        stream.set_nodelay(true).unwrap();
        let (mut sock, _) = client_tls_with_config(request, stream, None, None).unwrap();
        match sock.get_mut() {
            MaybeTlsStream::Plain(s) => s.set_nonblocking(true).unwrap(),
            MaybeTlsStream::Rustls(s) => s.sock.set_nonblocking(true).unwrap(),
            _ => todo!(),
        };
        Ok(Self {
            sock,
            queue_in: VecDeque::new(),
            queue_out: VecDeque::new(),
        })
    }
    pub fn poll(&mut self) {
        loop {
            self.queue_in.extend(match self.sock.read() {
                Ok(Message::Text(packet)) => match serde_json::from_str(&packet) {
                    Ok(packet) => {
                        debug!("<- {packet:?}");
                        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:?}");
            self.sock
                .write(Message::Text(serde_json::to_string(&packet).unwrap()))
                .unwrap();
        }
        self.sock.flush().unwrap();
    }
}