using SharpChat.Events; using SharpChat.Messages; using SharpChat.Sessions; using SharpChat.Users; using System; using System.Collections.Generic; using System.Linq; namespace SharpChat.Channels { public class ChannelUserRelations : IEventHandler { private IEventDispatcher Dispatcher { get; } private ChannelManager Channels { get; } private UserManager Users { get; } private SessionManager Sessions { get; } private MessageManager Messages { get; } public ChannelUserRelations( IEventDispatcher dispatcher, ChannelManager channels, UserManager users, SessionManager sessions, MessageManager messages ) { Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); Channels = channels ?? throw new ArgumentNullException(nameof(channels)); Users = users ?? throw new ArgumentNullException(nameof(users)); Sessions = sessions ?? throw new ArgumentNullException(nameof(sessions)); Messages = messages ?? throw new ArgumentNullException(nameof(messages)); } public void HasUser(IChannel channel, IUser user, Action callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => { if(c is not Channel channel) { callback(false); return; } callback(channel.HasUser(user)); }); } public void HasSession(IChannel channel, ISession session, Action callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(session == null) throw new ArgumentNullException(nameof(session)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => { if(c is not Channel channel) { callback(false); return; } callback(channel.HasSession(session)); }); } public void CountUsers(IChannel channel, Action callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => { if(c is not Channel channel) { callback(-1); return; } callback(channel.CountUsers()); }); } public void CountUserSessions(IChannel channel, IUser user, Action callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => { if(c is not Channel channel) { callback(-1); return; } callback(channel.CountUserSessions(user)); }); } public void CheckOverCapacity(IChannel channel, IUser user, Action callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, channel => { if(channel == null) { callback(true); return; } if(!channel.HasMaxCapacity() || user.UserId == channel.OwnerId) { callback(false); return; } CountUsers(channel, userCount => callback(channel == null || userCount >= channel.MaxCapacity)); }); } public void GetUsersByChannelId(string channelId, Action> callback) { if(channelId == null) throw new ArgumentNullException(nameof(channelId)); if(callback == null) throw new ArgumentNullException(nameof(callback)); if(string.IsNullOrWhiteSpace(channelId)) { callback(Enumerable.Empty()); return; } Channels.GetChannelById(channelId, c => GetUsersWithChannelCallback(c, callback)); } public void GetUsersByChannelName(string channelName, Action> callback) { if(channelName == null) throw new ArgumentNullException(nameof(channelName)); if(callback == null) throw new ArgumentNullException(nameof(callback)); if(string.IsNullOrWhiteSpace(channelName)) { callback(Enumerable.Empty()); return; } Channels.GetChannelByName(channelName, c => GetUsersWithChannelCallback(c, callback)); } public void GetUsers(IChannel channel, Action> callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => GetUsersWithChannelCallback(c, callback)); } private void GetUsersWithChannelCallback(IChannel c, Action> callback) { if(c is not Channel channel) { callback(Enumerable.Empty()); return; } channel.GetUserIds(ids => Users.GetUsers(ids, callback)); } public void GetUsers(IEnumerable channels, Action> callback) { if(channels == null) throw new ArgumentNullException(nameof(channels)); if(callback == null) throw new ArgumentNullException(nameof(callback)); // this is pretty disgusting Channels.GetChannels(channels, channels => { HashSet ids = new(); foreach(IChannel c in channels) { if(c is not Channel channel) continue; channel.GetUserIds(u => { foreach(long id in u) ids.Add(id); }); } Users.GetUsers(ids, callback); }); } // this makes me cry public void GetUsers(IUser user, Action> callback) { if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); HashSet all = new(); Channels.GetChannels(channels => { foreach(IChannel channel in channels) { GetUsers(channel, users => { foreach(ILocalUser user in users) all.Add(user); }); } }); callback(all); } public void GetLocalSessionsByChannelId(string channelId, Action> callback) { if(channelId == null) throw new ArgumentNullException(nameof(channelId)); if(callback == null) throw new ArgumentNullException(nameof(callback)); if(string.IsNullOrWhiteSpace(channelId)) { callback(Enumerable.Empty()); return; } Channels.GetChannelById(channelId, c => GetLocalSessionsChannelCallback(c, callback)); } public void GetLocalSessionsByChannelName(string channelName, Action> callback) { if(channelName == null) throw new ArgumentNullException(nameof(channelName)); if(callback == null) throw new ArgumentNullException(nameof(callback)); if(string.IsNullOrWhiteSpace(channelName)) { callback(Enumerable.Empty()); return; } Channels.GetChannelByName(channelName, c => GetLocalSessionsChannelCallback(c, callback)); } public void GetLocalSessions(IChannel channel, Action> callback) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Channels.GetChannel(channel, c => GetLocalSessionsChannelCallback(c, callback)); } private void GetLocalSessionsChannelCallback(IChannel c, Action> callback) { if(c is not Channel channel) { callback(Enumerable.Empty()); return; } channel.GetSessionIds(ids => Sessions.GetLocalSessions(ids, callback)); } public void GetLocalSessions(IUser user, Action> callback) { if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); GetChannels(user, channels => GetLocalSessionsUserCallback(channels, callback)); } public void GetLocalSessionsByUserId(long userId, Action> callback) { if(callback == null) throw new ArgumentNullException(nameof(callback)); if(userId < 1) { callback(null, Enumerable.Empty()); return; } GetChannelsByUserId(userId, (user, channels) => GetLocalSessionsUserCallback(channels, sessions => callback(user, sessions))); } private void GetLocalSessionsUserCallback(IEnumerable channels, Action> callback) { if(!channels.Any()) { callback(Enumerable.Empty()); return; } Channels.GetChannels(channels, channels => { HashSet sessionIds = new(); foreach(IChannel c in channels) { if(c is not Channel channel) continue; channel.GetSessionIds(ids => { foreach(string id in ids) sessionIds.Add(id); }); } Sessions.GetLocalSessions(sessionIds, callback); }); } public void GetChannelsByUserId(long userId, Action> callback) { if(callback == null) throw new ArgumentNullException(nameof(callback)); if(userId < 1) { callback(null, Enumerable.Empty()); return; } Users.GetUser(userId, u => GetChannelsUserCallback(u, channels => callback(u, channels))); } public void GetChannels(IUser user, Action> callback) { if(user == null) throw new ArgumentNullException(nameof(user)); if(callback == null) throw new ArgumentNullException(nameof(callback)); Users.GetUser(user, u => GetChannelsUserCallback(u, callback)); } private void GetChannelsUserCallback(IUser u, Action> callback) { if(u is not User user) { callback(Enumerable.Empty()); return; } user.GetChannels(c => Channels.GetChannelsByName(c, callback)); } public void JoinChannel(IChannel channel, ISession session) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(session == null) throw new ArgumentNullException(nameof(session)); HasSession(channel, session, hasSession => { if(hasSession) return; // SessionJoin and UserJoin should be combined HasUser(channel, session.User, HasUser => { Dispatcher.DispatchEvent( this, HasUser ? new ChannelSessionJoinEvent(channel, session) : new ChannelUserJoinEvent(channel, session) ); }); }); } public void LeaveChannel(IChannel channel, IUser user, UserDisconnectReason reason = UserDisconnectReason.Unknown) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(user == null) throw new ArgumentNullException(nameof(user)); HasUser(channel, user, hasUser => { if(hasUser) Dispatcher.DispatchEvent(this, new ChannelUserLeaveEvent(user, channel, reason)); }); } public void LeaveChannel(IChannel channel, ISession session) { if(channel == null) throw new ArgumentNullException(nameof(channel)); if(session == null) throw new ArgumentNullException(nameof(session)); HasSession(channel, session, hasSession => { // UserLeave and SessionLeave should be combined CountUserSessions(channel, session.User, sessionCount => { Dispatcher.DispatchEvent( this, sessionCount <= 1 ? new ChannelUserLeaveEvent(session.User, channel, UserDisconnectReason.Leave) : new ChannelSessionLeaveEvent(channel, session) ); }); }); } public void LeaveChannels(ISession session) { if(session == null) throw new ArgumentNullException(nameof(session)); Channels.GetChannels(channels => { foreach(IChannel channel in channels) LeaveChannel(channel, session); }); } public void HandleEvent(object sender, IEvent evt) { switch(evt) { case UserUpdateEvent uue: // fetch up to date user info GetChannelsByUserId(evt.UserId, (user, channels) => GetUsers(channels, users => { foreach(ILocalUser user in users) GetLocalSessions(user, sessions => { foreach(ISession session in sessions) session.HandleEvent(sender, new UserUpdateEvent(user, uue)); }); })); break; case ChannelUserJoinEvent cje: // THIS DOES NOT DO WHAT YOU WANT IT TO DO // I THINK // it really doesn't, figure out how to leave channels when MCHAN isn't active for the session //if((Sessions.GetCapabilities(cje.User) & ClientCapability.MCHAN) == 0) // LeaveChannel(cje.Channel, cje.User, UserDisconnectReason.Leave); break; case ChannelUserLeaveEvent cle: // Should ownership just be passed on to another user instead of Destruction? Channels.GetChannelById(evt.ChannelId, channel => { if(channel.IsTemporary && evt.UserId == channel.OwnerId) Channels.Remove(channel); }); break; case SessionDestroyEvent sde: Users.GetUser(sde.UserId, user => { if(user == null) return; Sessions.GetSessionCount(user, sessionCount => { if(sessionCount < 1) Users.Disconnect(user, UserDisconnectReason.TimeOut); }); }); break; } } } }