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

import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.events.BoardingEvent;
import de.steamwar.fightsystem.events.TeamLeaveEvent;
import de.steamwar.fightsystem.events.TeamSpawnEvent;
import de.steamwar.fightsystem.fight.Fight;
import de.steamwar.fightsystem.fight.FightTeam;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.techhider.TechHider;
import lombok.Getter;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class TechHiderWrapper extends StateDependent implements Listener {

    public static final boolean ENABLED = !Config.OnlyPublicSchematics && !Config.test() && Config.GameModeConfig.Techhider.Active;

    @Getter
    private final ConcurrentHashMap<Player, Region> hiddenRegion = new ConcurrentHashMap<>();
    private final TechHider techHider;


    public TechHiderWrapper(HullHider hullHider) {
        super(ENABLED, FightState.Schem);
        Set<Block> blocksToObfuscate = Config.GameModeConfig.Techhider.HiddenBlocks.stream()
                .map(CraftMagicNumbers::getBlock)
                .collect(Collectors.toUnmodifiableSet());
        /* Set<BlockEntityType<?>> blockEntityTypeToObfuscate =  Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
                .map((id) -> {
                    ResourceLocation loc = ResourceLocation.parse(id);
                    return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).get().value();
                })
                .collect(Collectors.toUnmodifiableSet()); */
        techHider = new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith)) {
            @Override
            public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) {
                return !hullHider.isBlockHidden(p, blockX, blockY, blockZ);
            }

            @Override
            public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
                Region hiddenRegion = getHiddenRegion(p);
                return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blocksToObfuscate.contains(block);
            }

            // TODO
            @Override
            public boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId) {
                return true;
            }

            @Override
            public boolean isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
                //Region hiddenRegion = getHiddenRegion(p);
                //return !hiddenRegion.inRegion(blockX, blockY, blockZ) || !blockEntityTypeToObfuscate.contains(type);
                return true;
            }

            @Override
            public boolean isPlayerPrivilegedToAccessContainer(Player p, int containerId) {
                return true;
            }

            @Override
            public boolean isPlayerPrivilegedToAccessSound(Player p, ResourceLocation soundId) {
                return true;
            }
        };

        new StateDependentListener(ENABLED, FightState.Schem, this);
        register();
    }

    @Override
    public void enable() {

    }

    @Override
    public void disable() {
        hiddenRegion.clear();
    }

    @EventHandler
    public void teamJoin(TeamSpawnEvent e) {
        e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player)));
    }

    @EventHandler
    public void boarding(BoardingEvent e) {
        e.getFightPlayer().ifPlayer(player -> hiddenRegion.put(player, getHiddenRegion(player)));
    }

    @EventHandler
    public void teamLeave(TeamLeaveEvent e) {
        e.getFightPlayer().ifPlayer(player -> {
            if (player.isOnline()) {
                hiddenRegion.put(player, getHiddenRegion(player));
            }
        });
    }

    @EventHandler
    public void playerQuit(PlayerQuitEvent e) {
        Player player = e.getPlayer();
        hiddenRegion.remove(player);
    }

    public void reloadChunks(Player player, Region region, Region exclusion) {
        if (!ENABLED || !FightState.Schem.contains(FightState.getFightState()) || !player.isOnline()) return;

        region.forEachChunk((chunkX, chunkZ) -> {
            if (exclusion.chunkOutside(chunkX, chunkZ)) {
                CraftbukkitWrapper.sendChunk(player, chunkX, chunkZ);
            }
        });
    }

    private Region getHiddenRegion(Player player) {
        if (Config.isReferee(player)) return Region.EMPTY;

        FightTeam team = Fight.getPlayerTeam(player);
        if (team == null) return Config.ArenaRegion;

        if (team.canPlayerEntern(player)) return Region.EMPTY;

        return Fight.getOpposite(team).getExtendRegion();
    }
}
