Documentation
use tracing::Span;
use tokio::{
	sync::broadcast,
	task
};

use crate::message::Packet;
use crate::peer::Address;
use crate::util::*;
use super::InterfaceAddress;
use super::chacha20;


#[derive(Debug)]
pub struct Interface {
	address: InterfaceAddress,
	channel: Option<Sender<PktTo>>
}

pub enum InterfaceLauncher {
	Chacha20Udp(chacha20::Server),
	Dummy(DummyServer)
}

pub enum Terminated {
	Shutdown,
	Crashed(String),
	Panic
}

#[derive(Clone, Debug)]
struct PktFromTo {
	pub to: Address,
	pub from: Address,
	pub pkt: Packet
}

pub struct DummyServer {
	address: usize,
	pkt_bus: broadcast::Sender<PktFromTo>
}


/*
 *	INTERFACE LAUNCHER
 */

impl std::fmt::Debug for InterfaceLauncher {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Chacha20Udp(server) => {
				write!(f, "Chacha20Udp @ {}", server.get_address())
			},
			Self::Dummy(server) => {
				write!(f, "DummyServer @ {}", server.get_address())
			}
		}
	}
}

impl InterfaceLauncher {

	pub fn get_address(&self) -> InterfaceAddress {
		match self {
			Self::Chacha20Udp(server) => server.get_address(),
			Self::Dummy(server) => server.get_address()
		}
	}

	pub fn spawn(
		self,
		channel: ServerChannel,
		span: &Span)
		-> task::JoinHandle<Terminated>
	{
		match self {
			Self::Chacha20Udp(server) => {
				let address = server.get_address();
				trace!(:span, %address, "spawned chacha20 task");
				task::spawn(server.launch(channel))
			},
			Self::Dummy(server) => {
				let address = server.get_address();
				trace!(:span, %address, "spawned dummy server task");
				task::spawn(server.run(channel))
			}
		}
	}
}


/*
 *	INTERFACE
 */

impl Interface {
	pub fn new(address: InterfaceAddress, channel: Sender<PktTo>) -> Self {
		Self {address, channel: Some(channel)}
	}
	pub fn shutdown(&mut self) {
		self.channel.take();
	}

	pub fn replace_address(&mut self, address: InterfaceAddress) {
		self.address = address;
	}

	pub fn get_address(&self) -> Address {
		self.address.get_address()
	}

	pub fn get_interface_address(&self) -> InterfaceAddress {
		self.address.clone()
	}

	pub fn can_send(&self, address: Address) -> bool {
		self.address.can_send(address)
	}

	pub fn send(&self, pkt: PktTo) {
		if let Some(channel) = self.channel.as_ref() {
			let _ = channel.send(pkt);
		}
	}
}

impl std::fmt::Display for Interface {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		self.address.fmt(f)
	}
}


/*
 *	DUMMY SERVER
 */

impl DummyServer {

	async fn observe(mut pkt_bus: broadcast::Receiver<PktFromTo>) {
		while let Ok(pkt) = pkt_bus.recv().await {
			println!("{}{} : {:?}", pkt.from, pkt.to, pkt.pkt);
		}
	}

	pub fn create_n(n: usize)
		-> (Vec<Self>, task::JoinHandle<()>)
	{
		let dummy_bus = broadcast::channel::<PktFromTo>(1024).0;

		let dummies = (0..n)
			.map(|address| Self { address, pkt_bus: dummy_bus.clone()})
			.collect();
		let observer_handle = task::spawn(
			Self::observe(dummy_bus.subscribe())
		);
		(dummies, observer_handle)
	}


	pub fn get_address(&self) -> InterfaceAddress {
		InterfaceAddress::Dummy(self.address)
	}

	pub async fn run(self, channel: ServerChannel) -> Terminated {

		let (inbound_pkt_tx, mut outbound_pkt_rx) = channel.split();
		let mut bus_receiver = self.pkt_bus.subscribe();

		println!("dummy {} running", self.address);

		loop { tokio::select! {
			r = bus_receiver.recv() => { match r {
				Ok(pkt) if pkt.to == Address::Dummy(self.address) => {
					println!("{} received pkt from {}", pkt.to, pkt.from);
					let _ = inbound_pkt_tx.send(Self::strip_to(pkt));
				},
				Ok(pkt) => {
					println!(
						"{} ignoring pkt for {} from {}",
						self.address,
						pkt.to,
						pkt.from
					);
				},
				// TODO: only break if RecvError::Closed, continue on Lagged()
				Err(_) => break,
			}},
			p = outbound_pkt_rx.recv() => { match p {
				Some(p) => {
					let _ = self.pkt_bus.send(self.add_from(p));
				},
				None => break
			}}
		}}

		Terminated::Shutdown
	}

	fn add_from(&self, pkt_to: PktTo) -> PktFromTo {
		PktFromTo {
			from: Address::Dummy(self.address),
			to: pkt_to.0,
			pkt: pkt_to.1
		}
	}

	fn strip_to(pkt_from_to: PktFromTo) -> PktFrom {
		(pkt_from_to.from, pkt_from_to.pkt)
	}

}