use super::*;

/// Messages passed to [`Stream::message`] are converted into a buffer through this
/// trait.
///
/// Any type that implements [`Into<Bytes<'static>>`](Bytes<'static>) inherently implements this
/// trait with [`Error = !`](core::convert::Infallible).
///
/// # Implementing [`IntoPayload`] for fun and profit
/// A common usage for [`IntoPayload`] is to implement it for a wrapper type that selects a
/// serialization method.
///
/// For example, here's how to leverage this in a system that uses both JSON and CBOR:
/// ```
/// # mod dittolive_ditto {
/// #   pub mod experimental {
/// #       pub use dittolive_ditto_base::*;
/// #   }
/// # }
/// # use dittolive_ditto::experimental::bus::{IntoPayload, Payload};
/// pub struct Cbor<'a, T>(pub &'a T);
/// impl<T: serde::Serialize> IntoPayload for Cbor<'_, T> {
///     type Error = serde_cbor::Error;
///     fn into_payload(self) -> Result<Payload, Self::Error> {
///         Ok(serde_cbor::to_vec(self.0)?.into())
///     }
/// }
///
/// pub struct Json<'a, T>(pub &'a T);
/// impl<T: serde::Serialize> IntoPayload for Json<'_, T> {
///     type Error = serde_json::Error;
///     fn into_payload(self) -> Result<Payload, Self::Error> {
///         Ok(serde_json::to_vec(self.0)?.into())
///     }
/// }
/// ```
///
/// Letting you then use both of these serializations in streams:
/// ```
/// # mod dittolive_ditto {
/// #   pub mod experimental {
/// #       pub use dittolive_ditto_base::*;
/// #   }
/// # }
/// # use dittolive_ditto::experimental::bus::{IntoPayload, Payload, Stream};
/// # pub struct Cbor<'a, T>(pub &'a T);
/// # impl<T: serde::Serialize> IntoPayload for Cbor<'_, T> {
/// #     type Error = serde_cbor::Error;
/// #     fn into_payload(self) -> Result<Payload, Self::Error> {
/// #         Ok(serde_cbor::to_vec(self.0)?.into())
/// #     }
/// # }
/// # pub struct Json<'a, T>(pub &'a T);
/// # impl<T: serde::Serialize> IntoPayload for Json<'_, T> {
/// #     type Error = serde_json::Error;
/// #     fn into_payload(self) -> Result<Payload, Self::Error> {
/// #         Ok(serde_json::to_vec(self.0)?.into())
/// #     }
/// # }
/// # async fn usage(json_stream: Stream<()>, cbor_stream: Stream<()>) {
/// let mut payload = vec!["Hello", "there"];
/// cbor_stream.message(Cbor(&payload)).try_send().unwrap();
/// json_stream.message(Json(&payload)).try_send().unwrap();
/// # }
/// ```
pub trait IntoPayload {
    /// The error returned in case of conversion failure (typically a serialization error).
    ///
    /// If conversion is infallible, it's advised to use [`core::convert::Infallible`].
    type Error;
    /// Converts `self` into a [`Bytes`] buffer.
    ///
    /// # Errors
    /// If conversion is fallible (due to fallibility of serialization, for example), that error
    /// may be returned.
    fn into_payload(self) -> Result<Bytes<'static>, Self::Error>;
}

/// Wrap a reference to a [serializable](serde::Serialize) value with this to use [CBOR](https://en.wikipedia.org/wiki/CBOR)
/// serialization to convert it into a [`Bytes`] buffer.
///
/// ```no_run
/// # mod dittolive_ditto {
/// #   pub mod experimental {
/// #       pub use dittolive_ditto_base::*;
/// #   }
/// # }
/// # use {dittolive_ditto::experimental::bus::{Stream, Cbor}};
/// # async fn usage(stream: Stream<()>) {
/// let mut payload = vec!["Hello", "there"];
/// stream.message(Cbor(&payload)).try_send().unwrap();
/// # }
/// ```
pub struct Cbor<'a, T>(pub &'a T);
impl<T: serde::Serialize> IntoPayload for Cbor<'_, T> {
    type Error = serde_cbor::Error;
    fn into_payload(self) -> Result<Bytes<'static>, Self::Error> {
        Ok(serde_cbor::to_vec(self.0)?.into())
    }
}

/// Wrap a reference to a [serializable](serde::Serialize) value with this to use [JSON](https://en.wikipedia.org/wiki/JSON)
/// serialization to convert it into a [`Bytes`] buffer.
///
/// ```no_run
/// # mod dittolive_ditto {
/// #   pub mod experimental {
/// #       pub use dittolive_ditto_base::*;
/// #   }
/// # }
/// # use {dittolive_ditto::experimental::bus::{Stream, Json}};
/// # async fn usage(stream: Stream<()>) {
/// let mut payload = vec!["General", "Kenobi!"];
/// stream.message(Json(&payload)).try_send().unwrap();
/// # }
/// ```
pub struct Json<'a, T>(pub &'a T);
impl<T: serde::Serialize> IntoPayload for Json<'_, T> {
    type Error = serde_json::Error;
    fn into_payload(self) -> Result<Bytes<'static>, Self::Error> {
        Ok(serde_json::to_vec(self.0)?.into())
    }
}

impl<T: Into<Bytes<'static>>> IntoPayload for T {
    type Error = core::convert::Infallible;
    fn into_payload(self) -> Result<Bytes<'static>, Self::Error> {
        Ok(self.into())
    }
}

#[cfg(test)]
mod tests {
    use serde::{Deserialize, Serialize};

    use super::*;

    #[test]
    fn json_wrapper() {
        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct Foo {
            a: bool,
            b: Vec<i32>,
        }

        let foo = Foo {
            a: true,
            b: vec![1, 2, 3],
        };

        let wrapper = Json(&foo);
        let bytes = wrapper.into_payload().unwrap();
        assert_eq!(bytes, b"{\"a\":true,\"b\":[1,2,3]}");
    }

    #[test]
    fn cbor_wrapper() {
        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct Foo {
            a: bool,
            b: Vec<i32>,
        }

        let foo = Foo {
            a: true,
            b: vec![1, 2, 3],
        };

        let wrapper = Cbor(&foo);
        let bytes = wrapper.into_payload().unwrap();
        assert_eq!(
            bytes,
            [0xA2, 0x61, 0x61, 0xF5, 0x61, 0x62, 0x83, 0x01, 0x02, 0x03]
        );
    }
}
