Documentation
use crate::message::DynamicPayload;
use crate::event::{
	//AnyEvent,
	Event,
	DaemonEvent,
	DynamicEvent,
	PeerEvent,

	Filter,
	DynamicEventFilter,
	DynamicPayloadFilter,
	PeerEventFilter,
	PeerEventTypeFilter,

	Dispatcher,
	DispatcherSetup,
	handle::{
		Handler,
		HandlerTask
	}
};
use crate::peer::Status;

use super::*;

#[test_log::test(tokio::test(start_paused = true))]
async fn empty() {
	let span = info_span!("empty_dispatcher");

	// create dispatcher
	let DispatcherSetup {
		dispatcher,
		event_tx,
		query_tx: _,
		interest_tx: _
	} = Dispatcher::setup(&span);
	// start
	let task = task::spawn(dispatcher.run());
	// close
	drop(event_tx);
	// expect stop
	timeout_100ms(task).await.unwrap();
}


fn plain_task() -> (Task, Replier<()>) {
	let (tx, rx) = new_oneshot();
	let task = task::spawn(async {rx.await.unwrap()});
	(task, tx)
}

fn launch_plain_handler<E: Event>()
	-> (HandlerTask<E>, Replier<()>, Receiver<E>)
{
	let (plain_tx, plain_rx) = new_channel();
	let plain = Handler::Plain {
		name: "plain_handler".to_owned(),
		filter: None,
		channel: plain_tx
	};

	let (task, task_stopper) = plain_task();
	(
		HandlerTask::new(plain, task),
		task_stopper,
		plain_rx
	)
}

#[test_log::test(tokio::test(start_paused = true))]
async fn dispatch() {
	let span = info_span!("empty_dispatcher");

	// create dispatcher, run
	let DispatcherSetup {
		dispatcher,
		event_tx,
		query_tx: _,
		interest_tx
	} = Dispatcher::setup(&span);
	let task = task::spawn(dispatcher.run());

	// create "Plain" Handler
	let (handler, _stopper, mut rx) = launch_plain_handler::<DaemonEvent>();
	interest_tx.send(Interest::Daemon(handler)).unwrap();

	//time for the handler to get registered
	sleep_ms(10).await;

	// send event, assert delivered
	let evt = DaemonEvent::Start;
	event_tx.send(evt.clone().into()).unwrap();

	let received = timeout_100ms(rx.recv()).await.unwrap();

	assert_eq!(evt, received);

	// close
	drop(event_tx);
	// expect stop
	timeout_100ms(task).await.unwrap();
}

#[test_log::test]
fn daemon_filter() {
	// create events
	let daemon1 = DaemonEvent::Start;
	let daemon2 = DaemonEvent::Inactive;

	// create filters
	let only_start = Some(DaemonEvent::Start);
	let only_inactive = Some(DaemonEvent::Inactive);

	// check filter.matches(event)
	assert!(only_start.matches(&daemon1));
	assert!(!only_inactive.matches(&daemon1));

	assert!(!only_start.matches(&daemon2));
	assert!(only_inactive.matches(&daemon2));
}

#[test_log::test]
fn dyn_filter() {
	let id_correct = NodeID::random();
	let id_wrong = NodeID::random();

	assert_ne!(id_correct, id_wrong, "lottery win!");

	let tag_correct = || "test.tag1".to_owned();
	let tag_wrong = || "test.tag2".to_owned();

	let pyl1 = DynamicPayload::Push {
		tag: tag_correct(),
		msg: "message payload Test123 :^)".to_owned()
	};
	let pyl2 = DynamicPayload::Push {
		tag: tag_wrong(),
		msg: "message payload Test123 :^)".to_owned()
	};
	let pyl3 = DynamicPayload::Query {
		tag: tag_correct(),
		msg: "message payload Test123 :^)".to_owned()
	};

	// create events
	let dyn1 = DynamicEvent {
		src: id_correct,
		pyl: pyl1.clone()
	};
	let dyn2: DynamicEvent = (id_correct, pyl2).into();
	let dyn3: DynamicEvent = (id_wrong, pyl1).into();
	let dyn4: DynamicEvent = (id_correct, pyl3).into();

	// create filters
	let check_id = Some(id_correct);
	let check_tag = || Some(tag_correct());


	let only_push = DynamicPayloadFilter::Push;
	let only_query = DynamicPayloadFilter::Query;

	let dyn_filter1 = DynamicEventFilter::simple(
		check_id,
		check_tag()
	);
	let dyn_filter2 = DynamicEventFilter {
		from: check_id,
		with: None,
		typ: only_push
	};
	let dyn_filter3 = DynamicEventFilter {
		from: check_id,
		with: None,
		typ: only_query
	};
	let dyn_filter4 = DynamicEventFilter::simple(None, None);

	// check filter.matches(event)

	assert!(dyn_filter1.matches(&dyn1), "id & tag correct");
	assert!(!dyn_filter1.matches(&dyn2), "id correct, tag wrong");
	assert!(!dyn_filter1.matches(&dyn3), "id wrong, tag correct");
	assert!(dyn_filter1.matches(&dyn4), "id & tag correct");

	assert!(dyn_filter2.matches(&dyn1), "id & typ correct");
	assert!(dyn_filter2.matches(&dyn2), "id & typ correct, tag ignored");
	assert!(!dyn_filter2.matches(&dyn3), "id wrong, typ correct");
	assert!(!dyn_filter2.matches(&dyn4), "id correct, typ wrong");

	assert!(!dyn_filter3.matches(&dyn1), "id correct, typ wrong");
	assert!(!dyn_filter3.matches(&dyn2), "id correct, typ wrong");
	assert!(!dyn_filter3.matches(&dyn3), "id & typ wrong");
	assert!(dyn_filter3.matches(&dyn4), "id & typ correct, tag ignored");

	assert!(dyn_filter4.matches(&dyn1), "matches everything");
	assert!(dyn_filter4.matches(&dyn2), "matches everything");
	assert!(dyn_filter4.matches(&dyn3), "matches everything");
	assert!(dyn_filter4.matches(&dyn4), "matches everything");
}

#[test_log::test]
fn peer_filter() {
	let id1 = NodeID::random();
	let id2 = NodeID::random();

	assert_ne!(id1, id2, "lottery win!");

	// create events
	let peer1 = PeerEvent::changed(id1, Status::Active);
	let peer2 = PeerEvent::new_address(id1, dummy_address(0));

	// create filters
	let only_id1 = Some(id1);
	let only_id2 = Some(id2);

	let only_seen = PeerEventTypeFilter::Seen;
	let only_status = PeerEventTypeFilter::StatusChanged;

	let peer_filter1 = PeerEventFilter {
		from: only_id1,
		typ: only_status
	};
	let peer_filter2 = PeerEventFilter {
		from: only_id2,
		typ: only_seen
	};
	let peer_filter3 = PeerEventFilter {
		from: None,
		typ: only_status
	};
	let peer_filter4 = PeerEventFilter {
		from: None,
		typ: PeerEventTypeFilter::Any
	};

	// check filter.matches(event)
	assert!(peer_filter1.matches(&peer1), "correct id and type");
	assert!(!peer_filter1.matches(&peer2), "correct id but wrong type");

	assert!(!peer_filter2.matches(&peer1), "wrong id and type");
	assert!(!peer_filter2.matches(&peer2), "wrong id and type");

	assert!(peer_filter3.matches(&peer1), "id ignored, correct type");
	assert!(!peer_filter3.matches(&peer2), "id ignored, wrong type");

	assert!(peer_filter4.matches(&peer1), "matches everything");
	assert!(peer_filter4.matches(&peer2), "matches everything");
}