Documentation
use futures_util::{Future, SinkExt, StreamExt};
use tokio::sync::mpsc::{self, Receiver};
use tokio_tungstenite::{connect_async, tungstenite::Message};

type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Failed twitch send text: {0}")]
    SendWssError(#[from] tokio_tungstenite::tungstenite::Error),
}

#[derive(Debug)]
pub struct TwitchIrcClient<'a> {
    commands: bool,
    membership: bool,
    tags: bool,
    nick: &'a str,
    channel: &'a str,
    access_token: &'a str,
    client: &'a str,
}

impl<'a> TwitchIrcClient<'a> {
    pub fn new(nick: &'a str, channel: &'a str, access_token: &'a str) -> TwitchIrcClient<'a> {
        TwitchIrcClient {
            commands: false,
            membership: false,
            tags: false,
            nick,
            channel,
            access_token,
            client: "wss://irc-ws.chat.twitch.tv:443",
        }
    }

    pub fn membership(mut self) -> Self {
        self.membership = true;
        self
    }

    pub fn commands(mut self) -> Self {
        self.commands = true;
        self
    }

    pub fn tags(mut self) -> Self {
        self.tags = true;
        self
    }

    pub async fn run(self) -> Result<(Receiver<String>, impl Future<Output = ()>)> {
        let (tx, rx) = mpsc::channel(1024);
        let mut capabilities = vec![];
        if self.commands {
            capabilities.push("twitch.tv/commands");
        }
        if self.membership {
            capabilities.push("twitch.tv/membership");
        }

        if self.tags {
            capabilities.push("twitch.tv/tags")
        }
        let (ws_stream, _) = connect_async(self.client)
            .await
            .expect("Failed to connect twitch irc");

        let cap = format!("CAP REQ :{}", capabilities.join(" "));
        let (mut write, mut read) = ws_stream.split();

        let auth_payload = format!("PASS oauth:{}", self.access_token);
        let nick_payload = format!("NICK {}", self.nick);
        let join_payload = format!("JOIN #{}", self.channel);

        write.send(Message::Text(cap)).await?;
        write.send(Message::Text(auth_payload)).await?;
        write.send(Message::Text(nick_payload)).await?;
        write.send(Message::Text(join_payload)).await?;

        let server = async move {
            loop {
                while let Ok(msg) = read.next().await.unwrap() {
                    match msg {
                        Message::Text(msg) => {
                            if let Err(e) = tx.send(msg).await {
                                eprint!("tx send error = {}", e);
                            };
                        }
                        Message::Ping(msg) => {
                            if let Err(e) = write.send(Message::Ping(msg)).await {
                                eprint!("ping Error : {}", e);
                            };
                        }
                        Message::Pong(msg) => {
                            println!("Pong {:?}", msg);
                        }
                        Message::Close(msg) => {
                            println!("Close {:?}", msg);
                        }
                        Message::Frame(msg) => {
                            println!("Frame {:?}", msg);
                        }
                        Message::Binary(msg) => {
                            println!("Binary {:?}", msg);
                        }
                    }
                }
            }
        };
        Ok((rx, server))
    }
}