Documentation
use std::time::Duration;

use crate::error::{ReadyTimeoutError, RecvError, SelectTimeoutError, SendError, TrySelectError};

use crate::{Receiver, Sender};

/// A handle for selecting over multiple channel operations.
///
/// This allows you to wait on multiple send or receive operations simultaneously
/// and get notified when any one of them becomes ready.
///
/// # Examples
///
/// ```rust
/// use linch::{bounded, Select};
///
/// let (tx1, rx1) = bounded(1);
/// let (tx2, rx2) = bounded(1);
///
/// tx1.send(1).unwrap();
/// tx2.send(2).unwrap();
///
/// let mut sel = Select::new();
/// let idx1 = sel.recv(&rx1);
/// let idx2 = sel.recv(&rx2);
///
/// let op = sel.select();
/// match op.index() {
///     i if i == idx1 => {
///         let value = op.recv(&rx1).unwrap();
///         println!("Received {} from first channel", value);
///     },
///     i if i == idx2 => {
///         let value = op.recv(&rx2).unwrap();
///         println!("Received {} from second channel", value);
///     },
///     _ => unreachable!(),
/// }
/// ```
pub struct Select<'a> {
    select: crossbeam_channel::Select<'a>,
}

impl<'a> Default for Select<'a> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> Select<'a> {
    /// Creates a new select handle.
    ///
    /// Operations will be selected in a pseudo-random order to ensure fairness.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::Select;
    ///
    /// let mut sel = Select::new();
    /// ```
    pub fn new() -> Self {
        let select = crossbeam_channel::Select::new();
        Self { select }
    }

    /// Creates a new biased select handle.
    ///
    /// Operations will be selected in the order they were added, giving
    /// priority to earlier operations.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::Select;
    ///
    /// let mut sel = Select::new_biased();
    /// ```
    pub fn new_biased() -> Self {
        let select = crossbeam_channel::Select::new_biased();
        Self { select }
    }

    /// Adds a send operation to the select.
    ///
    /// Returns the index of this operation, which can be used to identify
    /// which operation was selected.
    ///
    /// # Arguments
    ///
    /// * `sender` - The sender to add to the select
    ///
    /// # Returns
    ///
    /// The index of this send operation
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, _rx) = bounded::<i32>(1);
    /// let mut sel = Select::new();
    /// let send_idx = sel.send(&tx);
    /// ```
    pub fn send<T>(&mut self, sender: &'a Sender<T>) -> usize {
        self.select.send(&sender.tx)
    }

    /// Adds a receive operation to the select.
    ///
    /// Returns the index of this operation, which can be used to identify
    /// which operation was selected.
    ///
    /// # Arguments
    ///
    /// * `receiver` - The receiver to add to the select
    ///
    /// # Returns
    ///
    /// The index of this receive operation
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (_tx, rx) = bounded::<i32>(1);
    /// let mut sel = Select::new();
    /// let recv_idx = sel.recv(&rx);
    /// ```
    pub fn recv<T>(&mut self, receiver: &'a Receiver<T>) -> usize {
        self.select.recv(&receiver.rx)
    }

    /// Blocks until one of the operations becomes ready and returns it.
    ///
    /// # Returns
    ///
    /// A [`SelectedOperation`] that can be used to complete the operation
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, rx) = bounded(1);
    /// tx.send(42).unwrap();
    ///
    /// let mut sel = Select::new();
    /// let recv_idx = sel.recv(&rx);
    ///
    /// let op = sel.select();
    /// if op.index() == recv_idx {
    ///     let value = op.recv(&rx).unwrap();
    ///     assert_eq!(value, 42);
    /// }
    /// ```
    pub fn select(&mut self) -> SelectedOperation<'a> {
        self.select.select().into()
    }

    /// Attempts to select a ready operation without blocking.
    ///
    /// # Returns
    ///
    /// * `Ok(SelectedOperation)` if an operation was ready
    /// * `Err(TrySelectError)` if no operations were ready
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, rx) = bounded::<i32>(1);
    /// let mut sel = Select::new();
    /// let recv_idx = sel.recv(&rx);
    ///
    /// // This will return an error since no values are available
    /// assert!(sel.try_select().is_err());
    ///
    /// tx.send(42).unwrap();
    /// // Now it will succeed
    /// let op = sel.try_select().unwrap();
    /// if op.index() == recv_idx {
    ///     let value = op.recv(&rx).unwrap();
    ///     assert_eq!(value, 42);
    /// }
    /// ```
    pub fn try_select(&mut self) -> Result<SelectedOperation<'a>, TrySelectError> {
        Ok(SelectedOperation::from(self.select.try_select()?))
    }

    /// Blocks until one of the operations becomes ready or the timeout expires.
    ///
    /// # Arguments
    ///
    /// * `timeout` - The maximum duration to wait
    ///
    /// # Returns
    ///
    /// * `Ok(SelectedOperation)` if an operation became ready
    /// * `Err(SelectTimeoutError)` if the timeout expired
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    /// use std::time::Duration;
    ///
    /// let (_tx, rx) = bounded::<i32>(1);
    /// let mut sel = Select::new();
    /// sel.recv(&rx);
    ///
    /// // This will timeout since no values are available
    /// let result = sel.select_timeout(Duration::from_millis(10));
    /// assert!(result.is_err());
    /// ```
    pub fn select_timeout(
        &mut self,
        timeout: Duration,
    ) -> Result<SelectedOperation<'a>, SelectTimeoutError> {
        Ok(SelectedOperation::from(
            self.select.select_timeout(timeout)?,
        ))
    }

    /// Returns the index of a ready operation, if any.
    ///
    /// This is a non-blocking operation that returns immediately.
    ///
    /// # Returns
    ///
    /// The index of a ready operation, or a value indicating no operations are ready
    pub fn ready(&mut self) -> usize {
        self.select.ready()
    }

    /// Returns the index of a ready operation, waiting up to the timeout.
    ///
    /// # Arguments
    ///
    /// * `timeout` - The maximum duration to wait
    ///
    /// # Returns
    ///
    /// * `Ok(index)` if an operation became ready
    /// * `Err(ReadyTimeoutError)` if the timeout expired
    pub fn ready_timeout(&mut self, timeout: Duration) -> Result<usize, ReadyTimeoutError> {
        Ok(self.select.ready_timeout(timeout)?)
    }

    /// Removes an operation from the select.
    ///
    /// # Arguments
    ///
    /// * `index` - The index of the operation to remove
    pub fn remove(&mut self, index: usize) {
        self.select.remove(index);
    }
}

/// A selected operation that is ready to complete.
///
/// This struct represents an operation that was selected by a [`Select`] and is
/// ready to be executed. You can use the [`index`](SelectedOperation::index) method
/// to determine which operation was selected, and then call the appropriate
/// [`send`](SelectedOperation::send) or [`recv`](SelectedOperation::recv) method.
///
/// # Examples
///
/// ```rust
/// use linch::{bounded, Select};
///
/// let (tx, rx) = bounded(1);
/// tx.send(42).unwrap();
///
/// let mut sel = Select::new();
/// let recv_idx = sel.recv(&rx);
///
/// let op = sel.select();
/// if op.index() == recv_idx {
///     let value = op.recv(&rx).unwrap();
///     assert_eq!(value, 42);
/// }
/// ```
pub struct SelectedOperation<'a>(crossbeam_channel::SelectedOperation<'a>);

impl<'a> From<crossbeam_channel::SelectedOperation<'a>> for SelectedOperation<'a> {
    fn from(value: crossbeam_channel::SelectedOperation<'a>) -> Self {
        Self(value)
    }
}

impl<'a> SelectedOperation<'a> {
    /// Returns the index of the selected operation.
    ///
    /// This index corresponds to the value returned by the [`send`](Select::send)
    /// or [`recv`](Select::recv) method that added this operation to the select.
    ///
    /// # Returns
    ///
    /// The index of the selected operation
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, rx) = bounded::<i32>(1);
    /// tx.send(42).unwrap();
    ///
    /// let mut sel = Select::new();
    /// let recv_idx = sel.recv(&rx);
    ///
    /// let op = sel.select();
    /// assert_eq!(op.index(), recv_idx);
    /// let value = op.recv(&rx).unwrap();
    /// assert_eq!(value, 42);
    /// ```
    pub fn index(&self) -> usize {
        self.0.index()
    }

    /// Completes the selected send operation.
    ///
    /// This method should only be called if the selected operation was a send operation.
    ///
    /// # Arguments
    ///
    /// * `sender` - The sender that was selected
    /// * `msg` - The message to send
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the message was sent successfully
    /// * `Err(SendError(msg))` if all receivers have been dropped
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, rx) = bounded(1);
    /// let mut sel = Select::new();
    /// let send_idx = sel.send(&tx);
    ///
    /// let op = sel.select();
    /// if op.index() == send_idx {
    ///     op.send(&tx, 42).unwrap();
    ///     assert_eq!(rx.recv().unwrap(), 42);
    /// }
    /// ```
    pub fn send<T>(self, sender: &'a Sender<T>, msg: T) -> Result<(), SendError<T>> {
        let res = self.0.send(&sender.tx, msg);
        if res.is_ok() {
            sender.signal_recv();
        }

        Ok(res?)
    }

    /// Completes the selected receive operation.
    ///
    /// This method should only be called if the selected operation was a receive operation.
    ///
    /// # Arguments
    ///
    /// * `receiver` - The receiver that was selected
    ///
    /// # Returns
    ///
    /// * `Ok(value)` if a value was received successfully
    /// * `Err(RecvError)` if all senders have been dropped and the channel is empty
    ///
    /// # Examples
    ///
    /// ```rust
    /// use linch::{bounded, Select};
    ///
    /// let (tx, rx) = bounded(1);
    /// tx.send(42).unwrap();
    ///
    /// let mut sel = Select::new();
    /// let recv_idx = sel.recv(&rx);
    ///
    /// let op = sel.select();
    /// if op.index() == recv_idx {
    ///     let value = op.recv(&rx).unwrap();
    ///     assert_eq!(value, 42);
    /// }
    /// ```
    pub fn recv<T>(self, receiver: &'a Receiver<T>) -> Result<T, RecvError> {
        let res = self.0.recv(&receiver.rx);
        if res.is_ok() {
            receiver.signal_send();
        }

        Ok(res?)
    }
}