//! Bitmessage network address types.

use std::{
    convert::{TryFrom, TryInto},
    fmt,
    io::{self, Cursor, Read, Write},
    net::{
        AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr as StdSocketAddr, SocketAddrV4,
        SocketAddrV6,
    },
    num::ParseIntError,
    str::FromStr,
};

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use koibumi_base32 as base32;
#[cfg(feature = "koibumi-socks")]
use koibumi_socks::{Addr as SocksAddr, DomainName, SocketAddr as SocksSocketAddr};

use crate::{
    hash::sha3_256,
    io::{ReadFrom, WriteTo},
};

/// An Onion V2 address.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct OnionV2Addr {
    bytes: [u8; 10],
}

impl OnionV2Addr {
    const STR_LEN: usize = 22;

    /// Constructs an Onion V2 address from a byte array.
    pub fn new(bytes: [u8; 10]) -> Self {
        Self { bytes }
    }
}

impl fmt::Display for OnionV2Addr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = base32::encode(self.bytes).unwrap();
        s.push_str(".onion");
        s.fmt(f)
    }
}

impl AsRef<[u8]> for OnionV2Addr {
    fn as_ref(&self) -> &[u8] {
        &self.bytes
    }
}

impl From<[u8; 10]> for OnionV2Addr {
    fn from(bytes: [u8; 10]) -> Self {
        Self { bytes }
    }
}

#[cfg(feature = "koibumi-socks")]
impl From<OnionV2Addr> for DomainName {
    fn from(addr: OnionV2Addr) -> Self {
        addr.to_string().parse().unwrap()
    }
}

/// An error which can be returned when parsing an Onion V2 address.
///
/// This error is used as the error type for the `FromStr` implementation
/// for [`OnionV2Addr`].
/// This error is also used as a component of the error type
/// [`ParseAddrExtError`].
///
/// [`OnionV2Addr`]: struct.OnionV2Addr.html
/// [`ParseAddrExtError`]: enum.ParseAddrExtError.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseOnionV2AddrError {
    /// The length of the input string was invalid.
    /// The expected and the actual lengths of the input are
    /// returned as payloads of this variant.
    InvalidLength {
        /// The expected length.
        expected: usize,
        /// The actual length.
        actual: usize,
    },

    /// The input string did not end with ".onion".
    InvalidTld,

    /// The encoding of the input string was invalid.
    /// The actual error caught is returned as a payload of this variant.
    InvalidEncoding(base32::DecodeError),
}

impl fmt::Display for ParseOnionV2AddrError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidLength { expected, actual } => {
                write!(f, "length should be {}, but {}", expected, actual)
            }
            Self::InvalidTld => write!(f, "Onion address must end with \".onion\""),
            Self::InvalidEncoding(err) => err.fmt(f),
        }
    }
}

impl std::error::Error for ParseOnionV2AddrError {}

impl FromStr for OnionV2Addr {
    type Err = ParseOnionV2AddrError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.as_bytes().len() != Self::STR_LEN {
            return Err(Self::Err::InvalidLength {
                expected: Self::STR_LEN,
                actual: s.as_bytes().len(),
            });
        }
        if !s.ends_with(".onion") {
            return Err(Self::Err::InvalidTld);
        }
        let body = String::from_utf8(s.as_bytes()[0..16].to_vec()).unwrap();
        match base32::decode(&body) {
            Ok(b) => Ok(Self {
                bytes: b[..].try_into().unwrap(),
            }),
            Err(err) => Err(Self::Err::InvalidEncoding(err)),
        }
    }
}

#[test]
fn test_onion_v2_addr_parse() {
    let test: OnionV2Addr = "aerukz4jvpg677w4.onion".parse().unwrap();
    let expected = OnionV2Addr::new([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc]);
    assert_eq!(test, expected);
}

#[test]
fn test_onion_v2_addr_to_string() {
    let test =
        &OnionV2Addr::new([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc]).to_string();
    let expected = "aerukz4jvpg677w4.onion";
    assert_eq!(test, expected);
}

/// An Onion V3 address.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct OnionV3Addr {
    bytes: Vec<u8>, // [u8; 35]
}

/// An error which can be returned when parsing an Onion V3 address.
///
/// This error is used as the error type for the `FromStr` implementation
/// for [`OnionV3Addr`].
/// This error is also used as a component of the error type
/// [`ParseAddrExtError`].
///
/// [`OnionV3Addr`]: struct.OnionV3Addr.html
/// [`ParseAddrExtError`]: enum.ParseAddrExtError.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseOnionV3AddrError {
    /// The length of the input string was invalid.
    /// The expected and the actual lengths of the input are
    /// returned as payloads of this variant.
    InvalidLength {
        /// The expected length.
        expected: usize,
        /// The actual length.
        actual: usize,
    },

    /// The input string did not end with ".onion".
    InvalidTld,

    /// The encoding of the input string was invalid.
    /// The actual error caught is returned as a payload of this variant.
    InvalidEncoding(base32::DecodeError),

    /// The checksums did not match.
    /// The expected and the actual checksums are returned
    /// as payloads of this variant.
    InvalidChecksum {
        /// The expected checksum.
        expected: [u8; 2],
        /// The actual checksum.
        actual: [u8; 2],
    },

    /// The version number of the input was invalid.
    /// The actual version number supplied is returned
    /// as a payload of this variant.
    InvalidVersion(u8),
}

impl fmt::Display for ParseOnionV3AddrError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidLength { expected, actual } => {
                write!(f, "length should be {}, but {}", expected, actual)
            }
            Self::InvalidTld => write!(f, "Onion address must end with \".onion\""),
            Self::InvalidEncoding(err) => err.fmt(f),
            Self::InvalidChecksum { expected, actual } => write!(
                f,
                "checksum should be {:#02x}{:02x}, but {:#02x}{:02x}",
                expected[0], expected[1], actual[0], actual[1]
            ),
            Self::InvalidVersion(version) => {
                write!(f, "version must be 0x03, but {:#02x}", version)
            }
        }
    }
}

impl std::error::Error for ParseOnionV3AddrError {}

impl OnionV3Addr {
    const STR_LEN: usize = 62;

    /// Constructs an Onion V3 address from a byte array.
    pub fn new(bytes: [u8; 35]) -> Result<Self, ParseOnionV3AddrError> {
        let addr = Self {
            bytes: bytes.to_vec(),
        };
        addr.validate()?;
        Ok(addr)
    }

    fn validate(&self) -> Result<(), ParseOnionV3AddrError> {
        let pubkey = &self.bytes[0..32];
        let checksum = &self.bytes[32..34];
        let version = self.bytes[34];

        let mut bytes = Vec::with_capacity(15 + 32 + 1);
        bytes.extend_from_slice(b".onion checksum");
        bytes.extend_from_slice(pubkey);
        bytes.push(version);

        let c = &sha3_256(&bytes)[0..2];
        if c != checksum {
            return Err(ParseOnionV3AddrError::InvalidChecksum {
                expected: checksum.try_into().unwrap(),
                actual: c.try_into().unwrap(),
            });
        }

        if version != 0x03 {
            return Err(ParseOnionV3AddrError::InvalidVersion(version));
        }

        Ok(())
    }
}

impl fmt::Display for OnionV3Addr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = base32::encode(&self.bytes).unwrap();
        s.push_str(".onion");
        s.fmt(f)
    }
}

impl AsRef<[u8]> for OnionV3Addr {
    fn as_ref(&self) -> &[u8] {
        &self.bytes
    }
}

impl From<[u8; 35]> for OnionV3Addr {
    fn from(bytes: [u8; 35]) -> Self {
        Self {
            bytes: bytes.to_vec(),
        }
    }
}

#[cfg(feature = "koibumi-socks")]
impl From<OnionV3Addr> for DomainName {
    fn from(addr: OnionV3Addr) -> Self {
        addr.to_string().parse().unwrap()
    }
}

impl FromStr for OnionV3Addr {
    type Err = ParseOnionV3AddrError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.as_bytes().len() != Self::STR_LEN {
            return Err(Self::Err::InvalidLength {
                expected: Self::STR_LEN,
                actual: s.as_bytes().len(),
            });
        }
        if !s.ends_with(".onion") {
            return Err(Self::Err::InvalidTld);
        }
        let body = String::from_utf8(s.as_bytes()[0..56].to_vec()).unwrap();
        match base32::decode(&body) {
            Ok(b) => {
                let mut bytes = [0; 35];
                bytes.copy_from_slice(&b);
                Ok(Self::new(bytes)?)
            }
            Err(err) => Err(Self::Err::InvalidEncoding(err)),
        }
    }
}

#[test]
fn test_onion_v3_addr_parse() {
    let test: OnionV3Addr = "aerukz4jvpg677w4xkmhmvbscaabcirtirkwm54itgvlxtg5537uyyad.onion"
        .parse()
        .unwrap();
    let expected = OnionV3Addr::new([
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
        0x10, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
        0xee, 0xff, 0x4c, 0x60, 0x03,
    ])
    .unwrap();
    assert_eq!(test, expected);
}

#[test]
fn test_onion_v3_addr_to_string() {
    let test = OnionV3Addr::new([
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
        0x10, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
        0xee, 0xff, 0x4c, 0x60, 0x03,
    ])
    .unwrap()
    .to_string();
    let expected = "aerukz4jvpg677w4xkmhmvbscaabcirtirkwm54itgvlxtg5537uyyad.onion";
    assert_eq!(test, expected);
}

#[test]
fn test_onion_v3_addr_validate() {
    // Example address from https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt
    let _test: OnionV3Addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"
        .parse()
        .unwrap();
}

/// A Bitmessage network address, one of IPv4, IPv6 or Onion V2.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Addr {
    /// An IPv4 address.
    Ipv4(Ipv4Addr),

    /// An IPv6 address.
    Ipv6(Ipv6Addr),

    /// An Onion V2 address.
    OnionV2(OnionV2Addr),
}

impl fmt::Display for Addr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Addr::Ipv4(addr) => addr.fmt(f),
            Addr::Ipv6(addr) => addr.fmt(f),
            Addr::OnionV2(addr) => addr.fmt(f),
        }
    }
}

const IPV4_PREFIX: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff];
const ONION_PREFIX: [u8; 6] = [0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43];

impl From<[u8; 16]> for Addr {
    fn from(bytes: [u8; 16]) -> Self {
        if bytes[0..12] == IPV4_PREFIX {
            let b: [u8; 4] = bytes[12..].try_into().unwrap();
            Self::Ipv4(b.into())
        } else if bytes[0..6] == ONION_PREFIX {
            let b: [u8; 10] = bytes[6..].try_into().unwrap();
            Self::OnionV2(b.into())
        } else {
            Self::Ipv6(bytes.into())
        }
    }
}

impl From<IpAddr> for Addr {
    fn from(addr: IpAddr) -> Self {
        match addr {
            IpAddr::V4(addr) => Self::Ipv4(addr),
            IpAddr::V6(addr) => Self::Ipv6(addr),
        }
    }
}

/// The error type returned when a conversion from a Bitmessage network address
/// to an IP address fails.
///
/// This error is used as the error type for the `TryFrom` implementation
/// for `IpAddr`.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TryFromAddrError {
    /// The input was Onion V2 address
    /// and the output could not represent it.
    /// The actual address is returned
    /// as a payload of this variant.
    OnionV2(OnionV2Addr),
}

impl fmt::Display for TryFromAddrError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OnionV2(_addr) => write!(f, "Onion V2 address not supported"),
        }
    }
}

impl std::error::Error for TryFromAddrError {}

impl TryFrom<Addr> for IpAddr {
    type Error = TryFromAddrError;

    fn try_from(addr: Addr) -> Result<Self, <Self as TryFrom<Addr>>::Error> {
        match addr {
            Addr::Ipv4(addr) => Ok(Self::V4(addr)),
            Addr::Ipv6(addr) => Ok(Self::V6(addr)),
            Addr::OnionV2(addr) => Err(Self::Error::OnionV2(addr)),
        }
    }
}

// TODO try SocksAddr -> Addr

#[cfg(feature = "koibumi-socks")]
impl From<Addr> for SocksAddr {
    fn from(addr: Addr) -> Self {
        match addr {
            Addr::Ipv4(addr) => Self::Ipv4(addr),
            Addr::Ipv6(addr) => Self::Ipv6(addr),
            Addr::OnionV2(addr) => Self::DomainName(addr.into()),
        }
    }
}

impl WriteTo for Addr {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        match self {
            Self::Ipv4(addr) => {
                IPV4_PREFIX.write_to(w)?;
                addr.octets().write_to(w)
            }
            Self::Ipv6(addr) => addr.octets().write_to(w),
            Self::OnionV2(addr) => {
                ONION_PREFIX.write_to(w)?;
                addr.as_ref().write_to(w)
            }
        }
    }
}

impl ReadFrom for Addr {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 16]>::read_from(r)?;
        Ok(bytes.into())
    }
}

#[test]
fn test_addr_write_to() {
    let test = Addr::Ipv4(Ipv4Addr::LOCALHOST);
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1];
    assert_eq!(bytes, expected);

    let test = Addr::Ipv6(Ipv6Addr::new(
        0x0123, 0x4567, 0x89ab, 0xcdef, 0xfedc, 0xba98, 0x7654, 0x3210,
    ));
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
        0x10,
    ];
    assert_eq!(bytes, expected);

    let test = Addr::OnionV2(OnionV2Addr::new([
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc,
    ]));
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [
        0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe,
        0xdc,
    ];
    assert_eq!(bytes, expected);
}

#[test]
fn test_addr_read_from() {
    let mut bytes = Cursor::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1]);
    let test = Addr::read_from(&mut bytes).unwrap();
    let expected = Addr::Ipv4(Ipv4Addr::LOCALHOST);
    assert_eq!(test, expected);

    let mut bytes = Cursor::new([
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
        0x10,
    ]);
    let test = Addr::read_from(&mut bytes).unwrap();
    let expected = Addr::Ipv6(Ipv6Addr::new(
        0x0123, 0x4567, 0x89ab, 0xcdef, 0xfedc, 0xba98, 0x7654, 0x3210,
    ));
    assert_eq!(test, expected);

    let mut bytes = Cursor::new([
        0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe,
        0xdc,
    ]);
    let test = Addr::read_from(&mut bytes).unwrap();
    let expected = Addr::OnionV2(OnionV2Addr::new([
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc,
    ]));
    assert_eq!(test, expected);
}

type Port = u16;

/// An Onion V2 socket address.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct SocketAddrOnionV2 {
    onion_addr: OnionV2Addr,
    port: Port,
}

impl fmt::Display for SocketAddrOnionV2 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.onion_addr, self.port)
    }
}

impl SocketAddrOnionV2 {
    /// Constructs a socket address from an Onion V2 address with a port.
    pub fn new(onion_addr: OnionV2Addr, port: Port) -> Self {
        Self { onion_addr, port }
    }

    /// Returns the address part of the socket address.
    pub fn onion_addr(&self) -> &OnionV2Addr {
        &self.onion_addr
    }

    /// Returns the port part of the socket address.
    pub fn port(&self) -> Port {
        self.port
    }
}

/// A Bitmessage network socket address, one of IPv4, IPv6 or Onion V2.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum SocketAddr {
    /// A socket address using an IPv4 address.
    Ipv4(SocketAddrV4),

    /// A socket address using an IPv6 address.
    Ipv6(SocketAddrV6),

    /// A socket address using an Onion V2 address.
    OnionV2(SocketAddrOnionV2),
}

impl fmt::Display for SocketAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SocketAddr::Ipv4(addr) => addr.fmt(f),
            SocketAddr::Ipv6(addr) => addr.fmt(f),
            SocketAddr::OnionV2(addr) => addr.fmt(f),
        }
    }
}

impl SocketAddr {
    /// Constructs a new socket address from an address and a port.
    pub fn new(addr: Addr, port: Port) -> Self {
        match addr {
            Addr::Ipv4(addr) => Self::Ipv4(SocketAddrV4::new(addr, port)),
            Addr::Ipv6(addr) => Self::Ipv6(SocketAddrV6::new(addr, port, 0, 0)),
            Addr::OnionV2(addr) => Self::OnionV2(SocketAddrOnionV2::new(addr, port)),
        }
    }
}

impl From<[u8; 18]> for SocketAddr {
    fn from(bytes: [u8; 18]) -> Self {
        let mut bytes = Cursor::new(bytes);
        let addr = Addr::read_from(&mut bytes).unwrap();
        let port = Port::read_from(&mut bytes).unwrap();
        match addr {
            Addr::Ipv4(addr) => Self::Ipv4(SocketAddrV4::new(addr, port)),
            Addr::Ipv6(addr) => Self::Ipv6(SocketAddrV6::new(addr, port, 0, 0)),
            Addr::OnionV2(addr) => Self::OnionV2(SocketAddrOnionV2::new(addr, port)),
        }
    }
}

impl From<StdSocketAddr> for SocketAddr {
    fn from(addr: StdSocketAddr) -> Self {
        match addr {
            StdSocketAddr::V4(addr) => Self::Ipv4(addr),
            StdSocketAddr::V6(addr) => Self::Ipv6(addr),
        }
    }
}

/// The error type returned when a conversion
/// from a Bitmessage network socket address
/// to a standard socket address fails.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TryFromSocketAddrError {
    /// The input was Onion V2 socket address
    /// and the output could not represent it.
    /// The actual socket address is returned
    /// as a payload of this variant.
    OnionV2(SocketAddrOnionV2),
}

impl fmt::Display for TryFromSocketAddrError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OnionV2(_addr) => write!(f, "Onion V2 address not supported"),
        }
    }
}

impl std::error::Error for TryFromSocketAddrError {}

impl TryFrom<SocketAddr> for StdSocketAddr {
    type Error = TryFromSocketAddrError;

    fn try_from(addr: SocketAddr) -> Result<Self, <Self as TryFrom<SocketAddr>>::Error> {
        match addr {
            SocketAddr::Ipv4(addr) => Ok(Self::V4(addr)),
            SocketAddr::Ipv6(addr) => Ok(Self::V6(addr)),
            SocketAddr::OnionV2(addr) => Err(Self::Error::OnionV2(addr)),
        }
    }
}

// TODO try SocksSocketAddr -> SocketAddr

#[cfg(feature = "koibumi-socks")]
impl From<SocketAddr> for SocksSocketAddr {
    fn from(addr: SocketAddr) -> Self {
        match addr {
            SocketAddr::Ipv4(addr) => Self::new(SocksAddr::Ipv4(*addr.ip()), addr.port()),
            SocketAddr::Ipv6(addr) => Self::new(SocksAddr::Ipv6(*addr.ip()), addr.port()),
            SocketAddr::OnionV2(addr) => Self::new(
                SocksAddr::DomainName(
                    DomainName::new(addr.onion_addr().to_string().as_bytes().to_vec()).unwrap(),
                ),
                addr.port(),
            ),
        }
    }
}

impl WriteTo for SocketAddr {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        match self {
            Self::Ipv4(addr) => {
                IPV4_PREFIX.write_to(w)?;
                addr.ip().octets().write_to(w)?;
                addr.port().write_to(w)?;
                Ok(())
            }
            Self::Ipv6(addr) => {
                addr.ip().octets().write_to(w)?;
                addr.port().write_to(w)?;
                Ok(())
            }
            Self::OnionV2(addr) => {
                ONION_PREFIX.write_to(w)?;
                addr.onion_addr().as_ref().write_to(w)?;
                addr.port().write_to(w)?;
                Ok(())
            }
        }
    }
}

impl ReadFrom for SocketAddr {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        let bytes = <[u8; 18]>::read_from(r)?;
        Ok(bytes.into())
    }
}

#[test]
fn test_socket_addr_write_to() {
    let test = SocketAddr::Ipv4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8444));
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1, 0x20, 0xfc,
    ];
    assert_eq!(bytes, expected);
}

#[test]
fn test_socket_addr_read_from() {
    let mut bytes = Cursor::new([
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1, 0x20, 0xfc,
    ]);
    let test = SocketAddr::read_from(&mut bytes).unwrap();
    let expected = SocketAddr::Ipv4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8444));
    assert_eq!(test, expected);
}

/// An extended Bitmessage network address,
/// one of IPv4, IPv6, Onion V2 or Onion V3.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum AddrExt {
    /// An IPv4 address.
    Ipv4(Ipv4Addr),

    /// An IPv6 address.
    Ipv6(Ipv6Addr),

    /// An Onion V2 address.
    OnionV2(OnionV2Addr),

    /// An Onion V3 address.
    OnionV3(OnionV3Addr),
}

impl fmt::Display for AddrExt {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AddrExt::Ipv4(addr) => addr.fmt(f),
            AddrExt::Ipv6(addr) => addr.fmt(f),
            AddrExt::OnionV2(addr) => addr.fmt(f),
            AddrExt::OnionV3(addr) => addr.fmt(f),
        }
    }
}

impl From<Addr> for AddrExt {
    fn from(addr: Addr) -> Self {
        match addr {
            Addr::Ipv4(addr) => Self::Ipv4(addr),
            Addr::Ipv6(addr) => Self::Ipv6(addr),
            Addr::OnionV2(addr) => Self::OnionV2(addr),
        }
    }
}

/// The error type returned when a conversion
/// from an extended Bitmessage network address
/// to a nonextended Bitmessage network address fails.
///
/// This error is used as the error type for the `TryFrom` implementation
/// for [`Addr`].
///
/// [`Addr`]: enum.Addr.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TryFromAddrExtError {
    /// The input was Onion V3 address
    /// and the output could not represent it.
    /// The actual address is returned
    /// as a payload of this variant.
    OnionV3(OnionV3Addr),
}

impl fmt::Display for TryFromAddrExtError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OnionV3(_addr) => write!(f, "Onion address not supported"),
        }
    }
}

impl std::error::Error for TryFromAddrExtError {}

impl TryFrom<AddrExt> for Addr {
    type Error = TryFromAddrExtError;

    fn try_from(addr: AddrExt) -> Result<Self, <Self as TryFrom<AddrExt>>::Error> {
        match addr {
            AddrExt::Ipv4(addr) => Ok(Self::Ipv4(addr)),
            AddrExt::Ipv6(addr) => Ok(Self::Ipv6(addr)),
            AddrExt::OnionV2(addr) => Ok(Self::OnionV2(addr)),
            AddrExt::OnionV3(addr) => Err(Self::Error::OnionV3(addr)),
        }
    }
}

// TODO try SocksAddr -> AddrExt

#[cfg(feature = "koibumi-socks")]
impl From<AddrExt> for SocksAddr {
    fn from(addr: AddrExt) -> Self {
        match addr {
            AddrExt::Ipv4(addr) => Self::Ipv4(addr),
            AddrExt::Ipv6(addr) => Self::Ipv6(addr),
            AddrExt::OnionV2(addr) => Self::DomainName(addr.into()),
            AddrExt::OnionV3(addr) => Self::DomainName(addr.into()),
        }
    }
}

/// An error which can be returned when parsing an extended Bitmessage network address
/// or an extended Bitmessage network socket address.
///
/// This error is used as the error type for the `FromStr` implementation
/// for [`AddrExt`].
/// This error is also used as a component of the error type
/// [`ParseSocketAddrExtError`].
///
/// [`AddrExt`]: enum.AddrExt.html
/// [`ParseSocketAddrExtError`]: enum.ParseSocketAddrExtError.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseAddrExtError {
    /// Failed when parsing the input as an Onion V2 address.
    /// The actual error caught is returned as a payload of this variant.
    OnionV2(ParseOnionV2AddrError),

    /// Failed when parsing the input as an Onion V3 address.
    /// The actual error caught is returned as a payload of this variant.
    OnionV3(ParseOnionV3AddrError),

    /// Unsupported version of Onion address.
    UnsupportedOnion,

    /// Failed when parsing the input as each type of IP addresses.
    /// The actual errors caught are returned as a payloads of this variant.
    Ip {
        /// The error caught when parsing as IPv4 address.
        v4: AddrParseError,
        /// The error caught when parsing as IPv6 address.
        v6: AddrParseError,
    },
}

impl fmt::Display for ParseAddrExtError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OnionV2(err) => err.fmt(f),
            Self::OnionV3(err) => err.fmt(f),
            Self::UnsupportedOnion => write!(f, "unsupported version of Onion address"),
            Self::Ip { v4, v6 } => write!(f, "v4: {}, v6: {}", v4, v6),
        }
    }
}

impl std::error::Error for ParseAddrExtError {}

impl FromStr for AddrExt {
    type Err = ParseAddrExtError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.ends_with(".onion") {
            if s.as_bytes().len() == 22 {
                match s.parse() {
                    Ok(addr) => Ok(Self::OnionV2(addr)),
                    Err(err) => Err(Self::Err::OnionV2(err)),
                }
            } else if s.as_bytes().len() == 62 {
                match s.parse() {
                    Ok(addr) => Ok(Self::OnionV3(addr)),
                    Err(err) => Err(Self::Err::OnionV3(err)),
                }
            } else {
                Err(ParseAddrExtError::UnsupportedOnion)
            }
        } else {
            let err_v4;
            match s.parse() {
                Ok(addr) => return Ok(Self::Ipv4(addr)),
                Err(err) => err_v4 = err,
            }
            let err_v6;
            match s.parse::<Ipv6Addr>() {
                Ok(addr) => {
                    let addr = addr.octets().into();
                    match addr {
                        Addr::Ipv4(addr) => return Ok(Self::Ipv4(addr)),
                        Addr::Ipv6(addr) => return Ok(Self::Ipv6(addr)),
                        Addr::OnionV2(addr) => return Ok(Self::OnionV2(addr)),
                    }
                }
                Err(err) => err_v6 = err,
            }
            Err(Self::Err::Ip {
                v4: err_v4,
                v6: err_v6,
            })
        }
    }
}

/// An Onion V3 socket address.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct SocketAddrOnionV3 {
    onion_addr: OnionV3Addr,
    port: Port,
}

impl fmt::Display for SocketAddrOnionV3 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.onion_addr, self.port)
    }
}

impl SocketAddrOnionV3 {
    /// Constructs a socket address from an Onion V3 address with a port.
    pub fn new(onion_addr: OnionV3Addr, port: Port) -> Self {
        Self { onion_addr, port }
    }

    /// Returns the address part of the socket address.
    pub fn onion_addr(&self) -> &OnionV3Addr {
        &self.onion_addr
    }

    /// Returns the port part of the socket address.
    pub fn port(&self) -> Port {
        self.port
    }
}

/// An extended Bitmessage network socket address,
/// one of IPv4, IPv6, Onion V2 or Onion V3.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum SocketAddrExt {
    /// A socket address using an IPv4 address.
    Ipv4(SocketAddrV4),

    /// A socket address using an IPv6 address.
    Ipv6(SocketAddrV6),

    /// A socket address using an Onion V2 address.
    OnionV2(SocketAddrOnionV2),

    /// A socket address using an Onion V3 address.
    OnionV3(SocketAddrOnionV3),
}

impl fmt::Display for SocketAddrExt {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SocketAddrExt::Ipv4(addr) => addr.fmt(f),
            SocketAddrExt::Ipv6(addr) => addr.fmt(f),
            SocketAddrExt::OnionV2(addr) => addr.fmt(f),
            SocketAddrExt::OnionV3(addr) => addr.fmt(f),
        }
    }
}

impl SocketAddrExt {
    /// Constructs an extended socket address from an extended address and a port.
    pub fn new(addr: AddrExt, port: Port) -> Self {
        match addr {
            AddrExt::Ipv4(addr) => Self::Ipv4(SocketAddrV4::new(addr, port)),
            AddrExt::Ipv6(addr) => Self::Ipv6(SocketAddrV6::new(addr, port, 0, 0)),
            AddrExt::OnionV2(addr) => Self::OnionV2(SocketAddrOnionV2::new(addr, port)),
            AddrExt::OnionV3(addr) => Self::OnionV3(SocketAddrOnionV3::new(addr, port)),
        }
    }
}

impl Serialize for SocketAddrExt {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for SocketAddrExt {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        s.parse().map_err(de::Error::custom)
    }
}

impl From<SocketAddr> for SocketAddrExt {
    fn from(addr: SocketAddr) -> Self {
        match addr {
            SocketAddr::Ipv4(addr) => Self::Ipv4(addr),
            SocketAddr::Ipv6(addr) => Self::Ipv6(addr),
            SocketAddr::OnionV2(addr) => Self::OnionV2(addr),
        }
    }
}

/// The error type returned when a conversion
/// from an extended Bitmessage network socket address
/// to a nonextended Bitmessage network socket address fails.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TryFromSocketAddrExtError {
    /// The input was Onion V2 socket address
    /// and the output could not represent it.
    /// The actual socket address is returned
    /// as a payload of this variant.
    OnionV2(SocketAddrOnionV2),

    /// The input was Onion V3 socket address
    /// and the output could not represent it.
    /// The actual socket address is returned
    /// as a payload of this variant.
    OnionV3(SocketAddrOnionV3),
}

impl fmt::Display for TryFromSocketAddrExtError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::OnionV2(_addr) => write!(f, "Onion V2 address not supported"),
            Self::OnionV3(_addr) => write!(f, "Onion V3 address not supported"),
        }
    }
}

impl std::error::Error for TryFromSocketAddrExtError {}

impl TryFrom<SocketAddrExt> for SocketAddr {
    type Error = TryFromSocketAddrExtError;

    fn try_from(addr: SocketAddrExt) -> Result<Self, <Self as TryFrom<SocketAddrExt>>::Error> {
        match addr {
            SocketAddrExt::Ipv4(addr) => Ok(Self::Ipv4(addr)),
            SocketAddrExt::Ipv6(addr) => Ok(Self::Ipv6(addr)),
            SocketAddrExt::OnionV2(addr) => Ok(Self::OnionV2(addr)),
            SocketAddrExt::OnionV3(addr) => Err(Self::Error::OnionV3(addr)),
        }
    }
}

impl From<StdSocketAddr> for SocketAddrExt {
    fn from(addr: StdSocketAddr) -> Self {
        match addr {
            StdSocketAddr::V4(addr) => Self::Ipv4(addr),
            StdSocketAddr::V6(addr) => Self::Ipv6(addr),
        }
    }
}

impl TryFrom<SocketAddrExt> for StdSocketAddr {
    type Error = TryFromSocketAddrExtError;

    fn try_from(addr: SocketAddrExt) -> Result<Self, <Self as TryFrom<SocketAddrExt>>::Error> {
        match addr {
            SocketAddrExt::Ipv4(addr) => Ok(addr.into()),
            SocketAddrExt::Ipv6(addr) => Ok(addr.into()),
            SocketAddrExt::OnionV2(addr) => Err(Self::Error::OnionV2(addr)),
            SocketAddrExt::OnionV3(addr) => Err(Self::Error::OnionV3(addr)),
        }
    }
}

// TODO try SocksSocketAddr -> SocketAddrExt

#[cfg(feature = "koibumi-socks")]
impl From<SocketAddrExt> for SocksSocketAddr {
    fn from(addr: SocketAddrExt) -> Self {
        match addr {
            SocketAddrExt::Ipv4(addr) => Self::new(SocksAddr::Ipv4(*addr.ip()), addr.port()),
            SocketAddrExt::Ipv6(addr) => Self::new(SocksAddr::Ipv6(*addr.ip()), addr.port()),
            SocketAddrExt::OnionV2(addr) => Self::new(
                SocksAddr::DomainName(
                    DomainName::new(addr.onion_addr().to_string().as_bytes().to_vec()).unwrap(),
                ),
                addr.port(),
            ),
            SocketAddrExt::OnionV3(addr) => Self::new(
                SocksAddr::DomainName(
                    DomainName::new(addr.onion_addr().to_string().as_bytes().to_vec()).unwrap(),
                ),
                addr.port(),
            ),
        }
    }
}

/// An error which can be returned
/// when parsing an extended Bitmessage network socket address.
///
/// This error is used as the error type for the `FromStr` implementation
/// for [`SocketAddrExt`].
///
/// [`SocketAddrExt`]: enum.SocketAddrExt.html
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseSocketAddrExtError {
    /// The input did not have any port number.
    PortNotFound,

    /// An error was caught when parsing a port number.
    /// The actual error caught is returned
    /// as a payload of this variant.
    InvalidPort(ParseIntError),

    /// An error was caught when parsing
    /// a extended Bitmessage network address.
    /// The actual error caught is returned
    /// as a payload of this variant.
    InvalidAddr(ParseAddrExtError),
}

impl fmt::Display for ParseSocketAddrExtError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::PortNotFound => write!(f, "port not found"),
            Self::InvalidPort(err) => err.fmt(f),
            Self::InvalidAddr(err) => err.fmt(f),
        }
    }
}

impl std::error::Error for ParseSocketAddrExtError {}

impl From<ParseIntError> for ParseSocketAddrExtError {
    fn from(err: ParseIntError) -> Self {
        Self::InvalidPort(err)
    }
}

impl From<ParseAddrExtError> for ParseSocketAddrExtError {
    fn from(err: ParseAddrExtError) -> Self {
        Self::InvalidAddr(err)
    }
}

impl FromStr for SocketAddrExt {
    type Err = ParseSocketAddrExtError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let colon = s.rfind(':');
        if colon.is_none() {
            return Err(Self::Err::PortNotFound);
        }
        let colon = colon.unwrap();

        let mut addr_part = &s[..colon];
        if addr_part.starts_with('[') && addr_part.ends_with(']') {
            addr_part = &addr_part[1..addr_part.len() - 1];
        }
        let port_part = &s[colon + 1..];

        let port = port_part.parse::<Port>()?;
        let addr = addr_part.parse()?;
        Ok(Self::new(addr, port))
    }
}

#[test]
fn test_socket_addr_ext_from_str() {
    let test: SocketAddrExt = "[2001:db8::1]:8080".parse().unwrap();
    let v6 = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0);
    let expected = SocketAddrExt::Ipv6(v6);
    assert_eq!(test, expected);
}
