package tc.oc.pgm.raindrops;

import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import com.google.common.collect.HashMultimap;
import com.google.common.primitives.Ints;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDespawnInVoidEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Wool;
import tc.oc.api.docs.PlayerId;
import tc.oc.commons.bukkit.raindrops.RaindropConstants;
import tc.oc.commons.bukkit.raindrops.RaindropUtil;
import tc.oc.commons.core.chat.Component;
import tc.oc.commons.core.util.Comparables;
import tc.oc.pgm.PGM;
import tc.oc.pgm.destroyable.DestroyableContribution;
import tc.oc.pgm.destroyable.DestroyableDestroyedEvent;
import tc.oc.pgm.events.ListenerScope;
import tc.oc.pgm.events.MatchEndEvent;
import tc.oc.pgm.events.MatchPlayerDeathEvent;
import tc.oc.pgm.goals.Goal;
import tc.oc.pgm.goals.GoalMatchModule;
import tc.oc.pgm.goals.TouchableGoal;
import tc.oc.pgm.goals.events.GoalCompleteEvent;
import tc.oc.pgm.goals.events.GoalTouchEvent;
import tc.oc.pgm.match.Competitor;
import tc.oc.pgm.match.Match;
import tc.oc.pgm.match.MatchPlayer;
import tc.oc.pgm.match.MatchPlayerState;
import tc.oc.pgm.match.MatchScope;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.teams.Team;
import tc.oc.pgm.teams.TeamMatchModule;
import tc.oc.pgm.victory.VictoryMatchModule;
import tc.oc.pgm.wool.MonumentWool;
import tc.oc.pgm.wool.MonumentWoolFactory;
import tc.oc.pgm.wool.PlayerWoolPlaceEvent;

@ListenerScope(MatchScope.LOADED)
public class RaindropListener implements Listener {

    private final HashMultimap<MonumentWool, PlayerId> touchedWools = HashMultimap.create();
    private final Map<Item, PlayerId> droppedWools = new WeakHashMap<>();
    private final HashMultimap<DyeColor, PlayerId> destroyedWools = HashMultimap.create();
    private final HashMultimap<TouchableGoal, ParticipantState> deferredTouches = HashMultimap.create();

    private double tiedRewardPercent(Match match) {
        return 1.0d / match.needMatchModule(TeamMatchModule.class).getTeams().size();
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void handleItemDrop(final PlayerDropItemEvent event) {
        ParticipantState player = PGM.getMatchManager().getParticipantState(event.getPlayer());
        if(player == null) return;

        Competitor team = player.getParty();
        Item itemDrop = event.getItemDrop();
        ItemStack item = itemDrop.getItemStack();

        if (this.isDestroyableWool(item, team)) {
            this.droppedWools.put(itemDrop, player.getPlayerId());
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void handleItemDespawn(final EntityDespawnInVoidEvent event) {
        Entity entity = event.getEntity();
        if (!(entity instanceof Item)) return;
        ItemStack stack = ((Item) entity).getItemStack();

        PlayerId playerId = this.droppedWools.remove(entity);
        if (playerId == null) return;

        ParticipantState player = PGM.getMatchManager().getParticipantState(playerId);
        if (player == null) return;

        if(isDestroyableWool(stack, player.getParty())) {
            giveWoolDestroyRaindrops(player, ((Wool) stack.getData()).getColor());
        }
    }

    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void handleCraft(final CraftItemEvent event) {
        ParticipantState player = PGM.getMatchManager().getParticipantState(event.getWhoClicked());
        if (player == null) return;

        for (ItemStack ingredient : event.getInventory().getMatrix()) {
            if(this.isDestroyableWool(ingredient, player.getParty())) {
                giveWoolDestroyRaindrops(player, ((Wool) ingredient.getData()).getColor());
            }
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void handleMatchEndEvent(final MatchEndEvent event) {
        final Set<Competitor> winners = event.getMatch().needMatchModule(VictoryMatchModule.class).winners();
        Match match = event.getMatch();

        boolean applyCutoff = Comparables.greaterThan(match.getLength(), RaindropConstants.TEAM_REWARD_CUTOFF);
        for(MatchPlayer player : match.getParticipatingPlayers()) {
            if(player.getParty() instanceof Team) {
                Team team = (Team) player.getParty();
                Duration teamTime = team.getCumulativeParticipation(player.getPlayerId());
                if(!(applyCutoff && Comparables.lessThan(teamTime, RaindropConstants.TEAM_REWARD_CUTOFF))) {
                    Component message = new Component(net.md_5.bungee.api.ChatColor.GRAY);
                    double percent = 1.0;
                    Competitor playerTeam = player.getCompetitor();
                    assert playerTeam != null;

                    if(winners.contains(playerTeam)) {
                        if(winners.size() == 1) {
                            message.extra(new TranslatableComponent("matchend.team.won", playerTeam.getComponentName()));
                        } else {
                            message.extra(new TranslatableComponent("matchend.team.tied", playerTeam.getComponentName()));
                            percent = tiedRewardPercent(match);
                        }
                    } else {
                        message.extra(new TranslatableComponent("matchend.team.loyalty",
                                                                playerTeam.getComponentName(),
                                                                new Component(String.valueOf(teamTime.toMinutes()))));
                        percent = RaindropConstants.LOSING_TEAM_REWARD_PERCENT;
                    }

                    givePercentageRaindrops(player.getParticipantState(), RaindropConstants.TEAM_REWARD, message, true, percent);
                }
            }
        }

        this.touchedWools.clear();
        this.droppedWools.clear();
        this.destroyedWools.clear();
        this.deferredTouches.clear();
    }

    private void giveGoalTouchRaindrops(ParticipantState player, TouchableGoal goal) {
        this.giveRaindrops(player,
                           RaindropConstants.TOUCH_GOAL_REWARD,
                           goal.getTouchMessage(player, true));
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void handleGoalTouch(final GoalTouchEvent event) {
        Goal goal = event.getGoal();

        if (!goal.isVisible() || goal.isCompleted() || event.getPlayer() == null || !event.isFirstForPlayerLife()) {
            return;
        }

        if (goal instanceof MonumentWool) {
            PlayerId playerId = event.getPlayer().getPlayerId();
            if (this.touchedWools.containsEntry(goal, playerId)) {
                return;
            } else {
                this.touchedWools.put((MonumentWool) goal, playerId);
            }
        }

        if(event.getGoal().getDeferTouches()) {
            this.deferredTouches.put(event.getGoal(), event.getPlayer());
        } else {
            this.giveGoalTouchRaindrops(event.getPlayer(), event.getGoal());
            event.setCancelToucherMessage(true);
        }
    }

    @EventHandler
    public void handleGoalComplete(GoalCompleteEvent event) {
        if(event.getGoal() instanceof TouchableGoal) {
            TouchableGoal goal = (TouchableGoal) event.getGoal();
            for(ParticipantState player : this.deferredTouches.get(goal)) {
                this.giveGoalTouchRaindrops(player, goal);
            }
            this.deferredTouches.removeAll(goal);
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void woolPlace(final PlayerWoolPlaceEvent event) {
        if (event.getWool().isVisible()) {
            giveRaindrops(event.getPlayer().getPlayerId(), RaindropConstants.WOOL_PLACE_REWARD, new TranslatableComponent("match.wool.place", event.getWool().getComponentName()));
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void destroyableDestroy(final DestroyableDestroyedEvent event) {
        if (event.getDestroyable().isVisible()) {
            for (DestroyableContribution info : event.getDestroyable().getContributions()) {
                String percentage = ChatColor.GREEN.toString() + ((int) (info.getPercentage() * 100)) + ChatColor.GRAY;
                BaseComponent reason = new TranslatableComponent("match.destroyable.destroy", percentage, new Component(event.getDestroyable().getName(), net.md_5.bungee.api.ChatColor.GREEN));

                giveRaindrops(info.getPlayerState().getPlayerId(), (int) (RaindropConstants.DESTROYABLE_DESTROY_PERCENT_REWARD * info.getPercentage()), reason);
            }
        }
    }

    @EventHandler(priority = EventPriority.NORMAL)
    public void kill(final MatchPlayerDeathEvent event) {
        if(!event.isChallengeKill()) return;

        // Death raindrops are given by the backend, to reduce API usage

        final int raindrops = calculateRaindrops(
            event.getKiller(),
            RaindropConstants.KILL_REWARD,
            false,
            1
        );

        event.setRaindrops(raindrops);

        final MatchPlayer killer = event.getOnlineKiller();
        if(killer != null) {
            RaindropUtil.showRaindrops(
                killer.getBukkit(),
                raindrops,
                RaindropUtil.calculateMultiplier(event.getKiller().getPlayerId()),
                new TranslatableComponent("match.kill.killed", event.getVictim().getComponentName())
            );
        }
    }

    private void giveWoolDestroyRaindrops(final ParticipantState player, final DyeColor wool) {
        MatchPlayer online = player.getMatchPlayer();
        if(online != null) giveWoolDestroyRaindrops(online, wool);
    }

    private void giveWoolDestroyRaindrops(final MatchPlayer player, final DyeColor wool) {
        if(!this.destroyedWools.containsEntry(wool, player.getPlayerId())) {
            this.destroyedWools.put(wool, player.getPlayerId());
            giveRaindrops(player.getPlayerId(), RaindropConstants.WOOL_DESTROY_REWARD, new TranslatableComponent("match.wool.destroy", MonumentWoolFactory.makeComponentName(wool)));
        }
    }

    private int calculateRaindrops(MatchPlayerState player, int count, boolean scaled, double percent) {
        if(scaled) {
            final Match match = player.getMatch();
            count += (int) ((double) match.getParticipatingPlayers().size() / match.getMaxPlayers() * RaindropConstants.MATCH_FULLNESS_BONUS);

            if(player.getParty() instanceof Team) {
                count += Ints.min((int) (Math.sqrt(((Team) player.getParty()).getCumulativeParticipation(player.getPlayerId()).getSeconds()) / RaindropConstants.PLAY_TIME_BONUS), RaindropConstants.PLAY_TIME_BONUS_CUTOFF);
            }
        }

        return RaindropUtil.calculateRaindrops(player.getPlayerId(), (int) (count * percent), true);
    }

    private void giveRaindrops(final MatchPlayerState player, int count, BaseComponent reason) {
        giveRaindrops(player, count, reason, true);
    }

    private void giveRaindrops(final MatchPlayerState player, int count, final BaseComponent reason, boolean scaled) {
        givePercentageRaindrops(player, count, reason, scaled, 1.0);
    }

    private void giveRaindrops(final PlayerId playerId, int count, BaseComponent reason) {
        RaindropUtil.giveRaindrops(playerId, count, null, reason);
    }

    private void givePercentageRaindrops(final MatchPlayerState player, int count, final BaseComponent reason, boolean scaled, double percent) {
        RaindropUtil.giveRaindrops(
            player.getPlayerId(),
            calculateRaindrops(player, count, scaled, percent),
            RaindropUtil.calculateMultiplier(player.getPlayerId()),
            null,
            reason,
            true
        );
    }

    /**
     * Test if the given ItemStack is strictly an enemy wool i.e. not also
     * a wool that the given team can capture.
     */
    private boolean isDestroyableWool(ItemStack stack, Competitor team) {
        if(stack == null || stack.getType() != Material.WOOL) {
            return false;
        }

        DyeColor color = ((Wool) stack.getData()).getColor();
        boolean enemyOwned = false;

        for(Goal goal : team.getMatch().needMatchModule(GoalMatchModule.class).getGoals()) {
            if(goal instanceof MonumentWool) {
                MonumentWool wool = (MonumentWool) goal;
                if(wool.isVisible() && !wool.isPlaced() && wool.getDyeColor() == color) {
                    if(wool.getOwner() == team) {
                        return false;
                    } else {
                        enemyOwned = true;
                    }
                }
            }
        }

        return enemyOwned;
    }
}