// Copyright 2021-2022 Ian Jackson and contributors to Hippotat
// SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception
// There is NO WARRANTY.

use crate::prelude::*;

#[derive(Debug,Copy,Clone)]
pub enum LinkEnd { Server, Client }

#[derive(Debug,Clone,Hash,Eq,PartialEq,Ord,PartialOrd)]
pub struct ServerName(pub String);

#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd)]
pub struct ClientName(pub IpAddr);

#[derive(Clone,Hash,Eq,PartialEq,Ord,PartialOrd)]
pub struct LinkName {
  pub server: ServerName,
  pub client: ClientName,
}

impl Debug for LinkName {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "LinkName({})", self)?; }
}

impl FromStr for ClientName {
  type Err = AE;
  #[throws(AE)]
  fn from_str(s: &str) -> Self {
    ClientName(
      if let Ok(v4addr) = s.parse::<Ipv4Addr>() {
        if s != v4addr.to_string() {
          throw!(anyhow!("invalid client name (unusual IPv4 address syntax)"));
        }
        v4addr.into()
      } else if let Ok(v6addr) = s.parse::<Ipv6Addr>() {
        if s != v6addr.to_string() {
          throw!(anyhow!("invalid client name (non-canonical IPv6 address)"));
        }
        v6addr.into()
      } else {
        throw!(anyhow!("invalid client name (IPv4 or IPv6 address)"))
      }
    )
  }
}

impl FromStr for ServerName {
  type Err = AE;
  #[throws(AE)]
  fn from_str(s: &str) -> Self {
    if ! regex_is_match!(r"
        ^ (?: SERVER
            | [0-9a-z][-0-9a-z]* (:? \.
              [0-9a-z][-0-9a-z]*        )*
          ) $"x, s) {
      throw!(anyhow!("bad syntax for server name"));
    }
    if ! regex_is_match!(r"[A-Za-z-]", s) {
      throw!(anyhow!("bad syntax for server name \
                      (too much like an IPv4 address)"));
    }
    ServerName(s.into())
  }
}

impl Display for ServerName {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.0, f)?; }
}
impl Display for ClientName {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) { Display::fmt(&self.0, f)?; }
}
impl Display for LinkName {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) {
    write!(f, "[{} {}]", &self.server, &self.client)?;
  }
}

impl_inspectable_config_value!{ LinkName as Display }
