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

import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.techhider.BlockIds;
import de.steamwar.techhider.ProtocolUtils;
import lombok.Getter;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material;
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
import org.bukkit.entity.Player;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

@Deprecated
public class TechHider {

    public static final Class<?> blockPosition = BlockPos.class;

    public static final Class<?> iBlockData = BlockState.class;
    public static final Class<?> block = Block.class;

    public boolean iBlockDataHidden(BlockState iBlockData) {
        return obfuscateIds.contains(BlockIds.impl.getCombinedId(iBlockData));
    }

    public static final Object AIR = CraftMagicNumbers.getBlock(Material.AIR).defaultBlockState();
    public static final int AIR_ID = BlockIds.impl.materialToId(Material.AIR);

    private final Map<Class<?>, BiFunction<Player, Object, Object>> techhiders = new HashMap<>();
    @Getter
    private final LocationEvaluator locationEvaluator;
    @Getter
    private final Object obfuscationTarget;
    @Getter
    private final int obfuscationTargetId;
    @Getter
    private final Set<Integer> obfuscateIds;
    @Getter
    private final Set<String> hiddenBlockEntities;

    public TechHider(LocationEvaluator locationEvaluator, Material obfuscationTarget, Set<Material> obfuscate, Set<String> hiddenBlockEntities) {
        this.locationEvaluator = locationEvaluator;
        this.obfuscateIds = obfuscate.stream().flatMap(m -> BlockIds.impl.materialToAllIds(m).stream()).collect(Collectors.toSet());
        this.hiddenBlockEntities = hiddenBlockEntities;
        this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState();
        this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget);

        techhiders.put(ClientboundBlockEventPacket.class, (player, o) -> this.blockActionHider(player, (ClientboundBlockEventPacket) o));
        techhiders.put(ClientboundBlockUpdatePacket.class, (player, o) -> this.blockChangeHider(player, (ClientboundBlockUpdatePacket) o));
        techhiders.put(ClientboundBlockEntityDataPacket.class, (player, o) -> this.tileEntityDataHider(player, (ClientboundBlockEntityDataPacket) o));
        techhiders.put(ClientboundSectionBlocksUpdatePacket.class, (BiFunction) ProtocolWrapper.impl.multiBlockChangeGenerator(this));
        techhiders.put(ClientboundLevelChunkWithLightPacket.class, (BiFunction) ChunkHider.impl.chunkHiderGenerator(this));
        techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
        techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);

    }

    public void enable() {
        techhiders.forEach((type, playerObjectBiFunction) -> TinyProtocol.instance.addFilter((Class) type, playerObjectBiFunction));
    }

    public void disable() {
        techhiders.forEach(TinyProtocol.instance::removeFilter);
    }

    public static final UnaryOperator<ClientboundSectionBlocksUpdatePacket> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new);

    private static final UnaryOperator<ClientboundBlockUpdatePacket> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new);

    private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) {
        switch (locationEvaluator.checkBlockPos(p, packet.getPos())) {
            case SKIP:
                return packet;
            case CHECK:
                if (!iBlockDataHidden(packet.blockState)) {
                    return packet;
                }
            case HIDE:
                packet = blockChangeCloner.apply(packet);
                packet.blockState = (BlockState) obfuscationTarget;
                return packet;
            case HIDE_AIR:
            default:
                packet = blockChangeCloner.apply(packet);
                packet.blockState = (BlockState) AIR;
                return packet;
        }
    }

    private Object blockActionHider(Player p, ClientboundBlockEventPacket packet) {
        if (locationEvaluator.checkBlockPos(p, packet.getPos()) == State.SKIP) {
            return packet;
        }
        return null;
    }

    private ClientboundBlockEntityDataPacket tileEntityDataHider(Player p, ClientboundBlockEntityDataPacket packet) {
        switch (locationEvaluator.checkBlockPos(p, packet.getPos())) {
            case SKIP:
                return packet;
            case CHECK:
                if (packet.getType() != BlockEntityType.SIGN) {
                    return packet;
                }
            default:
                return null;
        }
    }

    public enum State {
        SKIP,
        CHECK,
        HIDE,
        HIDE_AIR
    }

    public interface LocationEvaluator {
        default boolean suppressInteractions(Player player) {
            return false;
        }

        boolean skipChunk(Player player, int x, int z);

        default boolean skipChunkSection(Player player, int x, int y, int z) {
            return skipChunk(player, x, z);
        }

        default State check(Player player, int x, int y, int z) {
            return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK;
        }

        default State checkBlockPos(Player player, Vec3i pos) {
            return check(player, pos.getX(), pos.getY(), pos.getZ());
        }

        default boolean blockPrecise(Player player, int x, int y, int z) {
            return false;
        }
    }
}
