//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//                    Version 2, December 2004
//
// Copyleft (ↄ) meh. <meh@schizofreni.co> | http://meh.schizofreni.co
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
//            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
//  0. You just DO WHAT THE FUCK YOU WANT TO.

use libc::{
    self, AF_INET, IFF_MULTI_QUEUE, IFF_NAPI, IFF_NO_PI, IFF_RUNNING, IFF_TAP, IFF_TUN, IFF_UP,
    IFF_VNET_HDR, IFNAMSIZ, O_CLOEXEC, O_RDWR, SOCK_CLOEXEC, SOCK_DGRAM, c_char, c_short, ifreq,
};
use nix::sys::ioctl::ioctl_param_type;
use std::{
    ffi::{CStr, CString},
    io::{Read, Write},
    mem,
    net::IpAddr,
    os::unix::io::{AsRawFd, IntoRawFd, RawFd},
    ptr,
};

use crate::{
    configuration::{Configuration, Layer},
    device::AbstractDevice,
    error::{Error, Result},
    platform::linux::sys::*,
    platform::posix::{self, Fd, Tun, ipaddr_to_sockaddr, sockaddr_union},
};

const OVERWRITE_SIZE: usize = std::mem::size_of::<libc::__c_anonymous_ifr_ifru>();

/// A TUN device using the TUN/TAP Linux driver.
pub struct Device {
    tun_name: String,
    pub(crate) tun: Tun,
    ctl: Fd,
}

impl AsRef<dyn AbstractDevice + 'static> for Device {
    fn as_ref(&self) -> &(dyn AbstractDevice + 'static) {
        self
    }
}

impl AsMut<dyn AbstractDevice + 'static> for Device {
    fn as_mut(&mut self) -> &mut (dyn AbstractDevice + 'static) {
        self
    }
}

impl Device {
    /// Create a new `Device` for the given `Configuration`.
    pub fn new(config: &Configuration) -> Result<Self> {
        if let Some(fd) = config.raw_fd {
            let close_fd_on_drop = config.close_fd_on_drop.unwrap_or(true);
            let tun_fd = Fd::new(fd, close_fd_on_drop)?;
            let mtu = config.mtu.unwrap_or(crate::DEFAULT_MTU);
            let packet_information = config.platform_config.packet_information;
            let tun_name = config.tun_name.clone().unwrap_or_else(|| "".into());
            let ctl = Fd::new(
                unsafe { libc::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0) },
                true,
            )
            .map_err(|_| std::io::Error::last_os_error())?;
            return Ok(Device {
                tun: Tun::new(tun_fd, mtu, packet_information),
                tun_name,
                ctl,
            });
        }

        let mut device = unsafe {
            let dev_name = match config.tun_name.as_ref() {
                Some(tun_name) => {
                    let tun_name = CString::new(tun_name.clone())?;

                    if tun_name.as_bytes_with_nul().len() > IFNAMSIZ {
                        return Err(Error::NameTooLong);
                    }

                    Some(tun_name)
                }

                None => None,
            };

            let mut req: ifreq = mem::zeroed();

            if let Some(dev_name) = dev_name.as_ref() {
                ptr::copy_nonoverlapping(
                    dev_name.as_ptr() as *const c_char,
                    req.ifr_name.as_mut_ptr(),
                    dev_name.as_bytes_with_nul().len(),
                );
            }

            let device_type: c_short = config.layer.unwrap_or(Layer::L3).into();

            let queues_num = config.queues.unwrap_or(1);
            if queues_num != 1 {
                return Err(Error::InvalidQueuesNumber);
            }

            let iff_no_pi = IFF_NO_PI as c_short;
            let iff_multi_queue = IFF_MULTI_QUEUE as c_short;
            let iff_napi = IFF_NAPI as c_short;
            let iff_vnet_hdr = IFF_VNET_HDR as c_short;
            let packet_information = config.platform_config.packet_information;
            let napi = config.platform_config.napi;
            let vnet_hdr = config.platform_config.vnet_hdr;
            req.ifr_ifru.ifru_flags = device_type
                | if packet_information { 0 } else { iff_no_pi }
                | if napi { iff_napi } else { 0 }
                | if vnet_hdr { iff_vnet_hdr } else { 0 }
                | if queues_num > 1 { iff_multi_queue } else { 0 };

            let tun_fd = {
                let fd = libc::open(c"/dev/net/tun".as_ptr() as *const _, O_RDWR | O_CLOEXEC);
                let tun_fd = Fd::new(fd, true).map_err(|_| std::io::Error::last_os_error())?;

                if let Err(err) = tunsetiff(tun_fd.inner, &mut req as *mut _ as *mut _) {
                    return Err(std::io::Error::from(err).into());
                }

                tun_fd
            };

            let mtu = config.mtu.unwrap_or(crate::DEFAULT_MTU);

            let ctl = Fd::new(libc::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0), true)
                .map_err(|_| std::io::Error::last_os_error())?;

            let tun_name = CStr::from_ptr(req.ifr_name.as_ptr())
                .to_string_lossy()
                .to_string();
            Device {
                tun_name,
                tun: Tun::new(tun_fd, mtu, packet_information),
                ctl,
            }
        };

        if config.platform_config.ensure_root_privileges {
            device.configure(config)?;
        }

        Ok(device)
    }

    /// Prepare a new request.
    unsafe fn request(&self) -> ifreq {
        let mut req: ifreq = unsafe { mem::zeroed() };
        unsafe {
            ptr::copy_nonoverlapping(
                self.tun_name.as_ptr() as *const c_char,
                req.ifr_name.as_mut_ptr(),
                self.tun_name.len(),
            )
        };

        req
    }

    /// Make the device persistent.
    pub fn persist(&mut self) -> Result<()> {
        unsafe {
            if let Err(err) = tunsetpersist(self.as_raw_fd(), 1) {
                Err(std::io::Error::from(err).into())
            } else {
                Ok(())
            }
        }
    }

    /// Set the owner of the device.
    pub fn user(&mut self, value: i32) -> Result<()> {
        unsafe {
            if let Err(err) = tunsetowner(self.as_raw_fd(), value as ioctl_param_type) {
                Err(std::io::Error::from(err).into())
            } else {
                Ok(())
            }
        }
    }

    /// Set the group of the device.
    pub fn group(&mut self, value: i32) -> Result<()> {
        unsafe {
            if let Err(err) = tunsetgroup(self.as_raw_fd(), value as ioctl_param_type) {
                Err(std::io::Error::from(err).into())
            } else {
                Ok(())
            }
        }
    }

    /// Split the interface into a `Reader` and `Writer`.
    pub fn split(self) -> (posix::Reader, posix::Writer) {
        (self.tun.reader, self.tun.writer)
    }

    /// Set non-blocking mode
    pub fn set_nonblock(&self) -> std::io::Result<()> {
        self.tun.set_nonblock()
    }

    /// Recv a packet from tun device
    pub fn recv(&self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.tun.recv(buf)
    }

    /// Recv a packet from tun device, waiting at most `timeout`.
    ///
    /// A TUN device is a character device, so `SO_RCVTIMEO`-style read
    /// timeouts do not apply; this is implemented as poll(2) followed by a
    /// read on every call. If no packet arrives within `timeout`, an error
    /// of kind `std::io::ErrorKind::TimedOut` is returned. A zero `timeout`
    /// checks for a packet without blocking. If several threads receive from
    /// the same device concurrently, another thread may consume the packet
    /// first and the read may still block.
    pub fn recv_timeout(
        &self,
        buf: &mut [u8],
        timeout: std::time::Duration,
    ) -> std::io::Result<usize> {
        self.tun.recv_timeout(buf, timeout)
    }

    /// Send a packet to tun device
    pub fn send(&self, buf: &[u8]) -> std::io::Result<usize> {
        self.tun.send(buf)
    }
}

impl Read for Device {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.tun.read(buf)
    }

    fn read_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> std::io::Result<usize> {
        self.tun.read_vectored(bufs)
    }
}

impl Write for Device {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.tun.write(buf)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.tun.flush()
    }

    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
        self.tun.write_vectored(bufs)
    }
}

impl AbstractDevice for Device {
    fn tun_index(&self) -> Result<i32> {
        let name = self.tun_name()?;
        Ok(posix::tun_name_to_index(name)? as i32)
    }

    fn tun_name(&self) -> Result<String> {
        Ok(self.tun_name.clone())
    }

    fn set_tun_name(&mut self, value: &str) -> Result<()> {
        unsafe {
            let tun_name = CString::new(value)?;

            if tun_name.as_bytes_with_nul().len() > IFNAMSIZ {
                return Err(Error::NameTooLong);
            }

            let mut req = self.request();
            ptr::copy_nonoverlapping(
                tun_name.as_ptr() as *const c_char,
                req.ifr_ifru.ifru_newname.as_mut_ptr(),
                value.len(),
            );

            if let Err(err) = siocsifname(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }

            self.tun_name = value.into();

            Ok(())
        }
    }

    fn enabled(&mut self, value: bool) -> Result<()> {
        unsafe {
            let mut req = self.request();

            if let Err(err) = siocgifflags(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }

            if value {
                req.ifr_ifru.ifru_flags |= (IFF_UP | IFF_RUNNING) as c_short;
            } else {
                req.ifr_ifru.ifru_flags &= !(IFF_UP as c_short);
            }

            if let Err(err) = siocsifflags(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }

            Ok(())
        }
    }

    fn address(&self) -> Result<IpAddr> {
        unsafe {
            let mut req = self.request();
            if let Err(err) = siocgifaddr(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }
            let sa = sockaddr_union::from(req.ifr_ifru.ifru_addr);
            Ok(std::net::SocketAddr::try_from(sa)?.ip())
        }
    }

    fn set_address(&mut self, value: IpAddr) -> Result<()> {
        unsafe {
            let mut req = self.request();
            ipaddr_to_sockaddr(value, 0, &mut req.ifr_ifru.ifru_addr, OVERWRITE_SIZE);
            if let Err(err) = siocsifaddr(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }
            Ok(())
        }
    }

    fn destination(&self) -> Result<IpAddr> {
        unsafe {
            let mut req = self.request();
            if let Err(err) = siocgifdstaddr(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }
            let sa = sockaddr_union::from(req.ifr_ifru.ifru_dstaddr);
            Ok(std::net::SocketAddr::try_from(sa)?.ip())
        }
    }

    fn set_destination(&mut self, value: IpAddr) -> Result<()> {
        unsafe {
            let mut req = self.request();
            ipaddr_to_sockaddr(value, 0, &mut req.ifr_ifru.ifru_dstaddr, OVERWRITE_SIZE);
            if let Err(err) = siocsifdstaddr(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }
            Ok(())
        }
    }

    fn broadcast(&self) -> Result<IpAddr> {
        unsafe {
            let mut req = self.request();
            if let Err(err) = siocgifbrdaddr(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }
            let sa = sockaddr_union::from(req.ifr_ifru.ifru_broadaddr);
            Ok(std::net::SocketAddr::try_from(sa)?.ip())
        }
    }

    fn set_broadcast(&mut self, value: IpAddr) -> Result<()> {
        unsafe {
            let mut req = self.request();
            ipaddr_to_sockaddr(value, 0, &mut req.ifr_ifru.ifru_broadaddr, OVERWRITE_SIZE);
            if let Err(err) = siocsifbrdaddr(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }
            Ok(())
        }
    }

    fn netmask(&self) -> Result<IpAddr> {
        unsafe {
            let mut req = self.request();
            if let Err(err) = siocgifnetmask(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }
            let sa = sockaddr_union::from(req.ifr_ifru.ifru_netmask);
            Ok(std::net::SocketAddr::try_from(sa)?.ip())
        }
    }

    fn set_netmask(&mut self, value: IpAddr) -> Result<()> {
        unsafe {
            let mut req = self.request();
            ipaddr_to_sockaddr(value, 0, &mut req.ifr_ifru.ifru_netmask, OVERWRITE_SIZE);
            if let Err(err) = siocsifnetmask(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }
            Ok(())
        }
    }

    fn mtu(&self) -> Result<u16> {
        unsafe {
            let mut req = self.request();

            if let Err(err) = siocgifmtu(self.ctl.as_raw_fd(), &mut req) {
                return Err(std::io::Error::from(err).into());
            }

            req.ifr_ifru
                .ifru_mtu
                .try_into()
                .map_err(|_| Error::TryFromIntError)
        }
    }

    fn set_mtu(&mut self, value: u16) -> Result<()> {
        unsafe {
            let mut req = self.request();
            req.ifr_ifru.ifru_mtu = value as i32;

            if let Err(err) = siocsifmtu(self.ctl.as_raw_fd(), &req) {
                return Err(std::io::Error::from(err).into());
            }
            self.tun.set_mtu(value);
            Ok(())
        }
    }

    fn packet_information(&self) -> bool {
        self.tun.packet_information()
    }
}

impl AsRawFd for Device {
    fn as_raw_fd(&self) -> RawFd {
        self.tun.as_raw_fd()
    }
}

impl IntoRawFd for Device {
    fn into_raw_fd(self) -> RawFd {
        self.tun.into_raw_fd()
    }
}

impl From<Layer> for c_short {
    fn from(layer: Layer) -> Self {
        match layer {
            Layer::L2 => IFF_TAP as c_short,
            Layer::L3 => IFF_TUN as c_short,
        }
    }
}
