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

Namaste

Copyright (C) 2019, 2021-2025  Anonymous

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

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

#![cfg(any(target_os="android", target_os="l4re", target_os="linux", windows))]
#![doc(cfg(any(target_os="android", target_os="l4re", target_os="linux", windows)))]

use {
    core::{
        hash::{Hash, Hasher},
        time::Duration,
    },
    std::{
        io::ErrorKind,
        time::Instant,
    },
    crate::{Result, SLEEP_DURATION},
    super::Namaste,
};

#[cfg(feature="tokio")]
use core::future::Future;

#[cfg(any(target_os="android", target_os="l4re", target_os="linux"))]
#[doc(cfg(any(target_os="android", target_os="l4re", target_os="linux")))]
mod linux;

#[cfg(windows)]
#[doc(cfg(windows))]
mod windows;

macro_rules! try_read { ($self: ident, $timeout: ident) => {{
    let start = Instant::now();
    loop {
        match async_call!($self.read()) {
            Ok(read) => return Ok(read),
            Err(_) => {
                sleep!();
                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()")),
                };
            },
        };
    }
}}}

macro_rules! try_write { ($self: ident, $timeout: ident) => {{
    let start = Instant::now();
    loop {
        match async_call!($self.write()) {
            Ok(write) => return Ok(write),
            Err(_) => {
                sleep!();
                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()")),
                };
            },
        };
    }
}}}

macro_rules! try_read_with { ($self: ident, $timeout: ident, $f: ident) => {{
    let _read = async_call!($self.try_read($timeout))?;

    #[cfg(not(feature="tokio"))]
    let result = $f();
    #[cfg(feature="tokio")]
    let result = $f.await;

    Ok(result)
}}}

macro_rules! try_write_with { ($self: ident, $timeout: ident, $f: ident) => {{
    let _write = async_call!($self.try_write($timeout))?;

    #[cfg(not(feature="tokio"))]
    let result = $f();
    #[cfg(feature="tokio")]
    let result = $f.await;

    Ok(result)
}}}

/// # Read/write Namaste
#[derive(Debug, Clone)]
pub struct RwNamaste<R, W> {
    read_id: R,
    write_id: W,
    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,
            write_id,
            timeout,
        })
    }

    /// # Tries to take a read using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    #[cfg(not(feature="tokio"))]
    #[doc(cfg(not(feature="tokio")))]
    pub fn try_read(&self, timeout: Option<Duration>) -> Result<Namaste> {
        try_read!(self, timeout)
    }

    /// # Tries to take a read using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    #[cfg(feature="tokio")]
    #[doc(cfg(feature="tokio"))]
    pub async fn try_read(&self, timeout: Option<Duration>) -> Result<Namaste> {
        try_read!(self, timeout)
    }

    /// # Tries to take a read using a timeout
    ///
    /// - If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    /// - When a read is taken, your function is called.
    /// - Then the read is dropped.
    #[cfg(not(feature="tokio"))]
    #[doc(cfg(not(feature="tokio")))]
    pub fn try_read_with<F, T>(&self, timeout: Option<Duration>, f: F) -> Result<T> where F: FnOnce() -> T {
        try_read_with!(self, timeout, f)
    }

    /// # Tries to take a read using a timeout
    ///
    /// - If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    /// - When a read is taken, your function is called.
    /// - Then the read is dropped.
    #[cfg(feature="tokio")]
    #[doc(cfg(feature="tokio"))]
    pub async fn try_read_with<F, T>(&self, timeout: Option<Duration>, f: F) -> Result<T> where F: Future<Output=T> {
        try_read_with!(self, timeout, f)
    }

    /// # Tries to take a write using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    #[cfg(not(feature="tokio"))]
    #[doc(cfg(not(feature="tokio")))]
    pub fn try_write(&self, timeout: Option<Duration>) -> Result<Namaste> {
        try_write!(self, timeout)
    }

    /// # Tries to take a write using a timeout
    ///
    /// If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    #[cfg(feature="tokio")]
    #[doc(cfg(feature="tokio"))]
    pub async fn try_write(&self, timeout: Option<Duration>) -> Result<Namaste> {
        try_write!(self, timeout)
    }

    /// # Tries to take a write using a timeout
    ///
    /// - If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    /// - When a write is taken, your function is called.
    /// - Then the write is dropped.
    #[cfg(not(feature="tokio"))]
    #[doc(cfg(not(feature="tokio")))]
    pub fn try_write_with<F, T>(&self, timeout: Option<Duration>, f: F) -> Result<T> where F: FnOnce() -> T {
        try_write_with!(self, timeout, f)
    }

    /// # Tries to take a write using a timeout
    ///
    /// - If `timeout` is `None`, the one provided in [`make()`](#method.make) will be used.
    /// - When a write is taken, your function is called.
    /// - Then the write is dropped.
    #[cfg(feature="tokio")]
    #[doc(cfg(feature="tokio"))]
    pub async fn try_write_with<F, T>(&self, timeout: Option<Duration>, f: F) -> Result<T> where F: Future<Output=T> {
        try_write_with!(self, timeout, f)
    }

}

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);
    }

}
