// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
/**
 * SERP's connection phase is much simpler than the initiation phase.
 *
 * Unlike the initiation phase, the rules aren't fixed, instead being defined by
 * the agreed upon subprotocol. I can't be bothered to explicitly document the
 * rules for each supported subprotocol. The source code is the documentation.
 *
 * Unlike the initiation phase which is limited to just one stream, multiple
 * streams are allowed to be created by both the client and the server in the
 * connection phase. The bidirectional stream created by the client for the
 * initiation phase is called the *main stream*. Unidirectional streams are also
 * allowed despite not currently existing in any subprotocol.
 *
 * Right now all subprotocols use a framing mechanism of first sending a vu30
 * for the length and data of that length. This is just how things have turned
 * out so far, and isn't at all a requirement of the connection phase.
 */
pub mod sender;
pub mod receiver;

#[cfg(feature = "client")] use std::time::Duration;

use quinn::{SendStream, RecvStream, VarInt};
#[cfg(feature = "client")] use quinn::SendDatagramError;

use sender::Sender;
use receiver::Receiver;

use super::{QuicConnection, Error};

pub struct Connection {
	#[cfg(feature = "client")] pub handle: ConnectionHandle,
	pub main_stream: Stream,
}

impl Connection {
	#[cfg(feature = "client")]
	pub(super) fn new(handle: ConnectionHandle, main_stream: Stream) -> Connection {
		Connection { handle, main_stream }
	}

	#[cfg(not(feature = "client"))]
	pub(super) fn new(_handle: ConnectionHandle, main_stream: Stream) -> Connection {
		Connection { main_stream }
	}
}

#[derive(Clone)]
pub struct ConnectionHandle(QuicConnection);

impl ConnectionHandle {
	pub(super) fn new(conn: QuicConnection) -> ConnectionHandle {
		ConnectionHandle(conn)
	}

	pub async fn open_stream(&self) -> Result<Stream, Error> {
		Ok(Stream::new(self.0.open_bi().await?, &self.0))
	}

	pub async fn accept_stream(&self) -> Result<Stream, Error> {
		Ok(Stream::new(self.0.accept_bi().await?, &self.0))
	}

	#[cfg(feature = "client")]
	pub fn send_datagram(&self, data: Vec<u8>) -> Result<(), Error> {
		match self.0.send_datagram(data.into()) {
			Ok(()) | Err(SendDatagramError::UnsupportedByPeer | SendDatagramError::Disabled) => Ok(()), // Failure for some reason isn't an error
			Err(SendDatagramError::TooLarge) => {
				log::warn!("failed sending bulk update datagram, too large");
				Ok(()) // Might happen with a TOCTOU (with `max_datagram_size` check done before) so despite the size being limited, I shouldn't consider that an error
			},
			Err(SendDatagramError::ConnectionLost(err)) => Err(err.into()),
		}
	}

	pub async fn recv_datagram(&self) -> Result<Vec<u8>, Error> {
		Ok(self.0.read_datagram().await?.into())
	}

	#[cfg(feature = "client")]
	pub fn max_datagram_size(&self) -> Option<usize> {
		self.0.max_datagram_size()
	}

	#[cfg(feature = "client")]
	pub fn rtt(&self) -> Duration {
		self.0.rtt()
	}

	pub fn close(&self, code: u32, reason: &str) {
		self.0.close(VarInt::from_u32(code), reason.as_bytes());
	}
}

/**
 * A bidirectional stream.
 */
pub struct Stream {
	pub sender: Sender,
	pub receiver: Receiver,
}

impl Stream {
	fn new((sender, receiver): (SendStream, RecvStream), conn: &QuicConnection) -> Stream {
		Stream { sender: Sender::new(sender, conn.clone()), receiver: Receiver::new(receiver, conn.clone()) }
	}
}
