-module(chatroom).
-include_lib("./defs.hrl").
-export([start_chatroom/1]).
-spec start_chatroom(_ChatName) -> _.
-spec loop(_State) -> _.
-spec do_register(_State, _Ref, _ClientPID, _ClientNick) -> _NewState.
-spec do_unregister(_State, _ClientPID) -> _NewState.
-spec do_update_nick(_State, _ClientPID, _NewNick) -> _NewState.
-spec do_propegate_message(_State, _Ref, _ClientPID, _Message) -> _NewState.
start_chatroom(ChatName) ->
loop(#chat_st{name = ChatName,
registrations = maps:new(), history = []}),
ok.
loop(State) ->
NewState =
receive
%% Server tells this chatroom to register a client
{_ServerPID, Ref, register, ClientPID, ClientNick} ->
do_register(State, Ref, ClientPID, ClientNick);
%% Server tells this chatroom to unregister a client
{_ServerPID, _Ref, unregister, ClientPID} ->
do_unregister(State, ClientPID);
%% Server tells this chatroom to update the nickname for a certain client
{_ServerPID, _Ref, update_nick, ClientPID, NewNick} ->
do_update_nick(State, ClientPID, NewNick);
%% Client sends a new message to the chatroom, and the chatroom must
%% propegate to other registered clients
{ClientPID, Ref, message, Message} ->
do_propegate_message(State, Ref, ClientPID, Message);
{TEST_PID, get_state} ->
TEST_PID!{get_state, State},
loop(State)
end,
loop(NewState).
%% This function should register a new client to this chatroom
do_register(State, Ref, ClientPID, ClientNick) ->
case maps:is_key(ClientPID, State#chat_st.registrations) of
true ->
ClientPID!{self(), Ref, connect, State#chat_st.history},
State;
false ->
UpdatedRegistrations = maps:put(ClientPID, ClientNick, State#chat_st.registrations),
ClientPID!{self(), Ref, connect, State#chat_st.history},
State#chat_st{registrations = UpdatedRegistrations}
end.
%% This function should unregister a client from this chatroom
do_unregister(State, ClientPID) ->
case maps:is_key(ClientPID, State#chat_st.registrations) of
false ->
State;
true ->
UpdatedRegistrations = maps:remove(ClientPID, State#chat_st.registrations),
State#chat_st{registrations = UpdatedRegistrations}
end.
%% This function should update the nickname of specified client.
do_update_nick(State, ClientPID, NewNick) ->
case maps:is_key(ClientPID, State#chat_st.registrations) of
false ->
State;
true ->
UpdatedRegistrations = maps:put(ClientPID, NewNick, State#chat_st.registrations),
lists:foreach(
fun(OtherClientPID) ->
OtherClientPID ! {self(), NewNick}
end,
maps:keys(UpdatedRegistrations)
),
State#chat_st{registrations = UpdatedRegistrations}
end.
%% This function should update all clients in chatroom with new message
%% (read assignment specs for details)
do_propegate_message(State, Ref, ClientPID, Message) ->
% Notify the sender that their message has been acknowledged.
ClientPID!{self(), Ref, ack_msg},
{ok, ClientNick} = maps:find(ClientPID, State#chat_st.registrations),
% Send the message to everyone else in the chatroom.
OtherClients = [Pid || Pid <- maps:keys(State#chat_st.registrations), Pid =/= ClientPID],
lists:foreach( % Personal comment, there's gotta be a better way than O(n) to do this, but this works
fun(Pid) ->
Pid ! {request, self(), Ref,
{incoming_msg, ClientNick, State#chat_st.name, Message}}
end,
OtherClients
),
% Add message to the message history
UpdatedHistory = [{ClientNick, Message} | State#chat_st.history],
State#chat_st{history = UpdatedHistory}.