use super::*;

/// A factory that can open a single [`StreamCandidate`].
///
/// While you can use the `OverloadId` pattern to define your own factories regardless of whether or
/// not you own their type, the common use cases should be covered by its two main implementations:
/// - [`WriteOnly`] is the advised [`CandidateHandler`] when you don't care about received data
///   (which will be dropped immediately if any arrives), but want to use the [`Stream`] to send
///   data.
/// - Any function that returns a handler that implements [`IntoChannel<_, Payload>`] will see its
///   return value used to [`StreamCandidate::open`], yielding a [`Stream<_>`] that wraps the
///   [`IntoChannel::Receiver`].
///
/// These factories can be passed to [`ConnectionBuilder::on_receive_factory`] and
/// [`AcceptorBuilder::on_receive_factory`] to simplify the handling of the [`StreamCandidate`] step
/// of setting up a [`Stream`].
pub trait SingleCandidateHandler<OverloadId, Arguments>: Send + Sync + 'static {
    /// Typically [`Stream`], but may be [`StreamCandidate`] or any other type if necessary.
    type Output;
    /// Typically a call to [`StreamCandidate::open`] or [`StreamCandidate::open_write_only`].
    fn open_once(self, candidate: StreamCandidate<Arguments>) -> Self::Output;
}

/// A factory that can open multiple [`StreamCandidate`]s.
///
/// While you can use the `OverloadId` pattern to define your own factories regardless of whether or
/// not you own their type, the common use cases should be covered by its two main implementations:
/// - [`WriteOnly`] is the advised [`CandidateHandler`] when you don't care about received data
///   (which will be dropped immediately if any arrives), but want to use the [`Stream`] to send
///   data.
/// - Any function that returns a handler that implements [`IntoChannel<_, Payload>`] will see its
///   return value used to [`StreamCandidate::open`], yielding a [`Stream<_>`] that wraps the
///   [`IntoChannel::Receiver`].
///
/// These factories can be passed to [`ConnectionBuilder::on_receive_factory`] and
/// [`AcceptorBuilder::on_receive_factory`] to simplify the handling of the [`StreamCandidate`] step
/// of setting up a [`Stream`].
pub trait CandidateHandler<OverloadId, Arguments>:
    SingleCandidateHandler<OverloadId, Arguments>
{
    /// Typically a call to [`StreamCandidate::open`] or [`StreamCandidate::open_write_only`].
    fn open(&self, candidate: StreamCandidate<Arguments>) -> Self::Output;
}

/// A factory that opens [`StreamCandidate`] as [write-only](StreamCandidate::open_write_only).
pub struct WriteOnly;

impl<
        OverloadId,
        Handler,
        F: FnOnce() -> Handler + Send + Sync + 'static,
        Arguments: IArguments,
    > SingleCandidateHandler<OverloadId, Arguments> for F
where
    Handler: IntoChannel<OverloadId, Inbound>,
    Handler::Sender: Channel<OverloadId, Inbound>,
{
    type Output = Stream<Handler::Receiver>;
    fn open_once(self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate.open(self())
    }
}
impl<OverloadId, Handler, F: Fn() -> Handler + Send + Sync + 'static, Arguments: IArguments>
    CandidateHandler<OverloadId, Arguments> for F
where
    Handler: IntoChannel<OverloadId, Inbound>,
    Handler::Sender: Channel<OverloadId, Inbound>,
{
    fn open(&self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate.open(self())
    }
}

impl<Arguments: IArguments> SingleCandidateHandler<(), Arguments> for WriteOnly {
    type Output = Stream<()>;
    fn open_once(self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate.open_write_only()
    }
}
impl<Arguments: IArguments> CandidateHandler<(), Arguments> for WriteOnly {
    fn open(&self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate.open_write_only()
    }
}
impl<Arguments: IArguments> SingleCandidateHandler<(), Arguments> for () {
    type Output = StreamCandidate<Arguments>;
    fn open_once(self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate
    }
}
impl<Arguments: IArguments> CandidateHandler<(), Arguments> for () {
    fn open(&self, candidate: StreamCandidate<Arguments>) -> Self::Output {
        candidate
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    pub fn candidate_handlers() {
        fn assert_single_handler<H: SingleCandidateHandler<Oid, NoArguments>, Oid>(_: H) {}
        fn assert_handler<H: CandidateHandler<Oid, NoArguments>, Oid>(value: H) {
            assert_single_handler(value);
        }
        assert_handler(crate::datastreams::WriteOnly);
        assert_handler(());
        assert_handler(std::sync::mpsc::channel);
        assert_handler(tokio::sync::mpsc::unbounded_channel);
        assert_handler(|| |_: Inbound| ());
    }
}
