-- MUC Max Occupants
-- Configuring muc_max_occupants will set a limit of the maximum number
-- of participants that will be able to join in a room.
-- Participants in muc_access_whitelist will not be counted for the
-- max occupants value (values are jids like recorder@jitsi.meeet.example.com).
-- This module is configured under the muc component that is used for jitsi-meet
local split_jid = require "util.jid".split;
local st = require "util.stanza";
local it = require "util.iterators";
local is_healthcheck_room = module:require "util".is_healthcheck_room;

local whitelist = module:get_option_set("muc_access_whitelist");
local MAX_OCCUPANTS = module:get_option_number("muc_max_occupants", -1);

local function count_keys(t)
  return it.count(it.keys(t));
end

local function check_for_max_occupants(event)
  local room, origin, stanza = event.room, event.origin, event.stanza;
  local user, domain, res = split_jid(stanza.attr.from);

  if is_healthcheck_room(room.jid) then
    return;
  end

  --no user object means no way to check for max occupants
  if user == nil then
    return
  end
  -- If we're a whitelisted user joining the room, don't bother checking the max
  -- occupants.
  if whitelist and (whitelist:contains(domain) or whitelist:contains(user..'@'..domain)) then
    return;
  end

	if room and not room._jid_nick[stanza.attr.from] then
        local max_occupants_by_room = event.room._data.max_occupants;
		local count = count_keys(room._occupants);
        -- if no of occupants limit is set per room basis use
        -- that settings otherwise use the global one
        local slots = max_occupants_by_room or MAX_OCCUPANTS;

		-- If there is no whitelist, just check the count.
		if not whitelist and count >= slots then
			module:log("info", "Attempt to enter a maxed out room: %s", room.jid);
			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
			return true;
		end

		-- TODO: Are Prosody hooks atomic, or is this a race condition?
		-- For each person in the room that's not on the whitelist, subtract one
		-- from the count.
		for _, occupant in room:each_occupant() do
			user, domain, res = split_jid(occupant.bare_jid);
			if not whitelist or (not whitelist:contains(domain) and not whitelist:contains(user..'@'..domain)) then
				slots = slots - 1
			end
		end

		-- If the room is full (<0 slots left), error out.
		if slots <= 0 then
			module:log("info", "Attempt to enter a maxed out room:%s", room.jid);
			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
			return true;
		end
	end
end

if MAX_OCCUPANTS > 0 then
	module:hook("muc-occupant-pre-join", check_for_max_occupants, 10);
end
