// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
/**
 * A module for the Spaceships Extensible Request Protocol (SERP).
 *
 * SERP is an application layer protocol that Spaceships uses for networked
 * multiplayer and many other types of requests, including the discovery and
 * publishing of online servers.
 *
 * SERP is built over QUIC, a transport layer protocol over UDP that provides
 * many features and advantages that the traditional protocol of TCP doesn't
 * have. One of these features is support for multiple independent streams,
 * which this documentation discusses.
 *
 * SERP consists of two phases: *initiation* and *connection*. The initiation
 * phase involves a handshake where both sides agree which version of which
 * *subprotocol* to use. If successful, SERP enters the connection phase which
 * is defined by the agreed upon subprotocol.
 *
 * Here are these steps:
 * 1. QUIC connection request sent by client.
 * 2. QUIC connection request received by server and response replied.
 * 3. SERP initiation phase begins, client sends SERP request.
 * 4. Server replies with SERP response.
 * 5. If the response was successful, enters the SERP connection phase. Exactly
 *    what happens in this state depends on the subprotocol.
 *
 * This overall explanation is pretty vague, for more details see the
 * documentation in the `init` and `conn` submodules for the initiation and
 * connection phases respectively. Also see the `vu30` submodule for an
 * explanation of the variable-length integer type that this protocol uses.
 */
mod init;
mod conn;
pub mod vu30;

pub use init::{client::Client as InitClient, server::{Server as InitServer, PendingResponse, ResponseSender}, response::FailureResponse};
pub use init::request;
pub use conn::{Connection, ConnectionHandle, sender::Sender, receiver::Receiver};

use std::fmt::{Display, Formatter, Result as FmtResult};

use quinn::{Connection as QuicConnection, ConnectionError};
use bincode::{Options, DefaultOptions, ErrorKind as BincodeErrorKind};
use serde::{Serialize, Deserialize};

/// A big enum categorising all possible errors that can arise during SERP.
#[derive(Debug, Clone)]
pub enum Error {
	/// The server replied with a non-successful SERP response. This only arises
	/// on the client-side.
	Response(FailureResponse),

	/// Peer violated SERP initiation phase protocol.
	Invalid(String),

	/// "Graceful" disconnection.
	Disconnected(Option<String>),

	/// "Non-graceful" disconnection.
	Network(String),

	/// Failed serialising (bincode and length-payload with vu30).
	Serialisation(String),

	/// Failed deserialising (bincode and length-payload with vu30).
	Deserialisation(String),

	/// Any error that doesn't fit into the above categories. Right now used in
	/// the message stream code.
	Other(String),
}

impl Error {
	fn serialisation(msg: BincodeErrorKind) -> Error {
		Error::Serialisation(Error::bincode_to_string(msg))
	}

	pub fn deserialisation(msg: BincodeErrorKind) -> Error {
		Error::Deserialisation(Error::bincode_to_string(msg))
	}

	fn bincode_to_string(err: BincodeErrorKind) -> String {
		match err {
			BincodeErrorKind::SizeLimit => String::from("byte limit reached"),
			BincodeErrorKind::Custom(err) => err,
			_ => format!("{err:?}"),
		}
	}
}

impl From<&QuicConnection> for Error {
	fn from(conn: &QuicConnection) -> Error {
		conn.close_reason().map_or(Error::Disconnected(None), Error::from)
	}
}

impl From<ConnectionError> for Error {
	fn from(err: ConnectionError) -> Error {
		match err {
			ConnectionError::TransportError(err) => Error::Network(format!("peer violated QUIC protocol: {err}")),
			ConnectionError::ConnectionClosed(err) => Error::Network(format!("connection closed: {err}")),
			ConnectionError::TimedOut => Error::Disconnected(Some(String::from("connection timed out"))),
			ConnectionError::ApplicationClosed(reason) => Error::Disconnected(Some(format!("server closed, reason provided: {reason}"))),
			_ => Error::Network(format!("other QUIC error: {err}")),
		}
	}
}

impl Display for Error {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		match self {
			Error::Response(response) => write!(fmt, "response: {response}"),
			Error::Invalid(msg) => write!(fmt, "invalid data: {msg}"),
			Error::Disconnected(Some(msg)) => write!(fmt, "disconnected: {msg}"),
			Error::Disconnected(None) => write!(fmt, "disconnected"),
			Error::Network(msg) => write!(fmt, "network error: {msg}"),
			Error::Serialisation(msg) => write!(fmt, "serialisation: {msg}"),
			Error::Deserialisation(msg) => write!(fmt, "deserialisation: {msg}"),
			Error::Other(msg) => write!(fmt, "{msg}"),
		}
	}
}

impl From<Error> for String {
	fn from(err: Error) -> String {
		err.to_string()
	}
}

const SIZE_LIMIT: usize = 1 << 16;

fn bincode_options() -> impl Options {
	DefaultOptions::new().with_little_endian().with_varint_encoding()
}

fn serialise_into<M: Serialize>(data: &mut Vec<u8>, msg: &M) -> Result<(), Error> {
	bincode_options().serialize_into(data, msg).map_err(|err| Error::serialisation(*err))
}

fn serialise<M: Serialize>(msg: &M) -> Result<Vec<u8>, Error> {
	bincode_options().serialize(msg).map_err(|err| Error::serialisation(*err))
}

fn deserialise<'de, M: Deserialize<'de>>(data: &'de [u8], size_limit: usize) -> Result<M, Error> {
	bincode_options().with_limit(size_limit as u64).deserialize(data).map_err(|err| Error::deserialisation(*err))
}
