/* 
    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.fight;

import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
import de.steamwar.fightsystem.ai.AIManager;
import de.steamwar.fightsystem.commands.GUI;
import de.steamwar.fightsystem.countdown.Countdown;
import de.steamwar.fightsystem.events.TeamLeaveEvent;
import de.steamwar.fightsystem.events.TeamSpawnEvent;
import de.steamwar.fightsystem.listener.FightScoreboard;
import de.steamwar.fightsystem.listener.Permanent;
import de.steamwar.fightsystem.listener.PersonalKitCreator;
import de.steamwar.fightsystem.listener.TeamArea;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.utils.*;
import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.fightsystem.winconditions.Winconditions;
import de.steamwar.inventory.SWItem;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.*;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scoreboard.NameTagVisibility;
import org.bukkit.scoreboard.Team;

import java.util.*;
import java.util.function.Consumer;


public class FightTeam {

    private static void setKitButton(HotbarKit kit, boolean leader) {
        if (Kit.getAvailableKits(leader).size() > 1 || Config.PersonalKits)
            kit.setItem(1, "CHOOSE_KIT", new ItemBuilder(Material.LEATHER_CHESTPLATE).enchant().build(), player -> GUI.kitSelection(player, ""));
        else
            kit.setItem(1, null, null, null);
    }

    private static final HotbarKit memberKit = new HotbarKit();
    static {
        setKitButton(memberKit, false);
        memberKit.setItem(7, "RESPAWN", new ItemBuilder(Material.BEACON).build(), player -> player.teleport(Objects.requireNonNull(Fight.getPlayerTeam(player)).getSpawn()));
    }
    private static final HotbarKit notReadyKit = new HotbarKit(memberKit);
    static {
        setKitButton(notReadyKit, true);

        if(ArenaMode.VariableTeams.contains(Config.mode)){
            notReadyKit.setItem(2, "REQUESTS", new ItemBuilder(Material.PAPER).build(), GUI::chooseJoinRequests);
            notReadyKit.setItem(3, "MANAGE_PLAYERS", SWItem.getPlayerSkull("AdmiralSeekrank").getItemStack(), GUI::managePlayers);
            if(!AIManager.availableAIs().isEmpty())
                notReadyKit.setItem(6, "ADD_AI", new ItemBuilder(Material.REDSTONE).build(), GUI::addAI);
        }

        if(Config.test())
            notReadyKit.setItem(5, "CHOOSE_SCHEMATIC", new ItemBuilder(SWItem.getMaterial("CAULDRON_ITEM")).enchant().build(), GUI::preSchemDialog);

        notReadyKit.setItem(4, "TEAM_NOT_READY", new ItemBuilder(SWItem.getDye(10), (short) 10).enchant().build(), player -> Objects.requireNonNull(Fight.getPlayerTeam(player)).setReady(true));
    }
    private static final HotbarKit chooseSchemKit = new HotbarKit(notReadyKit);
    static {
        chooseSchemKit.setItem(4, "CHOOSE_SCHEMATIC", new ItemBuilder(SWItem.getMaterial("CAULDRON_ITEM")).enchant().build(), GUI::preSchemDialog);
    }
    private static final HotbarKit readyKit = new HotbarKit(memberKit);
    static {
        readyKit.setItem(1, null, null, null);
        readyKit.setItem(4, "TEAM_READY", new ItemBuilder(SWItem.getDye(8), (short) 8).enchant().build(), player -> Objects.requireNonNull(Fight.getPlayerTeam(player)).setReady(false));
    }

    @Getter
    private UUID designatedLeader;
    @Getter
    private FightPlayer leader;
    @Getter
    private boolean publicsOnly;

    private final Map<UUID, FightPlayer> players = new HashMap<>();

    @Getter
    private String name;
    @Getter
    private String prefix;
    @Getter
    private ChatColor color;
    private final FightSchematic schematic;
    private final Team team;
    @Getter
    private final boolean blue;

    @Getter
    private boolean ready;
    private boolean skip;

    @Getter
    private final Location spawn;
    @Getter
    private final Region schemRegion;
    @Getter
    private final Region extendRegion;

    @SuppressWarnings("deprecation")
    public FightTeam(String name, String prefix, Location spawn, Region schemRegion, Region extendRegion, boolean rotate, boolean blue, UUID designatedLeader) { //TODO: Static TeamConfig object
        this.spawn = spawn;
        this.schemRegion = schemRegion;
        this.extendRegion = extendRegion;
        this.publicsOnly = false;
        this.ready = false;
        this.skip = false;
        this.blue = blue;
        this.designatedLeader = designatedLeader;
        setPrefixAndName(prefix, name);
        this.schematic = new FightSchematic(this, rotate);
        new KitLoader();
        new SpectateHandler();
        new TeamArea(this);

        team = FightScoreboard.getBukkitTeam(name);
        WorldOfColorWrapper.impl.setTeamColor(team, color);
        BountifulWrapper.impl.setNametagVisibility(team);
        team.setNameTagVisibility(NameTagVisibility.HIDE_FOR_OTHER_TEAMS);
        if (!Config.ActiveWinconditions.contains(Winconditions.AMONG_US)) {
            team.setAllowFriendlyFire(false);
        }

        new OneShotStateDependent(ArenaMode.Restartable, FightState.PreLeaderSetup, () -> Bukkit.getScheduler().runTask(FightSystem.getPlugin(), this::reset));
        new OneShotStateDependent(Config.replayserver(), FightState.PreLeaderSetup, () -> Bukkit.getScheduler().runTask(FightSystem.getPlugin(), this::reset));
        new OneShotStateDependent(ArenaMode.All, FightState.PostSchemSetup, () -> {
            if(leader != null)
                leader.ifPlayer(notReadyKit::loadToPlayer);
        });
    }

    public void setPrefixAndName(String prefix, String name){
        this.name = name;
        this.prefix = prefix;
        this.color = ChatColor.getByChar(ChatColor.getLastColors(prefix).replace("§", ""));
    }

    public boolean canbeLeader(Player p){
        return isLeaderless() && (designatedLeader == null || designatedLeader.equals(p.getUniqueId()));
    }

    public void teleportToSpawn(){
        players.forEach((player, fp) -> fp.getEntity().teleport(spawn));
    }

    public FightPlayer getFightPlayer(LivingEntity player) {
        return players.get(player.getUniqueId());
    }

    public boolean allPlayersOut() {
        for(FightPlayer fightPlayer : players.values()) {
            if(fightPlayer.isLiving())
                return false;
        }
        return true;
    }

    public boolean isPlayerInTeam(LivingEntity player) {
        return players.containsKey(player.getUniqueId());
    }

    public boolean canPlayerEntern(LivingEntity player) {
        return getFightPlayer(player).canEntern();
    }

    public boolean isPlayerLeader(LivingEntity player) {
        if(leader != null)
            return leader.getEntity().equals(player);
        else
            return false;
    }

    public void reset() {
        skip = false;
        ready = false;
        schematic.reset();

        Set<UUID> playerSet = new HashSet<>(players.keySet());
        playerSet.removeIf(uuid -> {
            Player player = Bukkit.getPlayer(uuid);
            if(player == null) {
                removePlayer(players.get(uuid).getEntity());
                return true;
            }

            return false;
        });
        FightPlayer leaderBackup = leader;
        players.clear();
        leader = null;

        if(leaderBackup != null){
            playerSet.remove(leaderBackup.getEntity().getUniqueId());
            addMember(leaderBackup.getEntity(), leaderBackup.getUser(), true);
        }

        playerSet.forEach(uuid -> addMember(Bukkit.getPlayer(uuid), SteamwarUser.get(uuid), true));

        if(ArenaMode.VariableTeams.contains(Config.mode) && isLeaderless()){
            List<Player> onlinePlayers = new ArrayList<>(Bukkit.getOnlinePlayers());
            Collections.shuffle(onlinePlayers);
            for(Player player : onlinePlayers) {
                if(Fight.getPlayerTeam(player) == null && canbeLeader(player)){
                    addMember(player);
                    break;
                }
            }
        }
    }

    public void broadcast(String message, Object... params) {
        broadcast(player -> FightSystem.getMessage().sendPrefixless(message, player, ChatMessageType.ACTION_BAR, params));
    }

    public void broadcastSystem(String message, Object... params) {
        broadcast(player -> FightSystem.getMessage().send(message, player, params));
    }

    public void broadcastChat(Player sender, String message) {
        broadcast(player -> FightSystem.getMessage().sendPrefixless("TEAM_CHAT", player, ChatMessageType.CHAT, prefix, sender.getName(), message));
    }

    private void broadcast(Consumer<Player> f) {
        players.forEach((uuid, fightPlayer) -> {
            Player player = Bukkit.getPlayer(uuid);
            if(player != null)
                f.accept(player);
        });
    }

    public void addMember(Player player) {
        addMember(player, SteamwarUser.get(player.getUniqueId()), false);
    }

    public void addMember(LivingEntity entity, SteamwarUser user) {
        addMember(entity, user, false);
    }

    public void addMember(LivingEntity entity, SteamwarUser user, boolean silent) {
        FightPlayer fightPlayer = getFightPlayer(entity) != null ? getFightPlayer(entity) : new FightPlayer(entity, user, this);
        fightPlayer.revive();
        players.put(entity.getUniqueId(), fightPlayer);
        Bukkit.getPluginManager().callEvent(new TeamSpawnEvent(fightPlayer));

        Permanent.getSpectatorTeam().removeEntry(entity.getName());
        team.addEntry(entity.getName());

        entity.setHealth(20);
        entity.teleport(spawn);

        fightPlayer.ifPlayer(player -> {
            BountifulWrapper.impl.setAttackSpeed(player);
            player.setFoodLevel(20);
            player.getInventory().clear();
            FightSystem.getHullHider().updatePlayer(player);

            if(FightState.Spectate.contains(FightState.getFightState())) {
                Fight.setPlayerGamemode(player, GameMode.SPECTATOR);
            } else {
                Fight.setPlayerGamemode(player, GameMode.SURVIVAL);
                (FightState.ingame() ? fightPlayer.getKit() : memberKit).loadToPlayer(player);
            }
        });

        if(FightState.Running.contains(FightState.getFightState()))
            fightPlayer.startEnternCountdown(Wincondition.getTimeOverCountdown());

        fightPlayer.ifPlayer(player -> FightSystem.getTechHider().reloadChunks(player, Config.ArenaRegion, fightPlayer.canEntern() ? Region.EMPTY : Fight.getOpposite(this).getExtendRegion()));

        if(isLeaderless())
            setLeader(fightPlayer, silent);
        else if(!silent)
            FightUI.addSubtitle("UI_PLAYER_JOINS", prefix, entity.getName());
    }

    public void removePlayer(LivingEntity entity) {
        FightPlayer fightPlayer = getFightPlayer(entity);
        Bukkit.getPluginManager().callEvent(new TeamLeaveEvent(fightPlayer));

        fightPlayer.ifPlayer(PersonalKitCreator::closeIfInKitCreator);
        players.remove(entity.getUniqueId());
        team.removeEntry(entity.getName());
        Permanent.getSpectatorTeam().addEntry(entity.getName());

        FightUI.addSubtitle("UI_PLAYER_LEAVES", prefix, entity.getName());

        if(fightPlayer.equals(leader))
            removeLeader();

        entity.teleport(Config.SpecSpawn);

        fightPlayer.ifPlayer(player -> {
            Fight.setPlayerGamemode(player, GameMode.SPECTATOR);
            player.getInventory().clear();

            if(player.isOnline()){
                FightSystem.getHullHider().updatePlayer(player);
                FightSystem.getTechHider().reloadChunks(player, Config.ArenaRegion, Fight.getOpposite(this).getExtendRegion());

                if(ArenaMode.VariableTeams.contains(Config.mode))
                    HotbarKit.SPECTATOR_KIT.loadToPlayer(player);
            }
        });
    }

    public boolean isLeaderless() {
        return leader == null;
    }

    private void removeLeader() {
        this.leader = null;
        if(!players.isEmpty()) {
            setLeader(players.values().iterator().next(), false);
        }else if(FightState.getFightState() != FightState.PRE_LEADER_SETUP && !ArenaMode.RankedEvent.contains(Config.mode)){
            Bukkit.getScheduler().runTaskLater(FightSystem.getPlugin(), () -> FightState.setFightState(FightState.PRE_LEADER_SETUP), 1);
        }
    }

    public void setLeader(FightPlayer leader, boolean silent) {
        if(this.leader != null)
            this.leader.ifPlayer(memberKit::loadToPlayer);

        leader.ifPlayer(PersonalKitCreator::closeIfInKitCreator);

        this.leader = leader;
        designatedLeader = null;
        if(ready)
            setReady(false);

        if(!silent)
            FightUI.addSubtitle("UI_LEADER_JOINS", prefix, leader.getEntity().getName());

        publicsOnly = SchematicNode.getAllAccessibleSchematicsOfType(leader.getUser().getId(), Config.SchematicType.toDB()).isEmpty();

        if(!Config.PersonalKits)
            leader.setKit(Kit.getKitByName(Config.LeaderDefault));

        leader.ifPlayer(player -> {
            if(FightState.getFightState() != FightState.POST_SCHEM_SETUP)
                chooseSchemKit.loadToPlayer(player);
            else
                notReadyKit.loadToPlayer(player);
        });

        if(FightState.getFightState() == FightState.PRE_LEADER_SETUP && !Fight.getOpposite(this).isLeaderless()){
            FightState.setFightState(FightState.PRE_SCHEM_SETUP);
        }

        leader.ifAI(ai -> pasteSchem(ai.chooseSchematic()));
    }

    public Collection<FightPlayer> getPlayers() {
        return players.values();
    }

    public void pasteSchem() {
        testPasteAction();
    }

    public void pasteSchem(SchematicNode schematic){
        if(schematic.getSchemtype().check()) {
            FightStatistics.unrank();
            FightSystem.getMessage().broadcast("SCHEMATIC_UNCHECKED", getColoredName());
        }

        setSchem(schematic);
        testPasteAction();
    }

    public void pasteSchem(int schemId, Clipboard clipboard){
        this.schematic.setSchematic(schemId, clipboard);
        testPasteAction();
    }

    private void testPasteAction(){
        if(Config.test())
            this.schematic.enable();
        else if(Fight.getOpposite(this).schematic.hasSchematic()){
            FightState.setFightState(FightState.POST_SCHEM_SETUP);
        }
    }

    public void pasteTeamName(){
        schematic.pasteTeamName();
    }

    public void setSchem(SchematicNode schematic){
        this.schematic.setSchematic(schematic);
        broadcast("SCHEMATIC_CHOSEN", Config.GameName, schematic.getName());
    }

    public void setReady(boolean ready) {
        LivingEntity l = leader.getEntity();

        if(!schematic.hasSchematic()){
            FightSystem.getMessage().sendPrefixless("SCHEMATIC_REQUIRED", l, ChatMessageType.ACTION_BAR);
            return;
        }

        this.ready = ready;
        if(ready) {
            broadcast("TEAM_READY");
            leader.ifPlayer(readyKit::loadToPlayer);
            if(Fight.getOpposite(this).isReady() || ArenaMode.SoloLeader.contains(Config.mode))
                FightState.setFightState(FightState.PRE_RUNNING);
        } else {
            broadcast("TEAM_NOT_READY");
            leader.ifPlayer(notReadyKit::loadToPlayer);
        }
    }

    public void skip(){
        this.skip = !skip;
        if(skip){
            broadcast("SKIP_READY");
            if(Fight.getOpposite(this).skip || Config.test()){
                skip = false;
                Fight.getOpposite(this).skip = false;
                Countdown.skip();
            }
        }else{
            broadcast("SKIP_NOT_READY");
        }
    }

    public String getColoredName() {
        return prefix + name;
    }

    public int getSchematic() {
        return schematic.getId();
    }

    public Clipboard getClipboard() {
        return schematic.getClipboard();
    }

    public double getCurrentHearts() {
        return players.values().stream().filter(FightPlayer::isLiving).mapToDouble(fp -> fp.getEntity().getHealth()).sum();
    }

    public double getHeartRatio(){
        int maximumHearts = players.size() * 20;
        return maximumHearts != 0 ? getCurrentHearts() / maximumHearts : 0;
    }

    public int getPlayerCount() {
        return players.size();
    }

    public int getAlivePlayers() {
        return (int) players.values().stream().filter(FightPlayer::isLiving).count();
    }

    @Override
    public String toString() {
        return name;
    }

    private class KitLoader extends StateDependent {
        private KitLoader() {
            super(ArenaMode.AntiReplay, FightState.Ingame);
            register();
        }

        @Override
        public void enable() {
            for(FightPlayer fightPlayer : players.values()) {
                fightPlayer.ifPlayer(player -> {
                    PersonalKitCreator.closeIfInKitCreator(player);

                    player.closeInventory();
                    fightPlayer.getKit().loadToPlayer(player);
                });
            }
        }

        @Override
        public void disable() {
            players.values().forEach(fightPlayer -> fightPlayer.ifPlayer(p -> p.getInventory().clear()));
        }
    }

    private class SpectateHandler extends StateDependent {
        private SpectateHandler() {
            super(ArenaMode.AntiReplay, FightState.Spectate);
            register();
        }

        @Override
        public void enable() {
            players.values().forEach(fightPlayer -> {
                fightPlayer.ifPlayer(player -> Fight.setPlayerGamemode(player, GameMode.SPECTATOR));
                fightPlayer.getEntity().teleport(FightTeam.this.spawn);
            });
        }

        @Override
        public void disable() {
            players.values().forEach(fightPlayer -> fightPlayer.ifPlayer(player -> Fight.setPlayerGamemode(player, GameMode.SURVIVAL)));
        }
    }
}
