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

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

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

#[cfg(not(windows))]
mod linux;

#[cfg(windows)]
mod windows;

/// # Read/write Namaste
///
/// [`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,
        })
    }

    /// # 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()")),
                    };
                },
            };
        }
    }

    /// # 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.
    /// - The the read is dropped.
    pub fn try_read_with<F, T>(&mut self, timeout: Option<Duration>, f: F) -> Result<T> where F: FnOnce() -> T {
        self.try_read(timeout)?;
        let result = f();
        self.drop_read();
        Ok(result)
    }

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

    /// # 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()")),
                    };
                },
            };
        }
    }

    /// # 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.
    /// - The the write is dropped.
    pub fn try_write_with<F, T>(&mut self, timeout: Option<Duration>, f: F) -> Result<T> where F: FnOnce() -> T {
        self.try_write(timeout)?;
        let result = f();
        self.drop_write();
        Ok(result)
    }

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

}