Documentation
use std::collections::HashMap;

use conciliator::{
	Print,
	Color,
	Paint,
	Conciliator
};
use serde::{Serialize, Deserialize};

use crate::event;
use crate::message;
use crate::net::{
	InterfaceAddress,
	interface::InterfaceLauncher
};
use crate::peer::{
	Address,
	Invitation,
	Status,
};
use crate::peer::link::{
	Link,
	LinkState
};
use crate::util::*;
use super::{
	KeyArg,
	NodeArg,
	NodeIDArg
};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CnsprcyStatus {
	pub id: NodeIDArg,
	pub name: String,
	pub addrs: Vec<InterfaceAddress>,
	pub handlers: Vec<String>,
	pub conspirators: Vec<Conspirator>
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Conspirator {
	pub id: NodeIDArg,
	pub name: String,
	pub state: Status,
	pub link: Option<Link>
}

#[derive(Debug)]
pub struct NodeJoinRequest {
	pub id: NodeIDArg,
	pub name: String,
	pub addr: Address
}

/*
 *	INTERNAL
 */

#[derive(Debug)]
pub enum AnyRequest {
	Daemon(DaemonReq),
	Node(NodeReq),
	Mirror(MirrorReq),
	Router(RouterReq),
	Dispatcher(DispatcherReq)
}

#[derive(Debug)]
pub enum DaemonReq {
	GetStatus(
		GetRequest<CnsprcyStatus>
	),
	StopServer(
		GetRequest<bool>
	),
	Invite(
		Request<KeyArg, Invitation>
	),
	Accept(
		Request<Invitation, bool>
	)
}

#[derive(Debug)]
pub enum NodeReq {
	Write(
		Request<(String, String), bool>
	),
	AddAddress(
		Request<(NodeArg, Address), bool>
	),
	Advertise(
		GetRequest<KeyArg>
	),
	Invite(
		Request<(KeyArg, Vec<Address>), Invitation>
	),
	Disable(
		Request<NodeArg, bool>
	),
	Join(
		Request<NodeJoinRequest, bool>
	),
	GetConspirator(
		Request<NodeArg, Conspirator>
	),
	GetConspirators(
		GetRequest<Vec<Conspirator>>
	),
	SendPayload(
		Request<(NodeArg, message::Payload, Option<Address>), bool>
	),
	GetStatus(
		Request<(Vec<InterfaceAddress>, Vec<String>), CnsprcyStatus>
	)
}

#[derive(Debug)]
pub enum DispatcherReq {
	GetHandlers(
		GetRequest<Vec<String>>
	),
	Dispatch(
		Request<event::DynamicEvent, bool>
	),
	//Enable(String),
	//Disable(String)
}

#[derive(Debug)]
pub enum RouterReq {
	GetInterfaces(
		GetRequest<Vec<InterfaceAddress>>
	),
	CanSend(
		Request<Address, bool>
	),
	BindInterface(
		Request<InterfaceAddress, bool>
	),
	LaunchInterface(
		Request<InterfaceLauncher, bool>
	),
	Rekey(
		Request<KeyArg, bool>
	),
	RemoveInterface(
		Request<Address, bool>
	)
}


#[derive(Debug)]
pub enum MirrorReq {
	Enable(Request<String, bool>),
	Disable(Request<String, bool>),
}

impl From<DaemonReq> for AnyRequest {
	fn from(req: DaemonReq) -> Self {Self::Daemon(req)}
}
impl From<NodeReq> for AnyRequest {
	fn from(req: NodeReq) -> Self {Self::Node(req)}
}
impl From<MirrorReq> for AnyRequest {
	fn from(req: MirrorReq) -> Self {Self::Mirror(req)}
}
impl From<RouterReq> for AnyRequest {
	fn from(req: RouterReq) -> Self {Self::Router(req)}
}
impl From<DispatcherReq> for AnyRequest {
	fn from(req: DispatcherReq) -> Self {Self::Dispatcher(req)}
}

impl Print for &Conspirator {
	fn print<C: Conciliator + ?Sized>(self, con: &C) {
		let mut buffer = con.get_line();
		buffer
			.push_bold("[")
			.push_omega_bold(&self.id)
			.push_bold("]")
			.push(' ')
			.push_alpha(&format_args!("{:<9}", &self.name))
			.push(' ');
		if self.link.is_none() && self.state == Status::Unreachable {
			buffer.push_iota_bold("offline");
		}
		else {
			buffer.push_beta_bold(&format_args!("{:^11?} ", self.state));
			if let Some(link) = self.link.as_ref() {
				if let Some(seen) = link.seen {
					buffer
						.push("\n  ")
						.push_omega(tree::KNOT)
						.push("seen ");
					push_time(&mut buffer, seen);
				}
				if let LinkState::BeenPinged(_) = link.state {
					if let Some(pinged) = link.pinged {
						buffer
							.push("\n  ")
							.push_omega(tree::KNOT)
							.push("pinged ");
						push_time(&mut buffer, pinged);
					}
				}
				buffer
					.push("\n  ")
					.push_omega(tree::TAIL)
					.push(link.address);
			}
		}
	}
}

impl Print for &CnsprcyStatus {
	fn print<C: Conciliator + ?Sized>(self, con: &C) {
		let CnsprcyStatus {id, name, addrs, handlers, conspirators} = self;

		let tag_color = match addrs.is_empty() {
			true => Color::Iota,
			false => Color::Alpha,
		};

		let mut header = con.get_line();
		header
			.tag(tag_color, "CNSPRCY")
			.push('\t')
			.push_alpha(name)
			.push('\t')
			.push_bold("[")
			.push_omega_bold(id)
			.push_bold("]");
		drop(header);

		// Interfaces list

		// first sort interfaces by name of the network device
		let mut named: HashMap<&str, Vec<&InterfaceAddress>> = HashMap::new();
		let mut unnamed = Vec::new();

		for addr in addrs {
			if let Some(name) = addr.get_name() {
				named.entry(name)
					.or_default()
					.push(addr);
			}
			else {
				unnamed.push(addr)
			};
		}

		let last_idx = unnamed.is_empty().then_some(
			named.len().saturating_sub(1)
		);

		for (i, (name, addrs)) in named.into_iter().enumerate() {
			con.get_line()
				.push("  ")
				.push_omega(
					if last_idx == Some(i) {tree::TAIL} else {tree::KNOT}
				)
				.push_zeta_bold(name);

			for (j, addr) in addrs.iter().enumerate() {
				let Some(ip) = addr.get_socket_address().map(|a| a.ip()) else {
					continue;
				};
				con.get_line()
					.push("  ")
					.push_omega(
						if last_idx == Some(i) {"  "} else {tree::STEM}
					)
					.push_omega(
						if j == addrs.len() - 1 {tree::TAIL} else {tree::KNOT}
					)
					.push_beta(&ip);
			}
		}

		// print interfaces without names beneath
		match &unnamed[..] {
			[] if addrs.is_empty() => {
				con.get_line()
					.push("  ")
					.push_omega(tree::TAIL)
					.push("< no interfaces >");
			},
			[] => {},
			[addrs @ .., last] => {
				for addr in addrs {
					con.get_line()
						.push("  ")
						.push_omega(tree::KNOT)
						.push(addr);
				}
				con.get_line()
					.push("  ")
					.push_omega(tree::TAIL)
					.push(last);
			}
		}

		// Conspirators
		for c in conspirators {
			c.print(con);
		}

		// Handlers list
		match &handlers[..] {
			[] => {
				con.get_line().push("No event handlers registered");
			},
			[handlers @ .., last] => {
				con.get_line().push("Event handlers:");
				for handler in handlers {
					con.get_line()
						.push("  ")
						.push_omega(tree::KNOT)
						.push(handler);
				}
				con.get_line()
					.push("  ")
					.push_omega(tree::TAIL)
					.push(last);
			}
		}
	}
}