1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use super::lobbies::{handle_create_lobby, handle_join_lobby, handle_list_lobbies};
use super::matchmaking::{handle_request_matchaking, handle_stop_matchmaking};
use crate::helpers::generate_random_seed;
use anyhow::Result;
use bones_matchmaker_proto::{
    GameID, LobbyId, LobbyInfo, MatchInfo, MatchmakerRequest, MatchmakerResponse,
    PlayerIdxAssignment,
};
use futures::future::BoxFuture;
use iroh::{endpoint::Connection, Endpoint, NodeAddr};
use once_cell::sync::Lazy;
use rand::{prelude::SliceRandom, SeedableRng};
use scc::HashMap as SccHashMap;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

/// Represents the lobbies for a specific game
pub struct GameLobbies {
    #[allow(dead_code)]
    pub game_id: GameID,
    pub lobbies: HashMap<LobbyId, LobbyInfo>,
}

/// Represents the global state of the matchmaker
#[derive(Default)]
pub struct State {
    pub game_lobbies: HashMap<GameID, GameLobbies>,
    pub lobby_connections: SccHashMap<(GameID, LobbyId), Vec<Connection>>,
    pub matchmaking_rooms: SccHashMap<MatchInfo, Vec<Connection>>,
}

/// Global state of the matchmaker
pub static MATCHMAKER_STATE: Lazy<Arc<Mutex<State>>> =
    Lazy::new(|| Arc::new(Mutex::new(State::default())));

#[derive(Debug)]
pub struct Matchmaker {
    endpoint: Endpoint,
}

impl iroh::protocol::ProtocolHandler for Matchmaker {
    fn accept(self: Arc<Self>, conn: iroh::endpoint::Connecting) -> BoxFuture<'static, Result<()>> {
        Box::pin(async move {
            let connection = conn.await;

            match connection {
                Ok(conn) => {
                    info!(
                        connection_id = conn.stable_id(),
                        addr = ?conn.remote_address(),
                        "Accepted connection from client"
                    );

                    // Spawn a task to handle the new connection
                    self.handle_connection(conn).await?;
                }
                Err(e) => error!("Error opening client connection: {e:?}"),
            }

            Ok(())
        })
    }
}

impl Matchmaker {
    pub fn new(endpoint: Endpoint) -> Self {
        Matchmaker { endpoint }
    }

    /// Handles incoming connections and routes requests to appropriate handlers
    async fn handle_connection(&self, conn: Connection) -> Result<()> {
        let connection_id = conn.stable_id();
        loop {
            tokio::select! {
                _ = conn.closed() => {
                    info!("[{}] Client closed connection.", connection_id);
                    return Ok(());
                }
                bi = conn.accept_bi() => {
                    let (mut send, mut recv) = bi?;
                    // Parse the incoming request
                    let request: MatchmakerRequest = postcard::from_bytes(&recv.read_to_end(256).await?)?;

                    // Route the request to the appropriate handler
                    match request {
                        MatchmakerRequest::RequestMatchmaking(match_info) => {
                            handle_request_matchaking(&self.endpoint, conn.clone(), match_info, &mut send).await?;
                            send.finish()?;
                            send.stopped().await?;
                        }
                        MatchmakerRequest::StopMatchmaking(match_info) => {
                            handle_stop_matchmaking(conn.clone(), match_info, &mut send).await?;
                        }
                        MatchmakerRequest::ListLobbies(game_id) => {
                            handle_list_lobbies(game_id, &mut send).await?;
                        }
                        MatchmakerRequest::CreateLobby(lobby_info) => {
                            handle_create_lobby(conn.clone(), lobby_info, &mut send).await?;
                        }
                        MatchmakerRequest::JoinLobby(game_id, lobby_id, password) => {
                            handle_join_lobby(&self.endpoint, conn.clone(), game_id, lobby_id, password, &mut send).await?;
                        }
                    }
                }
            }
        }
    }
}

/// Starts a match/lobby with the given members
pub async fn start_game(
    ep: Endpoint,
    members: Vec<Connection>,
    match_info: &MatchInfo,
) -> Result<()> {
    let random_seed = generate_random_seed();
    let mut player_ids = Vec::new();
    let player_count = members.len();

    // Generate player indices based on the PlayerIdxAssignment
    let player_indices = match &match_info.player_idx_assignment {
        PlayerIdxAssignment::Ordered => (0..player_count).collect::<Vec<_>>(),
        PlayerIdxAssignment::Random => {
            let mut indices: Vec<_> = (0..player_count).collect();
            let mut rng = rand::rngs::StdRng::seed_from_u64(random_seed);
            indices.shuffle(&mut rng);
            indices
        }
        PlayerIdxAssignment::SpecifiedOrder(order) => {
            let mut indices = order.clone();
            match indices.len().cmp(&player_count) {
                Ordering::Less => {
                    indices.extend(indices.len()..player_count);
                }
                Ordering::Greater => {
                    indices.truncate(player_count);
                }
                _ => (),
            }
            indices
        }
    };

    // Collect player IDs and addresses
    for (conn_idx, conn) in members.iter().enumerate() {
        let id = iroh::endpoint::get_remote_node_id(conn)?;
        let mut addr = NodeAddr::new(id);
        if let Some(info) = ep.remote_info(id) {
            if let Some(relay_url) = info.relay_url {
                addr = addr.with_relay_url(relay_url.relay_url);
            }
            addr = addr.with_direct_addresses(info.addrs.into_iter().map(|addr| addr.addr));
        }
        let player_idx = player_indices[conn_idx];
        player_ids.push((player_idx as u32, addr));
    }

    // Sort player_ids by the assigned player index
    player_ids.sort_by_key(|&(idx, _)| idx);

    // Send match information to each player
    for (conn_idx, conn) in members.into_iter().enumerate() {
        let player_idx = player_indices[conn_idx];
        let message = postcard::to_allocvec(&MatchmakerResponse::Success {
            random_seed,
            player_count: player_ids.len() as u32,
            player_idx: player_idx as u32,
            player_ids: player_ids.clone(),
        })?;
        let mut send = conn.open_uni().await?;
        send.write_all(&message).await?;
        send.finish()?;
        send.stopped().await?;
        conn.close(0u32.into(), b"done");
    }

    Ok(())
}