Documentation
use std::net::{
	SocketAddr,
	SocketAddrV4,
	SocketAddrV6
};
use ipnetwork::{
	IpNetwork,
	Ipv4Network,
	Ipv6Network
};
use serde::{Serialize, Deserialize};

use crate::peer::Address;


#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum InterfaceAddress {
	V4UdpChaCha20 {
		if_name: Option<String>,
		addr: SocketAddrV4,
		routes: Vec<Ipv4Network>
	},
	V6UdpChaCha20 {
		if_name: Option<String>,
		addr: SocketAddrV6,
		routes: Vec<Ipv6Network>
	},
	Dummy(usize)

}

#[derive(Copy, Clone, Debug, Serialize, PartialEq, Eq)]
pub enum InterfaceRoute<'a> {
	V4UdpChaCha20(&'a [Ipv4Network]),
	V6UdpChaCha20(&'a [Ipv6Network]),
	Dummy
}

/// Normalize an IpNetwork to use the network's first IP address
fn normalize_network(network: IpNetwork) -> IpNetwork {
	IpNetwork::new(network.network(), network.prefix()).unwrap()
}

impl InterfaceAddress {

	/*
	 *	ROUTER
	 */

	pub fn get_address(&self) -> Address {
		match self {
			Self::V4UdpChaCha20{addr, ..} => Address::V4UdpChaCha20(*addr),
			Self::V6UdpChaCha20{addr, ..} => Address::V6UdpChaCha20(*addr),
			Self::Dummy(addr) => Address::Dummy(*addr)
		}
	}

	pub fn get_routes(&self) -> InterfaceRoute {
		use InterfaceRoute::*;
		match self {
			Self::V4UdpChaCha20{routes, ..} => V4UdpChaCha20(routes),
			Self::V6UdpChaCha20{routes, ..} => V6UdpChaCha20(routes),
			Self::Dummy(_) => Dummy
		}
	}

	pub fn can_send(&self, address: Address) -> bool {
		match (&self, address) {
			(Self::V4UdpChaCha20{routes, ..}, Address::V4UdpChaCha20(addr)) => {
				routes
					.iter()
					.any(|r| r.contains(*addr.ip()))
			},
			(Self::V6UdpChaCha20{routes, ..}, Address::V6UdpChaCha20(addr)) => {
				routes
					.iter()
					.any(|r| r.contains(*addr.ip()))
			},
			(Self::Dummy(_), Address::Dummy(_)) => true,
			_ => false
		}
	}

	/*
	 *	LAUNCHER
	 */

	pub fn get_socket_address(&self) -> Option<SocketAddr> {
		match self {
			Self::V4UdpChaCha20{addr, ..} => Some(SocketAddr::V4(*addr)),
			Self::V6UdpChaCha20{addr, ..} => Some(SocketAddr::V6(*addr)),
			_ => None
		}
	}

	pub fn get_name(&self) -> Option<&String> {
		match self {
			Self::V4UdpChaCha20{if_name, ..} |
			Self::V6UdpChaCha20{if_name, ..} => if_name.as_ref(),
			_ => None
		}
	}
}

impl std::fmt::Display for InterfaceAddress {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match &self {
			Self::V4UdpChaCha20{addr, routes, ..} => {
				write!(f, "IPv4-UDP-ChaCha20/{addr}/[")?;
				if routes.is_empty() {
					write!(f, "no routes")?;
				}
				else {
					write!(f, "{}", routes[0])?;
					for r in routes[1..].iter() {
						write!(f, ", {r}")?
					}
				}
				write!(f, "]")
			},
			Self::V6UdpChaCha20{addr, routes, ..} => {
				write!(f, "IPv6-UDP-ChaCha20/{addr}/[")?;
				if routes.is_empty() {
					write!(f, "no routes")?;
				}
				else {
					write!(f, "{}", routes[0])?;
					for r in routes[1..].iter() {
						write!(f, ", {r}")?
					}
				}
				write!(f, "]")
			},
			Self::Dummy(addr) => write!(f, "dummy_if/{addr}")
		}
	}
}

impl std::str::FromStr for InterfaceAddress {
	type Err = String;

	fn from_str(s: &str) -> Result<Self, Self::Err> {

		let mut split = s.split_whitespace();

		let socket_addr: SocketAddr = split.next()
			.ok_or_else(|| "invalid empty interface address".to_string())?
			.parse()
			.map_err(|e| format!("failed to parse socket address: {e}"))?;

		let parse_route_spec = |route_spec: &str| -> Result<IpNetwork, String> {
			match route_spec.parse::<IpNetwork>() {
				Ok(n) if n.is_ipv4() && socket_addr.is_ipv4() => Ok(n),
				Ok(n) if n.is_ipv6() && socket_addr.is_ipv6() => Ok(n),
				Err(e) => Err(format!("invalid route spec: {e}")),
				_ => Err("invalid route spec: IP version mismatch".to_string())
			}
		};

		// "/24" is only valid for the first route_spec
		let parse_first_route_spec = |route_spec: &str| {
			if route_spec.starts_with('/') {
				let prefix: u8 = route_spec
					.split_at(1)
					.1
					.parse()
					.map_err(|e| format!("invalid route spec: {e}"))?;

				IpNetwork::new(socket_addr.ip(), prefix)
					.map_err(|e| format!("invalid route spec: {e}"))
			}
			else {
				parse_route_spec(route_spec)
			}
		};

		let routes = match split.next() {
			Some(route_spec) => {
				let mut networks = vec![
					parse_first_route_spec(route_spec)?,
				];
				for route_spec in split {
					networks.push(parse_route_spec(route_spec)?);
				}
				// Normalize the IpNetwork to use the network's first IP address
				networks
					.into_iter()
					.map(normalize_network)
					.collect()
			},
			None => Vec::new()
		};

		let if_name = None;
		match socket_addr {
			SocketAddr::V4(addr) => {
				let routes = routes.into_iter().map(|n| match n {
					IpNetwork::V4(r) => Ok(r),
					IpNetwork::V6(_) => Err(
						"IP version mismatch in parsed routes".to_string()
					)
				}).collect::<Result<_, _>>()?;
				Ok(Self::V4UdpChaCha20{addr, routes, if_name})
			},
			SocketAddr::V6(addr) => {
				let routes = routes.into_iter().map(|n| match n {
					IpNetwork::V4(_) => Err(
						"IP version mismatch in parsed routes".to_string()
					),
					IpNetwork::V6(r) => Ok(r)
				}).collect::<Result<_, _>>()?;
				Ok(Self::V6UdpChaCha20{addr, routes, if_name})
			}
		}
	}
}