package org.bukkit.plugin.messaging;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;

/**
 * Standard implementation to {@link Messenger}
 */
public class StandardMessenger {
    private final Map<String, Set<PluginMessageListenerRegistration>> incomingByChannel = new HashMap<String, Set<PluginMessageListenerRegistration>>();
    private final Map<Plugin, Set<PluginMessageListenerRegistration>> incomingByPlugin = new HashMap<Plugin, Set<PluginMessageListenerRegistration>>();
    private final Map<String, Set<Plugin>> outgoingByChannel = new HashMap<String, Set<Plugin>>();
    private final Map<Plugin, Set<String>> outgoingByPlugin = new HashMap<Plugin, Set<String>>();
    private final Object incomingLock = new Object();
    private final Object outgoingLock = new Object();

    /**
     * Package-private method for testing: returns the number of channels registered for a plugin.
     * This exposes internal state to verify proper cleanup after unregistration.
     */
    int getOutgoingChannelCountForPlugin(Plugin plugin) {
        synchronized (outgoingLock) {
            Set<String> channels = outgoingByPlugin.get(plugin);
            return channels != null ? channels.size() : 0;
        }
    }

    /**
     * Package-private method for testing: checks if a channel still has an entry in the outgoing map.
     * Returns true if the map contains the channel key, even if the set is empty.
     * This exposes internal state to verify proper map cleanup.
     */
    boolean hasOutgoingChannelMapEntry(String channel) {
        synchronized (outgoingLock) {
            return outgoingByChannel.containsKey(channel);
        }
    }

    /**
     * Package-private method for testing: checks if a plugin still has an entry in the outgoing map.
     * Returns true if the map contains the plugin key, even if the set is empty.
     * This exposes internal state to verify proper map cleanup.
     */
    boolean hasOutgoingPluginMapEntry(Plugin plugin) {
        synchronized (outgoingLock) {
            return outgoingByPlugin.containsKey(plugin);
        }
    }

    private void removeFromOutgoing(@NotNull Plugin plugin, @NotNull String channel) {
        synchronized (outgoingLock) {
            Set<Plugin> plugins = outgoingByChannel.get(channel);
            Set<String> channels = outgoingByPlugin.get(plugin);

            if (plugins != null) {
                plugins.remove(plugin);

                if (plugins.isEmpty()) {
                    outgoingByChannel.remove(channel);
                }
            }

            if (channels != null) {
                channels.remove(channel);

                if (channels.isEmpty()) {
                    // Old version:
                    // outgoingByChannel.remove(channel);

                    // New version:
                    outgoingByPlugin.remove(plugin);
                }
            }
        }
    }
}