parent root
object user_db

var root name 'user_db
var user_db user_names #[]
var user_db users []
var user_db connected_users []

eval
    .initialize();
.

method user_created
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();
    users = [@users, user];
    user_names = dict_add(user_names, user.vr_name(), user);
.

method user_changed_name
    arg old_name;
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();
    user_names = dict_del(user_names, old_name);
    user_names = dict_add(user_names, user.vr_name(), user);
.

method user_going_away
    var user;

    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    user = sender();
    user_names = dict_del(user_names, user.vr_name());
    users = setremove(users, user);
    connected_users = setremove(connected_users, user);
.

method user_connected
    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    connected_users = [@connected_users, sender()];
.

method user_disconnected
    if (caller() != $user)
        throw(~perm, "Caller is not $user.");
    connected_users = setremove(connected_users, sender());
.

method users
    return users;
.

method connected_users
    return connected_users;
.

method find_user
    arg name;

    catch ~keynf {
        return user_names[name];
    } with handler {
        throw(~usernf, "User name " + toliteral(name) + " not found.");
    }
.