package de.steamwar.techhider;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.ToIntFunction;

import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
import it.unimi.dsi.fastutil.shorts.ShortSets;
import net.minecraft.network.UnconfiguredPipelineHandler;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.Vec3;
import org.bukkit.entity.Player;

import com.comphenix.tinyprotocol.TinyProtocol;

import de.steamwar.Reflection;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.handshake.ClientIntentionPacket;
import net.minecraft.network.protocol.common.ClientboundClearDialogPacket;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.ClientboundCustomReportDetailsPacket;
import net.minecraft.network.protocol.common.ClientboundDisconnectPacket;
import net.minecraft.network.protocol.common.ClientboundKeepAlivePacket;
import net.minecraft.network.protocol.common.ClientboundPingPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket;
import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket;
import net.minecraft.network.protocol.common.ClientboundServerLinksPacket;
import net.minecraft.network.protocol.common.ClientboundShowDialogPacket;
import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket;
import net.minecraft.network.protocol.common.ClientboundTransferPacket;
import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket;
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket;
import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket;
import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket;
import net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundBlockEventPacket;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundBossEventPacket;
import net.minecraft.network.protocol.game.ClientboundBundleDelimiterPacket;
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
import net.minecraft.network.protocol.game.ClientboundChunkBatchFinishedPacket;
import net.minecraft.network.protocol.game.ClientboundChunkBatchStartPacket;
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket;
import net.minecraft.network.protocol.game.ClientboundCommandsPacket;
import net.minecraft.network.protocol.game.ClientboundContainerClosePacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetContentPacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.network.protocol.game.ClientboundCooldownPacket;
import net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket;
import net.minecraft.network.protocol.game.ClientboundDamageEventPacket;
import net.minecraft.network.protocol.game.ClientboundDeleteChatPacket;
import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket;
import net.minecraft.network.protocol.game.ClientboundEntityEventPacket;
import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
import net.minecraft.network.protocol.game.ClientboundExplodePacket;
import net.minecraft.network.protocol.game.ClientboundForgetLevelChunkPacket;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.network.protocol.game.ClientboundHorseScreenOpenPacket;
import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
import net.minecraft.network.protocol.game.ClientboundInitializeBorderPacket;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
import net.minecraft.network.protocol.game.ClientboundMerchantOffersPacket;
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
import net.minecraft.network.protocol.game.ClientboundMoveMinecartPacket;
import net.minecraft.network.protocol.game.ClientboundMoveVehiclePacket;
import net.minecraft.network.protocol.game.ClientboundOpenBookPacket;
import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket;
import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket;
import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatEndPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatEnterPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerRotationPacket;
import net.minecraft.network.protocol.game.ClientboundProjectilePowerPacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookAddPacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookRemovePacket;
import net.minecraft.network.protocol.game.ClientboundRecipeBookSettingsPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveMobEffectPacket;
import net.minecraft.network.protocol.game.ClientboundResetScorePacket;
import net.minecraft.network.protocol.game.ClientboundRespawnPacket;
import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket;
import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
import net.minecraft.network.protocol.game.ClientboundSetCameraPacket;
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
import net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket;
import net.minecraft.network.protocol.game.ClientboundServerDataPacket;
import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket;
import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket;
import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket;
import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket;
import net.minecraft.network.protocol.game.ClientboundSetHealthPacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerInventoryPacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTimePacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket;
import net.minecraft.network.protocol.game.ClientboundSystemChatPacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.network.protocol.game.ClientboundTagQueryPacket;
import net.minecraft.network.protocol.game.ClientboundTakeItemEntityPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.network.protocol.game.ClientboundTickingStatePacket;
import net.minecraft.network.protocol.game.ClientboundTickingStepPacket;
import net.minecraft.network.protocol.game.ClientboundTrackedWaypointPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
import net.minecraft.network.protocol.login.ClientboundHelloPacket;
import net.minecraft.network.protocol.login.ClientboundLoginCompressionPacket;
import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket;
import net.minecraft.network.protocol.login.ClientboundLoginFinishedPacket;
import net.minecraft.network.protocol.configuration.ClientboundSelectKnownPacks;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;

/**
 * The TechHider follows the default-deny security principle,
 * any packet must be explicitly whitelisted or processed in a
 * way that is also compliant with the principle of default-deny.
 */
public abstract class TechHider {
    private final Set<Class<?>> bypassingPackets;
    private final Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> packetProcessors;

    private final Block blockUsedForObfuscation;
    private final BlockState blockStateUsedForObfuscation;
    private ChunkHider chunkHider;

    // TODO handle packet bundle
    public TechHider(Block blockUsedForObfuscation) {
        this.blockUsedForObfuscation = blockUsedForObfuscation;
        this.blockStateUsedForObfuscation = blockUsedForObfuscation.defaultBlockState();

        this.chunkHider = new ChunkHider(blockUsedForObfuscation) {
            @Override
            public boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block) {
                return TechHider.this.isPlayerPrivilegedToAccessBlock(p, blockX, blockY, blockZ, block);
            }

            @Override
            public boolean isPlayerPrivilegedToAccessBlockEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type) {
                return TechHider.this.isPlayerPrivilegedToAccessBlocEntity(p, blockX, blockY, blockZ, type);
            }

            @Override
            public boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ) {
                return TechHider.this.isPlayerPrivilegedToAccessPosition(p, blockX, blockY, blockZ);
            }
        };

        this.bypassingPackets = new HashSet<>(List.of(
                // --- 5.1.x Login Protocol ---
                ClientboundLoginDisconnectPacket.class, // 5.1.1 Disconnect
                ClientboundHelloPacket.class, // 5.1.2 Encryption Request
                ClientboundLoginFinishedPacket.class, // 5.1.3 Login Success
                ClientboundLoginCompressionPacket.class, // 5.1.4 Set Compression
                ClientboundCustomQueryPacket.class, // 5.1.5 Login Plugin Request
                ClientboundCookieRequestPacket.class, // 5.1.6 Cookie Request

                // --- 6.1.x Configuration Protocol ---
                ClientboundSelectKnownPacks.class,
                ClientboundCustomPayloadPacket.class, // 6.1.2 Clientbound Plugin Message
                ClientboundFinishConfigurationPacket.class, // 6.1.4 Finish Configuration
                ClientboundKeepAlivePacket.class, // 6.1.5 Clientbound Keep Alive
                ClientboundPingPacket.class, // 6.1.6 Ping
                ClientboundRegistryDataPacket.class, // 6.1.8 Registry Data
                ClientboundResourcePackPopPacket.class, // 6.1.9 Remove Resource Pack
                ClientboundResourcePackPushPacket.class, // 6.1.10 Add Resource Pack
                ClientboundStoreCookiePacket.class, // 6.1.11 Store Cookie
                ClientboundTransferPacket.class, // 6.1.12 Transfer
                ClientboundUpdateEnabledFeaturesPacket.class, // 6.1.13 Feature Flags
                ClientboundCustomReportDetailsPacket.class, // 6.1.16 Custom Report Details
                ClientboundServerLinksPacket.class, // 6.1.17 Server Links
                ClientboundSystemChatPacket.class, // 6.1.18/19 Dialogs are often handled via System Chat or Custom
                                                   // Payloads
                ClientboundServerDataPacket.class, // 6.1.20 Code of Conduct is usually in Server Data

                // --- 7.1.x Play Protocol ---

                // --- Safe packets; Not involved with a critical subdomain ---
                ClientboundBundleDelimiterPacket.class, // 7.1.1 Bundle Delimiter
                ClientboundBossEventPacket.class, // 7.1.10 Boss Bar
                ClientboundChangeDifficultyPacket.class, // 7.1.11 Change Difficulty
                ClientboundClearTitlesPacket.class, // 7.1.15 Clear Titles
                ClientboundCommandSuggestionsPacket.class, // 7.1.16 Command Suggestions Response
                ClientboundCommandsPacket.class, // 7.1.17 Commands
                ClientboundCookieRequestPacket.class, // 7.1.22 Cookie Request (play)
                ClientboundCooldownPacket.class, // 7.1.23 Set Cooldown
                ClientboundCustomChatCompletionsPacket.class, // 7.1.24 Chat Suggestions
                ClientboundDeleteChatPacket.class, // 7.1.32 Delete Message
                ClientboundDisconnectPacket.class, // 7.1.33 Disconnect (play)
                ClientboundDisguisedChatPacket.class, // 7.1.34 Disguised Chat Message
                ClientboundGameEventPacket.class, // 7.1.39 Game Event (like ElderGuardian effect, rain, thunder, etc.)
                ClientboundHorseScreenOpenPacket.class, // 7.1.41 Open Horse Screen: entity id based container.
                ClientboundInitializeBorderPacket.class, // 7.1.43 Initialize World Border
                ClientboundKeepAlivePacket.class, // 7.1.44 Clientbound Keep Alive (play)
                ClientboundLoginPacket.class, // 7.1.49 Login (play)
                ClientboundMerchantOffersPacket.class, // 7.1.51 Merchant Offers
                ClientboundOpenBookPacket.class, // 7.1.57 Open Book
                ClientboundOpenScreenPacket.class, // 7.1.58 Open Screen
                ClientboundPingPacket.class, // 7.1.60 Ping (play)
                ClientboundPlaceGhostRecipePacket.class, // 7.1.62 Place Ghost Recipe
                ClientboundPlayerAbilitiesPacket.class, // 7.1.63 Player Abilities (clientbound)
                ClientboundPlayerChatPacket.class, // 7.1.64 Player Chat Message
                ClientboundPlayerCombatEndPacket.class, // 7.1.65 End Combat
                ClientboundPlayerCombatEnterPacket.class, // 7.1.66 Enter Combat
                ClientboundPlayerLookAtPacket.class, // 7.1.70 Look At (Player owning channel)
                ClientboundPlayerPositionPacket.class, // 7.1.71 Synchronize Player Position (Player owning the channel)
                ClientboundPlayerRotationPacket.class, // 7.1.72 Player Rotation (Player owning the channel)
                ClientboundRecipeBookAddPacket.class, // 7.1.73 Recipe Book Add
                ClientboundRecipeBookRemovePacket.class, // 7.1.74 Recipe Book Remove
                ClientboundRecipeBookSettingsPacket.class, // 7.1.75 Recipe Book Settings
                ClientboundResetScorePacket.class, // 7.1.78 Reset Score
                ClientboundResourcePackPopPacket.class, // 7.1.79 Remove Resource Pack (play)
                ClientboundResourcePackPushPacket.class, // 7.1.80 Add Resource Pack (play)
                ClientboundRespawnPacket.class, // 7.1.81 Respawn
                ClientboundSelectAdvancementsTabPacket.class, // 7.1.84 Select Advancements Tab
                ClientboundServerDataPacket.class, // 7.1.85 Server Data
                ClientboundSetActionBarTextPacket.class, // 7.1.86 Set Action Bar Text
                ClientboundSetBorderCenterPacket.class, // 7.1.87 Set Border Center
                ClientboundSetBorderLerpSizePacket.class, // 7.1.88 Set Border Lerp Size
                ClientboundSetBorderSizePacket.class, // 7.1.89 Set Border Size
                ClientboundSetBorderWarningDelayPacket.class, // 7.1.90 Set Border Warning Delay
                ClientboundSetBorderWarningDistancePacket.class, // 7.1.91 Set Border Warning Distance
                ClientboundSetCameraPacket.class, // 7.1.92 Set Camera
                ClientboundSetChunkCacheRadiusPacket.class, // 7.1.94 Set Render Distance
                ClientboundSetCursorItemPacket.class, // 7.1.95 Set Cursor Item
                ClientboundSetDefaultSpawnPositionPacket.class, // 7.1.96 Set Default Spawn Position
                ClientboundSetDisplayObjectivePacket.class, // 7.1.97 Display Objective
                ClientboundSetExperiencePacket.class, // 7.1.102 Set Experience
                ClientboundSetHealthPacket.class, // 7.1.103 Set Health, Saturation and food (Player owning the channel)
                ClientboundSetHeldSlotPacket.class, // 7.1.104 Set Held Item (Player owning the channel)
                ClientboundSetObjectivePacket.class, // 7.1.105 Update Objectives
                ClientboundSetPlayerInventoryPacket.class, // 7.1.107 Set Player Inventory Slot (Player owning the channel)
                ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
                ClientboundSetScorePacket.class, // 7.1.109 Update Score
                ClientboundSetSimulationDistancePacket.class, // 7.1.110 Set Simulation Distance
                ClientboundSetSubtitleTextPacket.class, // 7.1.111 Set Subtitle Text
                ClientboundSetTimePacket.class, // 7.1.112 Update Time
                ClientboundSetTitleTextPacket.class, // 7.1.113 Set Title Text
                ClientboundSetTitlesAnimationPacket.class, // 7.1.114 Set Title Animation Times
                ClientboundStartConfigurationPacket.class, // 7.1.117 Start Configuration
                ClientboundStoreCookiePacket.class, // 7.1.119 Store Cookie (play)
                ClientboundSystemChatPacket.class, // 7.1.120 System Chat Message
                ClientboundTabListPacket.class, // 7.1.121 Set Tab List Header And Footer
                ClientboundTickingStatePacket.class, // 7.1.126 Set Ticking State
                ClientboundTickingStepPacket.class, // 7.1.127 Step Tick
                ClientboundTransferPacket.class, // 7.1.128 Transfer (play)
                ClientboundUpdateAdvancementsPacket.class, // 7.1.129 Update Advancements
                ClientboundUpdateRecipesPacket.class, // 7.1.132 Update Recipes
                ClientboundCustomReportDetailsPacket.class, // 7.1.135 Custom Report Details
                ClientboundServerLinksPacket.class, // 7.1.136 Server Links
                ClientboundClearDialogPacket.class, // 7.1.138 Clear Dialog (play)
                ClientboundShowDialogPacket.class, // 7.1.139 Show Dialog (play)

                // --- Mostly safe packets; Are involved with critical subdomain, but in not critical way ---
                ClientboundBlockChangedAckPacket.class, // 7.1.5 Acknowledge Block Change
                ClientboundChunkBatchFinishedPacket.class, // 7.1.12 Chunk Batch Finished (Delimiter)
                ClientboundChunkBatchStartPacket.class, // 7.1.13 Chunk Batch Start (Delimiter)
                // ClientboundChunksBiomesPacket.class, // 7.1.14 Chunk Biomes
                ClientboundContainerClosePacket.class, // 7.1.18 Close Container
                ClientboundSetChunkCacheCenterPacket.class, // 7.1.93 Set Center Chunk
                ClientboundForgetLevelChunkPacket.class, // 7.1.38 Unload Chunk
                ClientboundUpdateTagsPacket.class, // 7.1.133 Update Tags (play)
                ClientboundPlayerInfoRemovePacket.class, // 7.1.68 Player Info Remove
                ClientboundPlayerInfoUpdatePacket.class, // 7.1.69 Player Info Update
                ClientboundMoveVehiclePacket.class, // 7.1.56 Move Vehicle (vehicle the player is in)

                //TODO rem later
                ClientboundBundlePacket.class
        ));

        BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>> tossPacket = (p, packet) -> null;
        Map<Class<? extends Packet<? extends PacketListener>>, BiFunction<Player, Packet<? extends PacketListener>, Packet<? extends PacketListener>>> processors = new HashMap<>();

        // 7.1.x Bundle Packet: packet container; blocked until nested packets are
        // safely unbundled/processed.
        processors.put(ClientboundBundlePacket.class, tossPacket);

        // --- Entity packets -
        // 7.1.2 Spawn Entity: entity type and position can reveal hidden contraptions.
        processors.put(ClientboundAddEntityPacket.class, (p, packet) -> this.processAddEntityPacket(p, (ClientboundAddEntityPacket) packet));
        // 7.1.3 Entity Animation: entity id based signal, keep blocked until entity visibility is modeled.
        processors.put(ClientboundAnimatePacket.class, this.buildEntityPacketProcessor(ClientboundAnimatePacket::getId));
        // 7.1.35 Entity Event: entity id based signal.
        processors.put(ClientboundEntityEventPacket.class, this.buildEntityPacketProcessor(ClientboundEntityEventPacket::getEventId));
        // 7.1.36 Teleport Entity: entity id and absolute position.
        processors.put(ClientboundTeleportEntityPacket.class, this.buildEntityPacketProcessor(ClientboundTeleportEntityPacket::id));
        // 7.1.42 Hurt Animation: entity id based signal.
        processors.put(ClientboundHurtAnimationPacket.class, this.buildEntityPacketProcessor(ClientboundHurtAnimationPacket::id));
        // 7.1.100 Set Entity Velocity: entity id and movement.
        processors.put(ClientboundSetEntityMotionPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityMotionPacket::getId));
        // 7.1.101 Set Equipment: entity equipment can reveal wargear.
        processors.put(ClientboundSetEquipmentPacket.class, this.buildEntityPacketProcessor(ClientboundSetEquipmentPacket::getEntity));
        // 7.1.106 Set Passengers: entity relationship signal.
        processors.put(ClientboundSetPassengersPacket.class, this.buildEntityPacketProcessor(ClientboundSetPassengersPacket::getVehicle));
        // 7.1.130 Update Attributes: entity id and attribute state.
        processors.put(ClientboundUpdateAttributesPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateAttributesPacket::getEntityId));
        // 7.1.131 Entity Effect: entity id and effect state.
        processors.put(ClientboundUpdateMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundUpdateMobEffectPacket::getEntityId));
        // 7.1.77 Remove Entity Effect: entity id and effect state.
        processors.put(ClientboundRemoveMobEffectPacket.class, this.buildEntityPacketProcessor(ClientboundRemoveMobEffectPacket::entityId));
        // 7.1.98 Set Entity Metadata: entity state can reveal blocks/items/displays.
        processors.put(ClientboundSetEntityDataPacket.class, this.buildEntityPacketProcessor(ClientboundSetEntityDataPacket::id));
        // 7.1.26 Damage Event: entity ids and damage source location can leak hidde activity.
        processors.put(ClientboundDamageEventPacket.class, this.buildEntityPacketProcessor(ClientboundDamageEventPacket::entityId));
        // 7.1.54 Move Minecart Along Track: entity path and position signal.
        processors.put(ClientboundMoveMinecartPacket.class, this.buildEntityPacketProcessor(ClientboundMoveMinecartPacket::entityId));
        // 7.1.124 Synchronize Vehicle Position: entity/vehicle position.
        processors.put(ClientboundEntityPositionSyncPacket.class, this.buildEntityPacketProcessor(ClientboundEntityPositionSyncPacket::id));
        // 7.1.134 Projectile Power: projectile/entity signal.
        processors.put(ClientboundProjectilePowerPacket.class, this.buildEntityPacketProcessor(ClientboundProjectilePowerPacket::getId));
        // 7.1.123 Pickup Item: item/entity ids and pickup activity.
        processors.put(ClientboundTakeItemEntityPacket.class,  this.buildEntityPacketProcessor(ClientboundTakeItemEntityPacket::getItemId));
        // 7.1.67 Combat Death: entity/player ids and death message context.
        processors.put(ClientboundPlayerCombatKillPacket.class, this.buildEntityPacketProcessor(ClientboundPlayerCombatKillPacket::playerId));
        // 7.1.52/53/55 Update Entity Position/Rotation: entity id and movement signal.
        processors.put(ClientboundMoveEntityPacket.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket) packet));
        processors.put(ClientboundMoveEntityPacket.Pos.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Pos) packet));
        processors.put(ClientboundMoveEntityPacket.Rot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.Rot) packet));
        processors.put(ClientboundMoveEntityPacket.PosRot.class, (p, packet) -> this.processMoveEntityPacket(p, (ClientboundMoveEntityPacket.PosRot) packet));

        // 7.1.82 Set Head Rotation: entity id and rotation.
        processors.put(ClientboundRotateHeadPacket.class, (p, packet) -> this.processRotateHeadPacket(p, (ClientboundRotateHeadPacket) packet));
        // 7.1.76 Remove Entities: entity id visibility side channel.
        processors.put(ClientboundRemoveEntitiesPacket.class, (p, packet) -> this.processRemoveEntitiesPacket(p, (ClientboundRemoveEntitiesPacket) packet));
        // 7.1.99 Link Entities: entity relationship signal.
        processors.put(ClientboundSetEntityLinkPacket.class, (p, packet) -> this.processSetEntityLinkPacket(p, (ClientboundSetEntityLinkPacket) packet));

        // --- Block entity packets ---
        // 7.1.7 Block Entity Data: currently filtered by block position and allowed tile-entity actions.
        processors.put(ClientboundBlockEntityDataPacket.class, (p, packet) -> processBlockEntityDataPacket(p, (ClientboundBlockEntityDataPacket) packet));
        // 7.1.122 Tag Query Response: block-entity query result.
        processors.put(ClientboundTagQueryPacket.class, tossPacket);

        // --- Block packets ---
        // 7.1.6 Set Block Destroy Stage: block position and mining progress can reveal activity.
        processors.put(ClientboundBlockDestructionPacket.class, (p, packet) -> proccessBlockDestructionPacket(p, (ClientboundBlockDestructionPacket) packet));
        // 7.1.8 Block Action: currently filtered by block position.
        processors.put(ClientboundBlockEventPacket.class, (p, packet) -> processBlockEventPacket(p, (ClientboundBlockEventPacket) packet));
        // 7.1.9 Block Update: currently filtered/obfuscated by block position and block id.
        processors.put(ClientboundBlockUpdatePacket.class, (p, packet) -> processBlockUpdatePacket(p, (ClientboundBlockUpdatePacket) packet));

        // --- Chunk packets ---
        // 7.1.45 Chunk Data and Update Light: currently filtered/obfuscated by chunk data processor.
        processors.put(ClientboundLevelChunkWithLightPacket.class, (p, packet) -> processChunkWithLight(p, (ClientboundLevelChunkWithLightPacket) packet));

        // --- Section packets ---
        // 7.1.83 Update Section Blocks: currently filtered/obfuscated by section block processor.
        processors.put(ClientboundSectionBlocksUpdatePacket.class, (p, packet) -> processSectionUpdate(p, (ClientboundSectionBlocksUpdatePacket) packet));

        // --- Particle packets ---
        // 7.1.47 Particle: particle type and position can reveal hidden machinery.
        processors.put(ClientboundLevelParticlesPacket.class, (p, packet) -> processLevelParticlesPacket(p, (ClientboundLevelParticlesPacket) packet));

        // --- Lighting packets ---
        // 7.1.48 Update Light: lighting can reveal hidden blocks/operations.
        processors.put(ClientboundLightUpdatePacket.class, tossPacket);

        // --- Container packets ---
        // 7.1.19 Set Container Content
        processors.put(ClientboundContainerSetContentPacket.class, buildContainerPacketProcessor(ClientboundContainerSetContentPacket::containerId));
        // 7.1.20 Set Container Property
        processors.put(ClientboundContainerSetDataPacket.class, buildContainerPacketProcessor(ClientboundContainerSetDataPacket::getContainerId));
        // 7.1.21 Set Container Slot
        processors.put(ClientboundContainerSetSlotPacket.class, buildContainerPacketProcessor(ClientboundContainerSetSlotPacket::getContainerId));

        // --- Sound packets ---
        // 7.1.115 Entity Sound Effect: entity id and sound.
        processors.put(ClientboundSoundEntityPacket.class, tossPacket);
        // 7.1.116 Sound Effect: sound type and position.
        processors.put(ClientboundSoundPacket.class, tossPacket);
        // 7.1.118 Stop Sound: sound state side channel.
        processors.put(ClientboundStopSoundPacket.class, tossPacket);

        // --- Others ---
        // 7.1.137 Waypoint: world/entity tracking signal.
        processors.put(ClientboundTrackedWaypointPacket.class, tossPacket);
        // 7.1.50 Map Data: map pixels/icons can reveal player/world state.
        processors.put(ClientboundMapItemDataPacket.class, tossPacket);
        // 7.1.46 World Event: block position/event id can leak activity.
        processors.put(ClientboundLevelEventPacket.class, buildPositionBasedPacketProcessor(ClientboundLevelEventPacket::getPos));
        // 7.1.59 Open Sign Editor: block position.
        processors.put(ClientboundOpenSignEditorPacket.class, buildPositionBasedPacketProcessor(ClientboundLevelEventPacket::getPos));
        // 7.1.37 Explosion: position, affected blocks, and knockback can reveal TNT/tech.
        processors.put(ClientboundExplodePacket.class, (p, rawPacket) -> {
            ClientboundExplodePacket packet = (ClientboundExplodePacket) rawPacket;
            Vec3 pos = packet.center();
            int blockX = (int) pos.x;
            int blockY = (int) pos.y;
            int blockZ = (int) pos.z;


            return  proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
        });

        this.packetProcessors = processors;

        TinyProtocol.instance.addGlobalClientboundFilter((player, packet) -> {


            if(bypassingPackets.contains(packet.getClass())) {
                return packet;
            }
            else if(packetProcessors.containsKey(packet.getClass())) {
                //System.out.println("processing");
                //System.out.println(packet.getClass());
                return packetProcessors.get(packet.getClass()).apply(player, (Packet<? extends PacketListener>) packet);
            }
            else {
                System.out.println("dropping");
                System.out.println(packet.getClass());
                return null;
            }
        });
    }

    private Packet<? extends PacketListener> processAddEntityPacket(Player player, ClientboundAddEntityPacket packet) {
        if(isPlayerPrivilegedToAccessEntity(player, packet.getId()) && isPlayerPrivilegedToAccessPosition(player, (int) packet.getX(), (int) packet.getY(), (int) packet.getZ())) {
            return packet;
        } else {
            return null;
        }
    }

    private Packet<? extends PacketListener> processEntityPacket(Player player, int entityId, Packet<? extends PacketListener> packet) {
        if(isPlayerPrivilegedToAccessEntity(player, entityId)) {
            return packet;
        } else {
            return null;
        }
    }
    private <TTargetPacket extends Packet<?>> BiFunction<Player,  Packet<? extends PacketListener>, Packet<? extends PacketListener>> buildEntityPacketProcessor(ToIntFunction<TTargetPacket> entityIdExtractor) {
        return (p, rawPacket) -> {
            TTargetPacket packet = (TTargetPacket) rawPacket;
            return processEntityPacket(p, entityIdExtractor.applyAsInt(packet), packet);
        };
    }

    private final Reflection.Field<Integer> moveEntityPacketEntityIdField = Reflection.getField(ClientboundMoveEntityPacket.class, int.class, 0);
    private Packet<?> processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) {
        int entityId = moveEntityPacketEntityIdField.get(packet);

        if(isPlayerPrivilegedToAccessEntity(player, entityId)) {
            return packet;
        }
        else {
            return null;
        }
    }

    private  final Reflection.Field<Integer> rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0);
    private Packet<?> processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) {
        int entityId = rotateHeadPacketEntityIdField.get(packet);

        if(isPlayerPrivilegedToAccessEntity(player, entityId)) {
            return packet;
        }
        else {
            return null;
        }
    }

    private Packet<?> processRemoveEntitiesPacket(Player player, ClientboundRemoveEntitiesPacket packet) {
        IntList entityIdsToRemove = packet.getEntityIds();

        IntList filteredEntitiesToRemove = entityIdsToRemove.intStream()
                .filter((id) -> isPlayerPrivilegedToAccessEntity(player, id))
                .collect(IntArrayList::new, IntList::add, IntList::addAll);

        return new ClientboundRemoveEntitiesPacket(filteredEntitiesToRemove);
    }

    public Packet<?> processSetEntityLinkPacket(Player player, ClientboundSetEntityLinkPacket packet) {
        int fromEntityId = packet.getSourceId();
        int toEntityId = packet.getDestId();

        if(isPlayerPrivilegedToAccessEntity(player, fromEntityId) && isPlayerPrivilegedToAccessEntity(player, toEntityId)) {
            return packet;
        }
        else {
            return null;
        }
    }

    private Packet<?> processBlockEventPacket(Player player, ClientboundBlockEventPacket packet) {
        BlockPos blockPos = packet.getPos();
        Block block = packet.getBlock();
        int blockX = blockPos.getX();
        int blockY = blockPos.getY();
        int blockZ = blockPos.getZ();

        if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
            return packet;
        } else {
            return null;
        }
    }

    private Packet<?> processBlockUpdatePacket(Player player, ClientboundBlockUpdatePacket packet) {
        BlockPos blockPos = packet.getPos();
        Block block = packet.getBlockState().getBlock();
        int blockX = blockPos.getX();
        int blockY = blockPos.getY();
        int blockZ = blockPos.getZ();

        if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlock(player, blockX, blockY, blockZ, block)) {
            return packet;
        } else if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
            return new ClientboundBlockUpdatePacket(blockPos, blockStateUsedForObfuscation);
        } else {
            return null;
        }
    }

    private Packet<?> processBlockEntityDataPacket(Player player, ClientboundBlockEntityDataPacket packet) {
        BlockPos blockPos = packet.getPos();
        BlockEntityType<?> blockEntityType = packet.getType();
        int blockX = blockPos.getX();
        int blockY = blockPos.getY();
        int blockZ = blockPos.getZ();

        if (isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ) && isPlayerPrivilegedToAccessBlocEntity(player, blockX, blockY, blockZ, blockEntityType)) {
            return packet;
        } else {
            return null;
        }
    }

    private Packet<?> proccessBlockDestructionPacket(Player player, ClientboundBlockDestructionPacket packet) {
        BlockPos blockPos = packet.getPos();
        int entityBreakingBlockId = packet.getId();

        if(isPlayerPrivilegedToAccessEntity(player, entityBreakingBlockId) && isPlayerPrivilegedToAccessPosition(player, blockPos.getX(), blockPos.getY(), blockPos.getZ())) {
            return packet;
        }
        else {
            return null;
        }
    }

    private final Reflection.Field<SectionPos> sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0);
    private final Reflection.Field<short[]> oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0);
    private final Reflection.Field<BlockState[]> oldStatesField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, BlockState[].class, 0);
    private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) {
        SectionPos sectionPos = sectionPosField.get(packet);
        short[] oldPos = oldPosField.get(packet);
        BlockState[] oldStates = oldStatesField.get(packet);

        boolean modified = false;
        List<Short> filteredPos = new ArrayList<>(oldPos.length);
        List<BlockState> filteredStates = new ArrayList<>(oldStates.length);

        for (int i = 0; i < oldPos.length; i++) {
            short posShort = oldPos[i];
            BlockState state = oldStates[i];
            Block block = state.getBlock();

            int worldX = sectionPos.relativeToBlockX(posShort);
            int worldY = sectionPos.relativeToBlockY(posShort);
            int worldZ = sectionPos.relativeToBlockZ(posShort);

            if (isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ) && isPlayerPrivilegedToAccessBlock(p, worldX, worldY, worldZ, block)) {
                // TODO statefull !!!
                modified = true;
                filteredPos.add(posShort);
                filteredStates.add(state);
            } else if(isPlayerPrivilegedToAccessPosition(p, worldX, worldY, worldZ)){
                modified = true;
                filteredPos.add(posShort);
                filteredStates.add(blockStateUsedForObfuscation);
            }
        }

        if (filteredStates.isEmpty()) {
            return null;
        }
        if (!modified) {
            return packet;
        }

        short[] newPos = new short[filteredPos.size()];
        for (int i = 0; i < newPos.length; i++) {
            newPos[i] = filteredPos.get(i);
        }

        BlockState[] newStates = filteredStates.toArray(new BlockState[0]);

        return new ClientboundSectionBlocksUpdatePacket(
                sectionPos,
                ShortSets.unmodifiable(new ShortArraySet(newPos)),
                newStates
        );
    }

    private Packet<?> processLevelParticlesPacket(Player player, ClientboundLevelParticlesPacket packet) {
        int blockX = (int) packet.getX();
        int blockY = (int) packet.getY();
        int blockZ = (int) packet.getZ();

        if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
            return packet;
        }
        else {
            return null;
        }
    }

    private ClientboundLevelChunkWithLightPacket processChunkWithLight(Player p, ClientboundLevelChunkWithLightPacket packet) {
        return chunkHider.processLevelChunkWithLightPacket(p, packet);
    }

    private Packet<? extends PacketListener> processContainerPacket(Player player, int containerId, Packet<? extends PacketListener> packet) {
        if(isPlayerPrivilegedToAccessContainer(player, containerId)) {
            return packet;
        } else {
            return null;
        }
    }
    private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildContainerPacketProcessor(ToIntFunction<TTargetPacket> containerIdExtractor) {
        return (p, rawPacket) -> {
            TTargetPacket packet = (TTargetPacket) rawPacket;

            return processContainerPacket(p, containerIdExtractor.applyAsInt(packet), packet);
        };
    }

    private Packet<? extends PacketListener> proccessPositionBasedPacket(Player player, int blockX, int blockY, int blockZ, Packet<? extends PacketListener> packet) {
        if(isPlayerPrivilegedToAccessPosition(player, blockX, blockY, blockZ)) {
            return packet;
        } else {
            return null;
        }
    }
    private <TTargetPacket extends Packet<?>> BiFunction<Player, Packet<?>, Packet<?>> buildPositionBasedPacketProcessor(Function<TTargetPacket, BlockPos> positionExtractor) {
        return (p, rawPacket) -> {
            TTargetPacket packet = (TTargetPacket) rawPacket;
            BlockPos pos = positionExtractor.apply(packet);
            int blockX = pos.getX();
            int blockY = pos.getY();
            int blockZ = pos.getZ();

            return proccessPositionBasedPacket(p, blockX, blockY, blockZ, packet);
        };
    }

    public abstract boolean isPlayerPrivilegedToAccessPosition(Player p, int blockX, int blockY, int blockZ);
    public abstract boolean isPlayerPrivilegedToAccessBlock(Player p, int blockX, int blockY, int blockZ, Block block);
    public abstract boolean isPlayerPrivilegedToAccessEntity(Player p, int entityId);
    public abstract boolean isPlayerPrivilegedToAccessBlocEntity(Player p, int blockX, int blockY, int blockZ, BlockEntityType<?> type);
    public abstract boolean isPlayerPrivilegedToAccessContainer(Player p, int containerId);
    public abstract boolean isPlayerPrivilegedToAccessSound(Player p, ResourceLocation soundId);
}
