use std::io::{self, Read, Write};

use crate::{
    io::{ReadFrom, WriteTo},
    net::SocketAddr,
    stream::StreamNumber,
    time::Time,
};

bitflags! {
    /// Bit flags that indicate what features a node serves.
    pub struct Services: u64 {
        /// Normal network node.
        const NETWORK = 1;
        /// Supports SSL/TLS.
        const SSL = 1<<1;
        /// My support PoW deligation.
        const POW = 1<<2;
        /// Supports dandelion.
        const DANDELION = 1<<3;
    }
}

impl WriteTo for Services {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.bits().write_to(w)
    }
}

impl ReadFrom for Services {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        let bits = u64::read_from(r)?;
        Ok(unsafe { Self::from_bits_unchecked(bits) })
    }
}

/// Represents a network address and other infomation of the node,
/// which is shared among the Bitmessage network.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct NetAddr {
    time: Time,
    stream_number: StreamNumber,
    services: Services,
    socket_addr: SocketAddr,
}

impl NetAddr {
    /// Constructs a network address from the specified parameters.
    pub fn new(
        time: Time,
        stream_number: StreamNumber,
        services: Services,
        socket_addr: SocketAddr,
    ) -> Self {
        Self {
            time,
            stream_number,
            services,
            socket_addr,
        }
    }

    /// Returns the time that the node was last seen.
    pub fn time(&self) -> Time {
        self.time
    }

    /// Returns the stream number for the node.
    pub fn stream_number(&self) -> StreamNumber {
        self.stream_number
    }

    /// Returns the flags what features the node serves.
    pub fn services(&self) -> Services {
        self.services
    }

    /// Returns the socket address of the node.
    pub fn socket_addr(&self) -> &SocketAddr {
        &self.socket_addr
    }
}

impl WriteTo for NetAddr {
    fn write_to(&self, w: &mut dyn Write) -> io::Result<()> {
        self.time.write_to(w)?;
        self.stream_number.as_u32().write_to(w)?;
        self.services.write_to(w)?;
        self.socket_addr.write_to(w)?;
        Ok(())
    }
}

impl ReadFrom for NetAddr {
    fn read_from(r: &mut dyn Read) -> io::Result<Self>
    where
        Self: Sized,
    {
        Ok(Self {
            time: Time::read_from(r)?,
            stream_number: u32::read_from(r)?.into(),
            services: Services::read_from(r)?,
            socket_addr: SocketAddr::read_from(r)?,
        })
    }
}

#[test]
fn test_net_addr_write_to() {
    use std::net::{Ipv4Addr, SocketAddrV4};

    let local_socket_addr = SocketAddr::Ipv4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8444));

    let test = NetAddr::new(
        0x0123_4567_89ab_cdef.into(),
        1.into(),
        Services::NETWORK,
        local_socket_addr,
    );
    let mut bytes = Vec::new();
    test.write_to(&mut bytes).unwrap();
    let expected = [
        0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, //
        0x00, 0x00, 0x00, 0x01, //
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, //
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1, 0x20, 0xfc, //
    ];
    assert_eq!(bytes, expected.to_vec());
}

#[test]
fn test_net_addr_read_from() {
    use std::{
        io::Cursor,
        net::{Ipv4Addr, SocketAddrV4},
    };

    let local_socket_addr = SocketAddr::Ipv4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 8444));

    let mut bytes = Cursor::new(
        [
            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, //
            0x00, 0x00, 0x00, 0x01, //
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, //
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1, 0x20, 0xfc, //
        ]
        .to_vec(),
    );
    let test = NetAddr::read_from(&mut bytes).unwrap();
    let expected = NetAddr::new(
        0x0123_4567_89ab_cdef.into(),
        1.into(),
        Services::NETWORK,
        local_socket_addr,
    );
    assert_eq!(test, expected);
}
