Documentation
use serde::{Serialize, Deserialize};
use strum::{
	EnumDiscriminants,
	EnumString
};
use tokio::process::Command;

use crate::message::{
	DynamicPayload,
	DynamicPayloadType,
	PingOrPong,
	Packet
};
use crate::peer::{
	Address,
	Clock,
	NodeID,
	status::Status
};

/*
 *	TRAITS
 */

pub trait Event {
	type Filter: Filter<Self>;
	const NAME: &'static str;

	fn into_envs(self, cmd: &mut Command);
}

pub trait Filter<E: Event + ?Sized> {
	fn matches(&self, evt: &E) -> bool;
	//TODO:
	//fn describe(&self) -> String;
}

/*
 *	EVENTS
 */

#[derive(Clone, Debug, EnumDiscriminants)]
#[strum_discriminants(name(EventType), derive(Serialize, Deserialize))]
pub enum AnyEvent {
	Dynamic(DynamicEvent),
	Peer(PeerEvent),
	Daemon(DaemonEvent)
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct DynamicEvent {
	pub src: NodeID,
	pub pyl: DynamicPayload
}

#[derive(Clone, Debug)]
pub struct PeerEvent {
	pub id: NodeID,
	pub evt: PeerEventContent
}

#[derive(Clone, Debug, EnumString, PartialEq, Eq)]
#[strum(serialize_all = "snake_case")]
pub enum DaemonEvent {
	Start,
	Stop,
	Active,
	Inactive
}

#[derive(Clone, Debug)]
pub enum PeerEventContent {
	//TODO: "Created / Added / Joined" ?
	//TODO: "Loaded {name: String}"!
	/// Received a message from this peer
	Seen {
		clock: Clock,
		png: PingOrPong,
	},
	/// Received a message from this peer from a new address
	NewAddress(Address),
	/// Peer status has changed
	StatusChanged(Status)
}

/*
 *	FILTERS
 */


pub type DaemonEventFilter = Option<DaemonEvent>;
pub type TagFilter = Option<String>;
pub type NodeFilter = Option<NodeID>;

#[derive(Debug)]
pub struct DynamicEventFilter {
	pub from: NodeFilter,
	pub with: TagFilter,
	pub typ: DynamicPayloadFilter
}

#[derive(Debug)]
pub struct PeerEventFilter {
	pub from: NodeFilter,
	pub typ: PeerEventTypeFilter
}

#[derive(Copy, Clone, Debug)]
pub enum DynamicPayloadFilter {
	Any,
	Push,
	Query,
	AnyResponse,
	Response(Clock)
}

//TODO: AnyStatusChanged, StatusChanged(Status) 
#[derive(Copy, Clone, Debug)]
pub enum PeerEventTypeFilter {
	Any,
	Seen,
	NewAddress,
	StatusChanged
}

/*
 *	EVENTS
 */


impl PeerEvent {
	pub fn seen(pkt: &Packet) -> Self {
		Self {
			id: pkt.src,
			evt: PeerEventContent::Seen {
				clock: pkt.clk,
				png: pkt.png
			}
		}
	}
	pub fn new_address(id: NodeID, address: Address) -> Self {
		let evt = PeerEventContent::NewAddress(address);
		Self {id, evt}
	}
	pub fn changed(id: NodeID, status: Status) -> Self {
		let evt = PeerEventContent::StatusChanged(status);
		Self {id, evt}
	}
}

impl Event for DynamicEvent {
	type Filter = DynamicEventFilter;
	const NAME: &'static str = "dynamic";

	fn into_envs(self, cmd: &mut Command) {
		//TODO: Response Clock ???
		let typ: DynamicPayloadType = (&self.pyl).into();
		let (DynamicPayload::Push{tag, msg} |
			DynamicPayload::Query{tag, msg} |
			DynamicPayload::Response{tag, msg, ..}) = self.pyl;

		cmd.envs([
			("CNSPRCY_EVENT_SRC", self.src.to_string()),
			("CNSPRCY_EVENT_DYN", format!("{typ:?}")),
			("CNSPRCY_EVENT_TAG", tag),
			("CNSPRCY_EVENT_MSG", msg)
		]);
	}
}
impl From<(NodeID, DynamicPayload)> for DynamicEvent {
	fn from((src, pyl): (NodeID, DynamicPayload)) -> Self {Self{src, pyl}}
}

impl Event for PeerEvent {
	type Filter = PeerEventFilter;
	const NAME: &'static str = "peer";

	fn into_envs(self, cmd: &mut Command) {
		cmd.envs([
			("CNSPRCY_PEER_ID", self.id.to_string()),
			("CNSPRCY_PEER_UPDATE", format!("{:?}", self.evt))
		]);
	}
}

impl Event for DaemonEvent {
	type Filter = DaemonEventFilter;
	const NAME: &'static str = "daemon";

	fn into_envs(self, cmd: &mut Command) {
		cmd.env("CNSPRCY_DAEMON", format!("{self:?}"));
	}
}

impl From<DynamicEvent> for AnyEvent {
	fn from(event: DynamicEvent) -> Self {Self::Dynamic(event)}
}
impl From<(NodeID, DynamicPayload)> for AnyEvent {
	fn from((src, evt): (NodeID, DynamicPayload)) -> Self {
		Self::Dynamic(DynamicEvent{src, pyl: evt})
	}
}
impl From<PeerEvent> for AnyEvent {
	fn from(event: PeerEvent) -> Self {Self::Peer(event)}
}
impl From<DaemonEvent> for AnyEvent {
	fn from(event: DaemonEvent) -> Self {Self::Daemon(event)}
}


/*
 *	FILTERS
 */

impl<E: Event, F: Filter<E>> Filter<E> for Option<F> {
	fn matches(&self, event: &E) -> bool {
		self
			.as_ref()
			.map(|filter| filter.matches(event))
			.unwrap_or(true)
	}
}

impl Filter<DaemonEvent> for DaemonEventFilter {
	fn matches(&self, event: &DaemonEvent) -> bool {
		self
			.as_ref()
			.map(|evt| evt == event)
			.unwrap_or(true)
	}
}

// TODO: Maybe avoid matching the payload twice (borrow_tag, typ.matches)
impl Filter<DynamicEvent> for DynamicEventFilter {
	fn matches(&self, event: &DynamicEvent) -> bool {
		self.from
			.map(|id| id == event.src)
			.unwrap_or(true)
		&& self.with
			.as_ref()
			.map(|tag| tag == event.pyl.borrow_tag())
			.unwrap_or(true)
		&& self.typ
			.matches(&event.pyl)
	}
}

impl DynamicPayloadFilter {
	pub fn matches(&self, dyn_payload: &DynamicPayload) -> bool {
		use DynamicPayload::*;
		match (self, dyn_payload) {
			(Self::Any, _) => true,
			(Self::Push, Push{..}) => true,
			(Self::Query, Query{..}) => true,
			(Self::AnyResponse, Response{..}) => true,
			(Self::Response(a), Response{to: (_, b), ..}) => a == b,
			_ => false
		}
	}
}

impl Filter<PeerEvent> for PeerEventFilter {
	fn matches(&self, event: &PeerEvent) -> bool {
		self.from
			.map(|id| id == event.id)
			.unwrap_or(true)
		&& self.typ
			.matches(&event.evt)

	}
}
impl PeerEventTypeFilter {
	pub fn matches(&self, peer_event: &PeerEventContent) -> bool {
		use PeerEventContent::*;
		matches!((self, peer_event),
			(Self::Any, _) |
			(Self::Seen, Seen{..}) |
			(Self::NewAddress, NewAddress(_)) |
			(Self::StatusChanged, StatusChanged(_))
		)
	}
}

impl DynamicEventFilter {
	pub fn simple(from: NodeFilter, with: TagFilter) -> Self {
		Self {
			from,
			with,
			typ: DynamicPayloadFilter::Any
		}
	}
}

impl PeerEventFilter {
	pub fn new(from: NodeFilter, typ: PeerEventTypeFilter) -> Self {
		Self {from, typ}
	}
}