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

use btlib::Result;
use btproto::protocol;
use btrun::{Activate, 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() {
    protocol! {
        let name = MinimalTest;
        let states = [Init];
        Init?Activate -> End;
    }

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

    struct InitState;

    impl Init for InitState {
        type HandleActivateFut = Ready<Result<End>>;
        fn handle_activate(self, _msg: btrun::Activate) -> Self::HandleActivateFut {
            ready(Ok(End))
        }
    }
}

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

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

    struct ServerInitState;

    impl ServerInit for ServerInitState {
        type ActivateOutputListening = ListeningState;
        type HandleActivateFut = Ready<Result<ListeningState>>;
        fn handle_activate(self, _msg: btrun::Activate) -> Self::HandleActivateFut {
            ready(Ok(ListeningState))
        }
    }

    struct ListeningState;

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

    struct ClientState;

    impl Client for ClientState {
        // TODO: Need to generate methods that the client must implement.
    }

    struct WaitingState;

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