//! A custom tunnel device type that interfaces with Apple's `NEPacketTunnelFlow` [1].
//!
//! (Really, it just lets you write and read packets with C callbacks, but that's what it's designed
//! to be used for.)
//!
//! [1]: https://developer.apple.com/documentation/networkextension/nepackettunnelflow

use crate::{ReaderCallback, WriterCallback};
use std::collections::VecDeque;
use std::io::{Error, ErrorKind};
use std::pin::Pin;
use std::slice;
use std::sync::Mutex;
use std::task::{Context, Poll, Waker};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};

static BUFFER: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());
static WAKER: Mutex<Option<Waker>> = Mutex::new(None);

pub struct NePacketTunnelFlow {
    reader: ReaderCallback,
    writer: WriterCallback,
}

impl NePacketTunnelFlow {
    pub fn new(reader: ReaderCallback, writer: WriterCallback) -> NePacketTunnelFlow {
        NePacketTunnelFlow { reader, writer }
    }

    pub unsafe fn receive(
        packets: *const *const u8,
        packet_lengths: *const usize,
        packet_count: usize,
    ) {
        let mut data = BUFFER.lock().expect("buffer poisoned");

        for i in 0..packet_count {
            let packet = *packets.add(i);
            let len = *packet_lengths.add(i);
            let slice = slice::from_raw_parts(packet, len);

            data.push_back(slice.to_vec());
        }

        if let Some(waker) = WAKER.lock().expect("waker poisoned").take() {
            waker.wake();
        }
    }
}

impl AsyncRead for NePacketTunnelFlow {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<std::io::Result<()>> {
        let mut buffer = BUFFER.lock().expect("buffer poisoned");

        match buffer.pop_front() {
            Some(packet) => {
                buf.put_slice(packet.as_slice());

                Poll::Ready(Ok(()))
            }
            None => {
                let mut waker = WAKER.lock().expect("waker poisoned");

                // Check, if we're already waiting for a read to complete.
                let already_reading = waker.is_some();

                // Replace the waker with the new one in any case.
                *waker = Some(cx.waker().clone());

                if !already_reading {
                    (self.reader)();
                }

                Poll::Pending
            }
        }
    }
}

impl AsyncWrite for NePacketTunnelFlow {
    fn poll_write(
        self: Pin<&mut Self>,
        _cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<Result<usize, Error>> {
        if (self.writer)(buf.as_ptr(), buf.len()) {
            Poll::Ready(Ok(buf.len()))
        } else {
            Poll::Ready(Err(Error::new(
                ErrorKind::Other,
                "Couldn't write to the NEPacketTunnelFlow",
            )))
        }
    }

    fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        Poll::Ready(Ok(()))
    }

    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        Poll::Ready(Ok(()))
    }
}
