package tc.oc.pgm.freeze;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;

import com.sk89q.minecraft.util.commands.CommandException;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import tc.oc.commons.bukkit.channels.AdminChannel;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.BukkitSound;
import tc.oc.commons.bukkit.chat.NameStyle;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.commands.CommandUtils;
import tc.oc.commons.bukkit.freeze.FrozenPlayer;
import tc.oc.commons.bukkit.freeze.PlayerFreezer;
import tc.oc.commons.bukkit.nick.Identity;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.bukkit.util.OnlinePlayerMapAdapter;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.chat.Components;
import tc.oc.commons.core.commands.ComponentCommandException;
import tc.oc.commons.core.plugin.PluginFacet;

@Singleton
public class Freeze implements PluginFacet {

    public static final String PERMISSION = "projectares.freeze";

    private static final BukkitSound FREEZE_SOUND = new BukkitSound(Sound.ENTITY_ENDERDRAGON_GROWL, 1f, 1f);
    private static final BukkitSound THAW_SOUND = new BukkitSound(Sound.ENTITY_ENDERDRAGON_GROWL, 1f, 2f);

    private final IdentityProvider identityProvider;
    private final PlayerFreezer playerFreezer;
    private final Audiences audiences;
    private final FreezeConfig config;
    private final OnlinePlayerMapAdapter<FrozenPlayer> frozenPlayers;
    private final AdminChannel adminChannel;

    @Inject Freeze(IdentityProvider identityProvider, PlayerFreezer playerFreezer, Audiences audiences, FreezeConfig config, OnlinePlayerMapAdapter<FrozenPlayer> frozenPlayers, AdminChannel adminChannel) {
        this.identityProvider = identityProvider;
        this.playerFreezer = playerFreezer;
        this.audiences = audiences;
        this.config = config;
        this.frozenPlayers = frozenPlayers;
        this.adminChannel = adminChannel;
    }

    @Override
    public void enable() {
        frozenPlayers.enable();
    }

    @Override
    public void disable() {
        frozenPlayers.disable();
    }

    public boolean enabled() {
        return config.enabled();
    }

    public boolean isFrozen(Entity player) {
        return player instanceof Player && frozenPlayers.containsKey(player);
    }

    public void setFrozen(@Nullable CommandSender freezer, Player freezee, boolean frozen) throws CommandException {
        if(!freezee.equals(freezer) && freezee.hasPermission("projectares.freeze.exempt")) {
            throw new ComponentCommandException(new TranslatableComponent(
                "command.freeze.exempt",
                new PlayerComponent(identityProvider.currentIdentity(freezee), NameStyle.VERBOSE)
            ));
        }

        final Identity freezerIdentity = identityProvider.createIdentity(freezer);
        final Audience freezeeAudience = audiences.get(freezee);

        final FrozenPlayer frozenPlayer = frozenPlayers.get(freezee);
        if(frozen && frozenPlayer == null) {
            frozenPlayers.put(freezee, playerFreezer.freeze(freezee));

            final BaseComponent freezeeMessage = new Component(
                new TranslatableComponent(
                    "freeze.frozen",
                    new PlayerComponent(freezerIdentity, NameStyle.FANCY)
                ),
                ChatColor.RED
            );

            freezeeAudience.playSound(FREEZE_SOUND);
            freezeeAudience.sendWarning(freezeeMessage, false);
            freezeeAudience.showTitle(Components.blank(), freezeeMessage, 5, 9999, 5);

            removeEntities(freezee.getLocation(), config.tntVictimRadius());

            if(freezer instanceof Player) {
                removeEntities(((Player) freezer).getLocation(), config.tntSenderRadius());
            }

            adminChannel.broadcast(CommandUtils.getDisplayName(freezer) +
                                   ChatColor.RED + " froze " +
                                   CommandUtils.getDisplayName(freezee));
        } else if(!frozen && frozenPlayer != null) {
            frozenPlayer.thaw();
            frozenPlayers.remove(freezee);

            freezeeAudience.hideTitle();
            freezeeAudience.playSound(THAW_SOUND);
            freezeeAudience.sendMessage(new Component(
                new TranslatableComponent(
                    "freeze.unfrozen",
                    new PlayerComponent(freezerIdentity, NameStyle.FANCY)
                ),
                ChatColor.GREEN
            ));

            adminChannel.broadcast(CommandUtils.getDisplayName(freezer) +
                                   ChatColor.RED + " unfroze " +
                                   CommandUtils.getDisplayName(freezee));
        }
    }

    // Borrowed from WorldEdit
    private void removeEntities(Location origin, double radius) {
        if(radius <= 0) return;

        double radiusSq = radius * radius;
        for(Entity ent : origin.getWorld().getEntities()) {
            if(origin.distanceSquared(ent.getLocation()) > radiusSq)
                continue;

            if(ent instanceof TNTPrimed) {
                ent.remove();
            }
        }
    }

    /** Toggle the player's frozen status.
     * @return Boolean indicating whether the player is now frozen or not.
     */
    public boolean toggleFrozen(CommandSender freezer, Player freezee) throws CommandException {
        if(enabled()) {
            boolean frozen = !isFrozen(freezee);
            setFrozen(freezer, freezee, frozen);
            return frozen;
        } else {
            return false;
        }
    }
}
