use std::future::{ready, Ready};

use btlib::Result;
use btproto::protocol;
use btrun::{CallMsg, End};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct Ping;

impl CallMsg for Ping {
    type Reply = ();
}

/// Tests that the given variable is of the given type.
macro_rules! assert_type {
    ($var:expr, $ty:ty) => {{
        let _: $ty = $var;
    }};
}

#[test]
fn minimal_syntax() {
    pub struct Msg;

    protocol! {
        named MinimalTest;
        let states = [Server];
        let client = [Client];
        Client -> End, >service(Server)!Msg;
        Server?Msg -> End;
    }

    let msg: Option<MinimalTestMsgs> = None;
    match msg {
        Some(MinimalTestMsgs::Msg(act)) => assert_type!(act, Msg),
        None => (),
    }

    struct ServerState;

    impl Server for ServerState {
        type HandleMsgFut = Ready<Result<End>>;
        fn handle_msg(self, _msg: Msg) -> Self::HandleMsgFut {
            ready(Ok(End))
        }
    }
}

#[test]
fn reply() {
    protocol! {
        named ReplyTest;
        let server = [Listening];
        let client = [Client, Waiting];
        Client -> Waiting, >service(Listening)!Ping;
        Listening?Ping -> Listening, >Waiting!Ping::Reply;
        Waiting?Ping::Reply -> End;
    }

    let msg: Option<ReplyTestMsgs> = None;
    match msg {
        Some(ReplyTestMsgs::Ping(ping)) => assert_type!(ping, Ping),
        Some(ReplyTestMsgs::PingReply(reply)) => assert_type!(reply, <Ping as CallMsg>::Reply),
        None => (),
    }

    struct ListeningState;

    impl Listening for ListeningState {
        type HandlePingListening = Self;
        type HandlePingFut = Ready<Result<Self>>;
        fn handle_ping(self, _msg: Ping) -> Self::HandlePingFut {
            ready(Ok(self))
        }
    }

    struct ClientState;

    impl Client for ClientState {
        type SendPingWaiting = WaitingState;
        type SendPingFut = Ready<Result<WaitingState>>;
        fn send_ping(self) -> Self::SendPingFut {
            ready(Ok(WaitingState))
        }
    }

    struct WaitingState;

    impl Waiting for WaitingState {
        type HandlePingReplyFut = Ready<Result<End>>;
        fn handle_ping_reply(self, _msg: Ping) -> Self::HandlePingReplyFut {
            ready(Ok(End))
        }
    }
}