package tc.oc.pgm.channels;

import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;

import com.github.rmsy.channels.Channel;
import com.github.rmsy.channels.ChannelsPlugin;
import com.github.rmsy.channels.PlayerManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import tc.oc.chatmoderator.ChatModeratorPlugin;
import tc.oc.commons.bukkit.chat.ComponentRenderers;
import tc.oc.commons.bukkit.util.NullCommandSender;
import tc.oc.commons.bukkit.util.OnlinePlayerMapAdapter;
import tc.oc.commons.core.util.DefaultMapAdapter;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.PartyAddEvent;
import tc.oc.pgm.events.PartyRemoveEvent;
import tc.oc.pgm.events.PlayerJoinPartyEvent;
import tc.oc.pgm.events.PlayerLeavePartyEvent;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.MultiPlayerParty;
import tc.oc.pgm.match.Party;

@ListenerScope(MatchScope.LOADED)
public class ChannelMatchModule extends MatchModule implements Listener {

    public static final String RECEIVE_ALL_PERMISSION = "pgm.chat.all.receive";
    public static final String TEAM_RECEIVE_PERMISSION = "pgm.chat.team.receive";
    public static final String TEAM_SEND_PERMISSION = "pgm.chat.team.send";

    // This dynamic permission has all party channel listening permissions as children.
    // A player with this permission receives all channels simultaneously. It is granted
    // automatically to observers that have the RECEIVE_ALL_PERMISSION.
    private final Permission matchListeningPermission;

    private final ChannelsPlugin channelsPlugin = ChannelsPlugin.get();
    private final Map<MultiPlayerParty, PartyChannel> partyChannels = new HashMap<>();

    // This is used to keep track of players' global/team chat preference. We can't just
    // check their channel through the Channels plugin because FFA players always use the
    // global channel. All players are set to team chat when they join a match, and the /t
    // command sets them to team chat. They can switch to global chat with /g, but we don't
    // know when this happens, so we check for it whenever they switch parties.
    private final Map<Player, Boolean> teamChatters;

    @Inject ChannelMatchModule(Match match, Plugin plugin) {
        this.matchListeningPermission = new Permission("pgm.chat.all." + match.getId() + ".receive", PermissionDefault.FALSE);

        final OnlinePlayerMapAdapter<Boolean> map = new OnlinePlayerMapAdapter<>(plugin);
        map.enable();
        this.teamChatters = new DefaultMapAdapter<>(map, true, false);
    }

    protected void updatePlayerChannel(MatchPlayer player) {
        if(teamChatters.get(player.getBukkit())) {
            channelsPlugin.getPlayerManager().setMembershipChannel(player.getBukkit(), getChannel(player.getParty()));
        } else {
            channelsPlugin.getPlayerManager().setMembershipChannel(player.getBukkit(), channelsPlugin.getGlobalChannel());
        }
    }

    public void setTeamChat(MatchPlayer player, boolean teamChat) {
        teamChatters.put(player.getBukkit(), teamChat);
        updatePlayerChannel(player);
    }

    public Channel getChannel(Party party) {
        if(party instanceof MultiPlayerParty) {
            return partyChannels.get(party);
        } else {
            return channelsPlugin.getGlobalChannel();
        }
    }

    protected Permission createChannelPermission(Party party) {
        Permission permission = new Permission("pgm.chat.team." + this.match.getId() + '-' + party.hashCode() + ".receive", PermissionDefault.FALSE);
        getMatch().getPluginManager().addPermission(permission);
        permission.addParent(matchListeningPermission, true);
        return permission;
    }

    protected void removeChannelPermission(Channel channel) {
        matchListeningPermission.getChildren().remove(channel.getListeningPermission().getName());
        matchListeningPermission.recalculatePermissibles();
        getMatch().getPluginManager().removePermission(channel.getListeningPermission());
    }

    protected void createChannel(Party party) {
        if(party instanceof MultiPlayerParty) {
            logger.fine("Creating channel for " + party);

            String format = ComponentRenderers.toLegacyText(party.getChatPrefix(), NullCommandSender.INSTANCE) + "{1}§f: {3}";

            PartyChannel channel;
            if (getMatch().getPluginManager().getPlugin("ChatModerator") == null) {
                channel = new UnfilteredPartyChannel(format,
                                                     createChannelPermission(party),
                                                     party);
            } else {
                channel = new FilteredPartyChannel(format,
                                                   createChannelPermission(party),
                                                   party,
                                                   ChatModeratorPlugin.MINIMUM_SCORE_NO_SEND,
                                                   ChatModeratorPlugin.PARTIALLY_OFFENSIVE_RATIO);
            }

            if(partyChannels.put((MultiPlayerParty) party, channel) != null) {
                throw new IllegalStateException("Party added multiple times");
            }
        }
    }

    @Override
    public void load() {
        super.load();

        // Let the console receive all channels
        match.getPluginManager().addPermission(matchListeningPermission);
        getMatch().getServer().getConsoleSender().addAttachment(getMatch().getPlugin()).setPermission(matchListeningPermission, true);

        // Parties may be created before the module loads
        for(Party party : getMatch().getParties()) {
            createChannel(party);
        }
    }

    @Override
    public void unload() {
        getMatch().getServer().getConsoleSender().removeAttachments(matchListeningPermission);
        getMatch().getPluginManager().removePermission(matchListeningPermission);

        super.unload();
    }

    @EventHandler(priority = EventPriority.LOWEST)
    public void partyAdd(final PartyAddEvent event) {
        createChannel(event.getParty());
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void partyRemove(final PartyRemoveEvent event) {
        if(event.getParty() instanceof MultiPlayerParty) {
            PartyChannel channel = partyChannels.remove(event.getParty());
            if(channel != null) {
                removeChannelPermission(channel);
            }
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void partyJoin(PlayerJoinPartyEvent event) {
        if(event.getNewParty() instanceof MultiPlayerParty) {
            PlayerManager playerManager = channelsPlugin.getPlayerManager();
            Player bukkitPlayer = event.getPlayer().getBukkit();
            PartyChannel channel = partyChannels.get(event.getNewParty());

            if(channel != null) {
                if(event.getNewParty().isObservingType() && bukkitPlayer.hasPermission(RECEIVE_ALL_PERMISSION)) {
                    // If the player is joining observers and they have the receive-all perm, let them listen to all channels
                    bukkitPlayer.addAttachment(getMatch().getPlugin()).setPermission(matchListeningPermission, true);
                } else if(bukkitPlayer.hasPermission(TEAM_RECEIVE_PERMISSION)) {
                    // Give the player listening permission for the team's channel
                    bukkitPlayer.addAttachment(getMatch().getPlugin()).setPermission(channel.getListeningPermission(), true);
                }

                // If their sending channel was previously set to a team channel, switch it to the new team's channel
                if(playerManager.getMembershipChannel(bukkitPlayer) instanceof PartyChannel) {
                    playerManager.setMembershipChannel(bukkitPlayer, channel);
                }
            }
        }

        updatePlayerChannel(event.getPlayer());
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void partyLeave(PlayerLeavePartyEvent event) {
        if(event.getOldParty() instanceof MultiPlayerParty) {
            PlayerManager playerManager = channelsPlugin.getPlayerManager();
            Player bukkitPlayer = event.getPlayer().getBukkit();
            PartyChannel channel = partyChannels.get(event.getOldParty());

            if(channel != null) {
                bukkitPlayer.removeAttachments(channel.getListeningPermission());
                bukkitPlayer.removeAttachments(matchListeningPermission);

                // Whenever the player leaves a party with its own channel, check if that is the player's current channel,
                // and if it's not, then set their team chat setting to false. This is the only way to find out when they
                // switch to global chat, because the Channels plugin doesn't provide any notifcation.
                if(playerManager.getMembershipChannel(bukkitPlayer) != channel) {
                    teamChatters.put(bukkitPlayer, false);
                }
            }
        }
    }
}
