Documentation
use data_encoding::BASE64URL_NOPAD;
use serde::{Serialize, Deserialize};
use strum::EnumDiscriminants;

use crate::peer::{
	status,
	NodeID
};
use crate::store::{
	Op,
	Version
};

#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Packet {
	pub dst: NodeID,
	pub src: NodeID,
	pub clk: status::Clock,
	pub hsh: u64,
	pub vec: Vec<status::PeerState>,
	pub png: PingOrPong,
	pub pyl: Payload
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PingOrPong {
	Ping,
	Pong
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum Payload {
	Protocol(ProtocolPayload),
	Dynamic(DynamicPayload),
	None
	/*
	Forward {
		src: NodeID,
		clk: status::Clock,
		typ: DynamicEvent,
		tag: String,
		msg: String
	}*/
}

#[derive(
	Clone,
	Debug,
	Serialize,
	Deserialize,
	PartialEq,
	Eq,
	EnumDiscriminants
)]
#[strum_discriminants(
	name(ProtocolPayloadType),
	derive(Serialize, Deserialize)
)]
pub enum ProtocolPayload {
	/// Synchronize the chain of [`Op`]s
	Sync {
		counters: Vec<Version>,
		ops: Vec<Op>
	},

	/// Exchange new [`Op`](s)
	Ops(Vec<Op>),

	/// Member goes inactive
	Quit
}
#[derive(
	Clone,
	Debug,
	Serialize,
	Deserialize,
	PartialEq,
	Eq,
	EnumDiscriminants
)]
#[strum_discriminants(
	name(DynamicPayloadType),
	derive(Serialize, Deserialize)
)]
pub enum DynamicPayload {
	Push {
		tag: String,
		msg: String
	},
	Query {
		tag: String,
		msg: String
	},
	Response {
		tag: String,
		msg: String,
		to: (NodeID, status::Clock)
	}
}

impl std::fmt::Debug for Packet {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		let Self {src, clk, hsh, dst, vec, png, pyl} = &self;
		write!(f, "Packet{{ {src}@{clk}#")?;
		BASE64URL_NOPAD.encode_write(&hsh.to_le_bytes(), f)?;
		write!(f, "{dst} | {vec:?} | {png:?} | {pyl:?} }}")
	}
}

impl std::ops::Not for PingOrPong {
	type Output = Self;
	fn not(self) -> Self::Output {
		match self {
			Self::Ping => Self::Pong,
			Self::Pong => Self::Ping
		}
	}
}

impl Payload {
	pub fn or(self, other: Payload) -> Payload {
		match self {
			Payload::None => other,
			_ => self
		}
	}
}
impl From<ProtocolPayload> for Payload {
	fn from(event: ProtocolPayload) -> Self {Self::Protocol(event)}
}
impl From<DynamicPayload> for Payload {
	fn from(event: DynamicPayload) -> Self {Self::Dynamic(event)}
}

impl DynamicPayload {
	pub fn borrow_tag(&self) -> &String {
		match self {
			Self::Push{tag, ..} => tag,
			Self::Query{tag, ..} => tag,
			Self::Response{tag, ..} => tag,
		}
	}
}