use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::config::AppConfig;
use crate::message::ServerMessage;
use crate::room::{ConnectedClient, Room};
#[derive(Clone)]
pub struct AppState {
pub rooms: Arc<RwLock<HashMap<String, Room>>>,
pub config: AppConfig,
pub started_at: Instant,
}
impl AppState {
pub fn new(config: AppConfig) -> Self {
Self {
rooms: Arc::new(RwLock::new(HashMap::new())),
config,
started_at: Instant::now(),
}
}
pub async fn join_room(
&self,
room_name: &str,
client: ConnectedClient,
) -> Result<(), String> {
let mut rooms = self.rooms.write().await;
if rooms.len() >= self.config.limits.max_rooms && !rooms.contains_key(room_name) {
return Err("max rooms reached".into());
}
let room = rooms
.entry(room_name.to_string())
.or_insert_with(|| Room::new(room_name.to_string()));
if room.clients.len() >= self.config.limits.max_clients {
return Err("room is full".into());
}
room.clients.insert(client.id, client);
Ok(())
}
pub async fn leave_room(&self, room_name: &str, client_id: Uuid) -> Option<String> {
let mut rooms = self.rooms.write().await;
let room = rooms.get_mut(room_name)?;
let client = room.clients.remove(&client_id)?;
let username = client.username.clone();
if room.clients.is_empty() {
rooms.remove(room_name);
}
Some(username)
}
pub async fn broadcast(&self, room_name: &str, msg: &ServerMessage, exclude_id: Option<Uuid>) {
let mut rooms = self.rooms.write().await;
if let Some(room) = rooms.get_mut(room_name) {
room.broadcast(msg, exclude_id);
}
}
pub async fn send_private(
&self,
from: &str,
to_username: &str,
text: &str,
) -> Result<(), String> {
let rooms = self.rooms.read().await;
for room in rooms.values() {
if let Some(client) = room.find_client_by_username(to_username) {
let msg = ServerMessage::Private {
from: from.into(),
text: text.into(),
};
let payload = serde_json::to_string(&msg).map_err(|e| e.to_string())?;
client.sender.try_send(payload).map_err(|_| String::from("failed to send"))?;
return Ok(());
}
}
Err("user not found".into())
}
pub async fn stats(&self) -> serde_json::Value {
let rooms = self.rooms.read().await;
let total_clients: usize = rooms.values().map(|r| r.clients.len()).sum();
let rooms_detail: Vec<serde_json::Value> = rooms
.values()
.map(|r| {
serde_json::json!({
"name": r.name,
"clients": r.clients.len(),
"users": r.clients.values().map(|c| &c.username).collect::<Vec<_>>(),
})
})
.collect();
serde_json::json!({
"rooms": rooms.len(),
"clients": total_clients,
"detail": rooms_detail,
})
}
}