package tc.oc.pgm.doublejump;

import java.util.Iterator;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;

import org.bukkit.GameMode;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerToggleFlightEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
import tc.oc.commons.bukkit.util.OnlinePlayerMapAdapter;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.PlayerResetEvent;
import tc.oc.pgm.match.MatchModule;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchScope;

@ListenerScope(MatchScope.RUNNING)
public class DoubleJumpMatchModule extends MatchModule implements Listener {
    private class Jumper {
        final Player player;
        final DoubleJumpKit kit;
        float charge;

        private Jumper(Player player, DoubleJumpKit kit) {
            this.player = player;
            this.kit = kit;
        }
    }

    private final OnlinePlayerMapAdapter<Jumper> jumpers;

    @Inject DoubleJumpMatchModule(Plugin plugin) {
        this.jumpers = new OnlinePlayerMapAdapter<>(plugin);
        this.jumpers.enable();
    }

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

        getMatch().getScheduler(MatchScope.LOADED).createRepeatingTask(1, new Runnable() {
            public void run() {
                for(Map.Entry<Player, Jumper> entry : jumpers.entrySetCopy()) {
                    Player player = entry.getKey();
                    Jumper jumper = entry.getValue();
                    if(player.isOnGround() || jumper.kit.rechargeInAir || jumper.charge > 0f) {
                        setCharge(jumper, jumper.charge + jumper.kit.chargePerTick());
                        refreshJump(player);
                    }
                }
            }
        });
    }

    @Override
    public void disable() {
        this.disableAll();

        super.disable();
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onPlayerReset(PlayerResetEvent event) {
        this.setKit(event.getPlayer().getBukkit(), null);
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onPlayerDeath(PlayerDeathEvent event) {
        this.setKit(event.getEntity(), null);
    }

    public void setKit(Player player, @Nullable DoubleJumpKit kit) {
        if(kit != null && kit.enabled) {
            Jumper jumper = new Jumper(player, kit);
            this.jumpers.put(player, jumper);
            this.setCharge(jumper, 1f);
        } else {
            this.jumpers.remove(player);
            this.refreshJump(player);
        }
    }

    public void disableAll() {
        for(Iterator<Player> iterator = this.jumpers.keySet().iterator(); iterator.hasNext(); ) {
            Player player = iterator.next();
            iterator.remove();
            this.refreshJump(player);
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onPlayerToggleFlight(final PlayerToggleFlightEvent event) {
        Player player = event.getPlayer();
        Jumper jumper = this.jumpers.get(player);
        if(jumper == null) return;

        if(event.isFlying()) {
            event.setCancelled(true);

            this.setCharge(jumper, 0f);
            this.refreshJump(player);

            // calculate jump
            Vector impulse = player.getLocation().getDirection();

            impulse.setY(0.75 + Math.abs(impulse.getY()) * 0.5);
            impulse.multiply(jumper.kit.power / 3f);
            event.getPlayer().applyImpulse(impulse, true);

            player.getWorld().playSound(player.getLocation(), Sound.ENTITY_ZOMBIE_INFECT, 0.5f, 1.8f);
        }
    }

    private void setCharge(Jumper jumper, float charge) {
        if(jumper.kit.needsRecharge()) {
            charge = Math.min(charge, 1.0f);
            if(charge != jumper.charge) {
                jumper.charge = charge;
                jumper.player.setExp(jumper.charge);
            }
        }
    }

    private void refreshJump(Player player) {
        if(player.getGameMode() != GameMode.CREATIVE) {
            player.setAllowFlight(this.canJump(player));
        }
    }

    private boolean canJump(Player player) {
        Jumper jumper = this.jumpers.get(player);
        return jumper != null && (!jumper.kit.needsRecharge() || jumper.charge >= 1f);
    }

    public boolean hasKit(MatchPlayer player) {
        return jumpers.containsKey(player.getBukkit());
    }
}
