/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2020  SteamWar.de-Serverteam
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.fightsystem.winconditions;

import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.Message;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerQuitEvent;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class WinconditionPointsAirShip extends WinconditionPercent implements Listener {

    private double[] as = new double[] {
            0.5,
            0.6,
            0.68,
            0.74,
            0.8
    };

    private double[] es = new double[] {
            0.25,
            0.3,
            0.34,
            0.37,
            0.4
    };

    private final Map<FightTeam, TeamPoints> teamMap = new HashMap<>();

    public WinconditionPointsAirShip(){
        super(Winconditions.POINTS_AIRSHIP, "Points");

        checkWin = team -> {
            if (teamMap.get(team).getPoints() > TeamPoints.WIN_POINTS) {
                end();
            }
        };
        postEnable = this::pointInit;

        Fight.teams().forEach(team -> teamMap.put(team, new TeamPoints(team)));

        new StateDependentListener(Winconditions.POINTS_AIRSHIP, FightState.Ingame, this);
        new StateDependentTask(Winconditions.POINTS_AIRSHIP, FightState.Running, this::running, 20, 20);

    }

    private void end() {
        comparisonWin(team -> teamMap.get(team).getPoints(), "WIN_POINTS", "WIN_POINTS_EQUAL");
    }

    private void running() {
        AtomicBoolean possibleEnd = new AtomicBoolean(false);
        Fight.teams().forEach(team -> {
            teamMap.get(team).points += 1;
            possibleEnd.compareAndSet(false, teamMap.get(team).getPoints() >= TeamPoints.WIN_POINTS);
        });
        if(possibleEnd.get())
            end();
    }

    @EventHandler
    public void handlePlayerDeath(PlayerDeathEvent event) {
        handleDeath(event.getEntity().getPlayer());
    }

    @EventHandler
    public void handlePlayerQuit(PlayerQuitEvent event) {
        handleDeath(event.getPlayer());
    }

    private void handleDeath(Player player){
        FightTeam team = isTarget(player);
        if(team == null)
            return;

        TeamPoints enemy = teamMap.get(Fight.getOpposite(team));
        enemy.points += 200.0 / team.getPlayerCount();

        if (enemy.getPoints() >= TeamPoints.WIN_POINTS)
            end();
    }

    private void pointInit(FightTeam team) {
        TeamPoints opponent = teamMap.get(Fight.getOpposite(team));
        if(getTotalBlocks(team) == 0)
            return;

        teamMap.get(team).setup(getTotalBlocks(Fight.getOpposite(team)));
        opponent.setup(getTotalBlocks(team));
    }

    @Override
    public Message getDisplay(FightTeam team) {
        return new Message("BAR_POINTS_OF", team.getPrefix() + (int) Math.round(teamMap.get(team).getPoints()), TeamPoints.WIN_POINTS);
    }

    private class TeamPoints {
        protected static final double WIN_POINTS = 1200;

        private final FightTeam team;
        private double pointGetPerXBlocksBroken;
        private double points;

        TeamPoints(FightTeam team){
            this.team = team;
        }

        private void setup(int enemyBlocks){
            points = 0;

            int factorIndex = Math.min(Math.max(team.getPlayerCount(), Fight.getOpposite(team).getPlayerCount()), as.length) - 1;
            double a = as[factorIndex];
            double e = es[factorIndex];
            double volume = team.getSchemRegion().volume();

            double needed = enemyBlocks * (a - (a * enemyBlocks) / volume + (e * enemyBlocks) / volume);
            pointGetPerXBlocksBroken = needed / WIN_POINTS;
        }

        public double getPoints(){
            FightTeam fightTeam = Fight.getOpposite(team);
            return points + (getPercentWincondition().getTotalBlocks(fightTeam) - getPercentWincondition().getCurrentBlocks(fightTeam)) / pointGetPerXBlocksBroken;
        }
    }
}
