/*
 * This file is a part of the SteamWar software.
 *
 * Copyright (C) 2022  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.bausystem.features.techhider;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.features.world.SpectatorListener;
import de.steamwar.bausystem.features.xray.XrayCommand;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.techhider.TechHider;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

@Linked
public class TechHiderCommand extends SWCommand implements Listener, ScoreboardElement {

    public TechHiderCommand() {
        super("techhider", "hider", "obfuscate", "hide", "hide-tech");
    }

    private Map<Region, Optional<TechHider>> techHiders = new HashMap<>();
    private Map<Region, Set<Player>> hidden = new HashMap<>();

    @LinkedInstance
    public XrayCommand xrayCommand;

    @Register(description = "TECHHIDER_HELP")
    public void toggleHider(@Validator Player player) {
        Region region = Region.getRegion(player.getLocation());
        if (region.isGlobal()) {
            BauSystem.MESSAGE.send("TECHHIDER_GLOBAL", player);
            return;
        }

        Optional<TechHider> techHider = techHiders.computeIfAbsent(region, rg -> {
            File file = rg.gameModeConfig();
            if (file == null) {
                return Optional.empty();
            }

            FileConfiguration config = YamlConfiguration.loadConfiguration(file);
            if (!config.getBoolean("Techhider.Active", false)) {
                return Optional.empty();
            }

            hidden.put(rg, new HashSet<>());

            String obfuscateWith = config.getString("Techhider.ObfuscateWith", "end_stone");
            Set<String> hiddenBlocks = Collections.unmodifiableSet(new HashSet<>(config.getStringList("Techhider.HiddenBlocks")));
            Set<String> hiddenBlockEntities = Collections.unmodifiableSet(new HashSet<>(config.getStringList("Techhider.HiddenBlockEntities")));

            TechHider current = new TechHider((TechHider.LocationEvaluator) (p, cX, cY) -> {
                if (rg.buildChunkOutside(cX, cY)) return true;
                return !hidden.get(rg).contains(p);
            }, Material.valueOf(obfuscateWith.toUpperCase()), hiddenBlocks.stream().map(String::toUpperCase).map(Material::valueOf).collect(Collectors.toSet()), hiddenBlockEntities);
            current.enable();

            return Optional.of(current);
        });
        if (!techHider.isPresent()) {
            return;
        }

        xrayCommand.disable(region, player);
        if (hidden.get(region).contains(player)) {
            hidden.get(region).remove(player);
            BauSystem.MESSAGE.sendPrefixless("TECHHIDER_OFF", player, ChatMessageType.ACTION_BAR);
        } else {
            hidden.get(region).add(player);
            BauSystem.MESSAGE.sendPrefixless("TECHHIDER_ON", player, ChatMessageType.ACTION_BAR);
        }
        region.forEachChunk((x, z) -> {
            CraftbukkitWrapper.impl.sendChunk(player, x, z);
        });
    }

    @Register
    public void toggleHider(@Validator("supervisor") Player player, @Mapper("spectator") Player other) {
        SpectatorListener.toggleNoTechHider(other);
    }

    @Mapper("spectator")
    public TypeMapper<Player> spectatorMapper() {
        return new TypeMapper<>() {
            @Override
            public Player map(CommandSender commandSender, String[] previousArguments, String s) {
                Player player = Bukkit.getPlayer(s);
                if (player != null && Permission.REAL_SPECTATOR.hasPermission(player)) {
                    return player;
                }
                return null;
            }

            @Override
            public Collection<String> tabCompletes(CommandSender sender, PreviousArguments previousArguments, String s) {
                return Bukkit.getOnlinePlayers().stream()
                        .filter(Permission.REAL_SPECTATOR::hasPermission)
                        .map(Player::getName)
                        .collect(Collectors.toList());
            }
        };
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        hidden.values().forEach(set -> set.remove(event.getPlayer()));
    }

    public void disable(Region region, Player player) {
        hidden.computeIfPresent(region, (rg, set) -> {
            set.remove(player);
            return set;
        });
    }

    @Override
    public ScoreboardGroup getGroup() {
        return ScoreboardGroup.OTHER;
    }

    @Override
    public int order() {
        return 0;
    }

    @Override
    public String get(Region region, Player p) {
        if (!hidden.getOrDefault(region, Collections.emptySet()).contains(p)) return null;
        return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TECHHIDER", p);
    }
}
