/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Namaste

Copyright (C) 2019, 2021-2023  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2021-2023".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Read/write Namaste for Linux

use {
    core::{
        hash::{Hash, Hasher},
        time::Duration,
    },
    std::{
        io::ErrorKind,
        os::{
            fd::FromRawFd,
            linux::net::SocketAddrExt,
            unix::net::{SocketAddr, UnixListener, UnixStream},
        },
        thread,
        time::Instant,
    },
    crate::{Result, SLEEP_DURATION, UdsxUnixStream},
    super::Namaste,
};

/// # Read/write Namaste for Linux
///
/// [`Clone`][trait:core/clone/Clone] implementation does *not* clone read/write access.
///
/// [trait:core/clone/Clone]: https://doc.rust-lang.org/core/clone/trait.Clone.html
#[derive(Debug)]
pub struct RwNamaste<R, W> where R: AsRef<[u8]>, W: AsRef<[u8]> {
    read_id: R,
    read: Option<Namaste>,
    write_id: W,
    write: Option<Namaste>,
    timeout: Duration,
}

impl<R, W> RwNamaste<R, W> where R: AsRef<[u8]>, W: AsRef<[u8]> {

    /// # Makes new instance
    ///
    /// - An error is returned if:
    ///
    ///     + Read ID is same as write ID.
    ///     + `timeout` is zero.
    ///
    /// - `timeout` will be used in [`try_read()`](#method.try_read), [`try_write()`](#method.try_write)...
    pub fn make(read_id: R, write_id: W, timeout: Duration) -> Result<Self> {
        if read_id.as_ref() == write_id.as_ref() {
            return Err(err!("Read ID must be different to write ID"));
        }
        if timeout.is_zero() {
            return Err(err!("Timeout must not be zero"));
        }
        Ok(Self {
            read_id,
            read: None,
            write_id,
            write: None,
            timeout,
        })
    }

    /// # Takes a read
    pub fn read(&mut self) -> Result<()> {
        loop {
            match &self.read {
                None => {
                    let read = match Namaste::make(&self.read_id) {
                        Ok(namaste) => namaste,
                        Err(_) => match self.connect_to(&self.read_id) {
                            Ok(stream) => Namaste {
                                _sender: super::spawn_thread(unsafe {
                                    UnixListener::from_raw_fd(stream.recv_streams::<_, _, 1>(super::STREAM_ID)?[usize::MIN])
                                })?,
                            },
                            Err(_) => continue,
                        },
                    };
                    return match self.connect_to(&self.write_id) {
                        Ok(_) => Err(err!("Another write is working")),
                        Err(_) => Ok(self.read = Some(read)),
                    };
                },
                Some(_) => return Ok(()),
            };
        }
    }

    /// # Tries to take a read using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    pub fn try_read(&mut self, timeout: Option<Duration>) -> Result<()> {
        let start = Instant::now();
        loop {
            match self.read() {
                Ok(()) => return Ok(()),
                Err(_) => {
                    thread::sleep(SLEEP_DURATION);
                    match Instant::now().checked_duration_since(start) {
                        Some(duration) => if duration >= timeout.unwrap_or(self.timeout) {
                            return Err(err!(ErrorKind::TimedOut, "Timed out waiting for a read"));
                        },
                        None => return Err(err!("Failed calling Instant::checked_duration_since()")),
                    };
                },
            };
        }
    }

    /// # Drops read
    pub fn drop_read(&mut self) {
        self.read.take();
    }

    /// # Connects to a server
    fn connect_to<B>(&self, address: B) -> Result<UnixStream> where B: AsRef<[u8]> {
        UnixStream::connect_addr(&SocketAddr::from_abstract_name(address)?)
    }

    /// # Takes a write
    pub fn write(&mut self) -> Result<()> {
        match &self.write {
            None => {
                let write = Namaste::make(&self.write_id)?;
                match self.connect_to(&self.read_id) {
                    Ok(_) => Err(err!("Another read is working")),
                    Err(_) => Ok(self.write = Some(write)),
                }
            },
            Some(_) => Ok(()),
        }
    }

    /// # Tries to take a write using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    pub fn try_write(&mut self, timeout: Option<Duration>) -> Result<()> {
        let start = Instant::now();
        loop {
            match self.write() {
                Ok(()) => return Ok(()),
                Err(_) => {
                    thread::sleep(SLEEP_DURATION);
                    match Instant::now().checked_duration_since(start) {
                        Some(duration) => if duration >= timeout.unwrap_or(self.timeout) {
                            return Err(err!(ErrorKind::TimedOut, "Timed out waiting for a write"));
                        },
                        None => return Err(err!("Failed calling Instant::checked_duration_since()")),
                    };
                },
            };
        }
    }

    /// # Drops write
    pub fn drop_write(&mut self) {
        self.write.take();
    }

}

impl<R, W> Clone for RwNamaste<R, W> where R: AsRef<[u8]> + Clone, W: AsRef<[u8]> + Clone {

    fn clone(&self) -> Self {
        Self {
            read_id: self.read_id.clone(),
            read: None,
            write_id: self.write_id.clone(),
            write: None,
            timeout: self.timeout,
        }
    }

}

impl<R, W> Eq for RwNamaste<R, W> where R: AsRef<[u8]>, W: AsRef<[u8]> {}

impl<R, W> PartialEq for RwNamaste<R, W> where R: AsRef<[u8]>, W: AsRef<[u8]> {

    fn eq(&self, other: &Self) -> bool {
        self.read_id.as_ref() == other.read_id.as_ref() && self.write_id.as_ref() == other.write_id.as_ref()
    }

}

impl<R, W> Hash for RwNamaste<R, W> where R: AsRef<[u8]>, W: AsRef<[u8]> {

    fn hash<H>(&self, h: &mut H) where H: Hasher {
        self.read_id.as_ref().hash(h);
        self.write_id.as_ref().hash(h);
    }

}
