package net.minecraft.server.level;

import org.apache.logging.log4j.LogManager;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.item.UseOnContext;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.JigsawBlock;
import net.minecraft.world.level.block.StructureBlock;
import net.minecraft.world.level.block.CommandBlock;
import java.util.Objects;
import net.minecraft.world.level.Level;
import net.minecraft.network.protocol.game.ClientboundBlockBreakAckPacket;
import net.minecraft.core.Direction;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.GameType;
import org.apache.logging.log4j.Logger;

public class ServerPlayerGameMode {
    private static final Logger LOGGER;
    public ServerLevel level;
    public ServerPlayer player;
    private GameType gameModeForPlayer;
    private GameType previousGameModeForPlayer;
    private boolean isDestroyingBlock;
    private int destroyProgressStart;
    private BlockPos destroyPos;
    private int gameTicks;
    private boolean hasDelayedDestroy;
    private BlockPos delayedDestroyPos;
    private int delayedTickStart;
    private int lastSentState;
    
    public ServerPlayerGameMode(final ServerLevel zd) {
        this.gameModeForPlayer = GameType.NOT_SET;
        this.previousGameModeForPlayer = GameType.NOT_SET;
        this.destroyPos = BlockPos.ZERO;
        this.delayedDestroyPos = BlockPos.ZERO;
        this.lastSentState = -1;
        this.level = zd;
    }
    
    public void setGameModeForPlayer(final GameType bpy) {
        this.setGameModeForPlayer(bpy, (bpy != this.gameModeForPlayer) ? this.gameModeForPlayer : this.previousGameModeForPlayer);
    }
    
    public void setGameModeForPlayer(final GameType bpy1, final GameType bpy2) {
        this.previousGameModeForPlayer = bpy2;
        (this.gameModeForPlayer = bpy1).updatePlayerAbilities(this.player.abilities);
        this.player.onUpdateAbilities();
        this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_GAME_MODE, new ServerPlayer[] { this.player }));
        this.level.updateSleepingPlayerList();
    }
    
    public GameType getGameModeForPlayer() {
        return this.gameModeForPlayer;
    }
    
    public GameType getPreviousGameModeForPlayer() {
        return this.previousGameModeForPlayer;
    }
    
    public boolean isSurvival() {
        return this.gameModeForPlayer.isSurvival();
    }
    
    public boolean isCreative() {
        return this.gameModeForPlayer.isCreative();
    }
    
    public void updateGameMode(final GameType bpy) {
        if (this.gameModeForPlayer == GameType.NOT_SET) {
            this.gameModeForPlayer = bpy;
        }
        this.setGameModeForPlayer(this.gameModeForPlayer);
    }
    
    public void tick() {
        ++this.gameTicks;
        if (this.hasDelayedDestroy) {
            final BlockState cfj2 = this.level.getBlockState(this.delayedDestroyPos);
            if (cfj2.isAir()) {
                this.hasDelayedDestroy = false;
            }
            else {
                final float float3 = this.incrementDestroyProgress(cfj2, this.delayedDestroyPos, this.delayedTickStart);
                if (float3 >= 1.0f) {
                    this.hasDelayedDestroy = false;
                    this.destroyBlock(this.delayedDestroyPos);
                }
            }
        }
        else if (this.isDestroyingBlock) {
            final BlockState cfj2 = this.level.getBlockState(this.destroyPos);
            if (cfj2.isAir()) {
                this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
                this.lastSentState = -1;
                this.isDestroyingBlock = false;
            }
            else {
                this.incrementDestroyProgress(cfj2, this.destroyPos, this.destroyProgressStart);
            }
        }
    }
    
    private float incrementDestroyProgress(final BlockState cfj, final BlockPos fu, final int integer) {
        final int integer2 = this.gameTicks - integer;
        final float float6 = cfj.getDestroyProgress(this.player, this.player.level, fu) * (integer2 + 1);
        final int integer3 = (int)(float6 * 10.0f);
        if (integer3 != this.lastSentState) {
            this.level.destroyBlockProgress(this.player.getId(), fu, integer3);
            this.lastSentState = integer3;
        }
        return float6;
    }
    
    public void handleBlockBreakAction(final BlockPos fu, final ServerboundPlayerActionPacket.Action a, final Direction fz, final int integer) {
        final double double6 = this.player.getX() - (fu.getX() + 0.5);
        final double double7 = this.player.getY() - (fu.getY() + 0.5) + 1.5;
        final double double8 = this.player.getZ() - (fu.getZ() + 0.5);
        final double double9 = double6 * double6 + double7 * double7 + double8 * double8;
        if (double9 > 36.0) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, false, "too far"));
            return;
        }
        if (fu.getY() >= integer) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, false, "too high"));
            return;
        }
        if (a == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
            if (!this.level.mayInteract(this.player, fu)) {
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, false, "may not interact"));
                return;
            }
            if (this.isCreative()) {
                this.destroyAndAck(fu, a, "creative destroy");
                return;
            }
            if (this.player.blockActionRestricted(this.level, fu, this.gameModeForPlayer)) {
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, false, "block action restricted"));
                return;
            }
            this.destroyProgressStart = this.gameTicks;
            float float14 = 1.0f;
            final BlockState cfj15 = this.level.getBlockState(fu);
            if (!cfj15.isAir()) {
                cfj15.attack(this.level, fu, this.player);
                float14 = cfj15.getDestroyProgress(this.player, this.player.level, fu);
            }
            if (!cfj15.isAir() && float14 >= 1.0f) {
                this.destroyAndAck(fu, a, "insta mine");
            }
            else {
                if (this.isDestroyingBlock) {
                    this.player.connection.send(new ClientboundBlockBreakAckPacket(this.destroyPos, this.level.getBlockState(this.destroyPos), ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, false, "abort destroying since another started (client insta mine, server disagreed)"));
                }
                this.isDestroyingBlock = true;
                this.destroyPos = fu.immutable();
                final int integer2 = (int)(float14 * 10.0f);
                this.level.destroyBlockProgress(this.player.getId(), fu, integer2);
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, true, "actual start of destroying"));
                this.lastSentState = integer2;
            }
        }
        else if (a == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
            if (fu.equals(this.destroyPos)) {
                final int integer3 = this.gameTicks - this.destroyProgressStart;
                final BlockState cfj15 = this.level.getBlockState(fu);
                if (!cfj15.isAir()) {
                    final float float15 = cfj15.getDestroyProgress(this.player, this.player.level, fu) * (integer3 + 1);
                    if (float15 >= 0.7f) {
                        this.isDestroyingBlock = false;
                        this.level.destroyBlockProgress(this.player.getId(), fu, -1);
                        this.destroyAndAck(fu, a, "destroyed");
                        return;
                    }
                    if (!this.hasDelayedDestroy) {
                        this.isDestroyingBlock = false;
                        this.hasDelayedDestroy = true;
                        this.delayedDestroyPos = fu;
                        this.delayedTickStart = this.destroyProgressStart;
                    }
                }
            }
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, true, "stopped destroying"));
        }
        else if (a == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
            this.isDestroyingBlock = false;
            if (!Objects.equals(this.destroyPos, fu)) {
                ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: " + this.destroyPos + " " + fu);
                this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
                this.player.connection.send(new ClientboundBlockBreakAckPacket(this.destroyPos, this.level.getBlockState(this.destroyPos), a, true, "aborted mismatched destroying"));
            }
            this.level.destroyBlockProgress(this.player.getId(), fu, -1);
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, true, "aborted destroying"));
        }
    }
    
    public void destroyAndAck(final BlockPos fu, final ServerboundPlayerActionPacket.Action a, final String string) {
        if (this.destroyBlock(fu)) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, true, string));
        }
        else {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fu, this.level.getBlockState(fu), a, false, string));
        }
    }
    
    public boolean destroyBlock(final BlockPos fu) {
        final BlockState cfj3 = this.level.getBlockState(fu);
        if (!this.player.getMainHandItem().getItem().canAttackBlock(cfj3, this.level, fu, this.player)) {
            return false;
        }
        final BlockEntity cdl4 = this.level.getBlockEntity(fu);
        final Block bvr5 = cfj3.getBlock();
        if ((bvr5 instanceof CommandBlock || bvr5 instanceof StructureBlock || bvr5 instanceof JigsawBlock) && !this.player.canUseGameMasterBlocks()) {
            this.level.sendBlockUpdated(fu, cfj3, cfj3, 3);
            return false;
        }
        if (this.player.blockActionRestricted(this.level, fu, this.gameModeForPlayer)) {
            return false;
        }
        bvr5.playerWillDestroy(this.level, fu, cfj3, this.player);
        final boolean boolean6 = this.level.removeBlock(fu, false);
        if (boolean6) {
            bvr5.destroy(this.level, fu, cfj3);
        }
        if (this.isCreative()) {
            return true;
        }
        final ItemStack bki7 = this.player.getMainHandItem();
        final ItemStack bki8 = bki7.copy();
        final boolean boolean7 = this.player.hasCorrectToolForDrops(cfj3);
        bki7.mineBlock(this.level, cfj3, fu, this.player);
        if (boolean6 && boolean7) {
            bvr5.playerDestroy(this.level, this.player, fu, cfj3, cdl4, bki8);
        }
        return true;
    }
    
    public InteractionResult useItem(final ServerPlayer ze, final Level bqb, final ItemStack bki, final InteractionHand anf) {
        if (this.gameModeForPlayer == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        if (ze.getCooldowns().isOnCooldown(bki.getItem())) {
            return InteractionResult.PASS;
        }
        final int integer6 = bki.getCount();
        final int integer7 = bki.getDamageValue();
        final InteractionResultHolder<ItemStack> anh8 = bki.use(bqb, ze, anf);
        final ItemStack bki2 = anh8.getObject();
        if (bki2 == bki && bki2.getCount() == integer6 && bki2.getUseDuration() <= 0 && bki2.getDamageValue() == integer7) {
            return anh8.getResult();
        }
        if (anh8.getResult() == InteractionResult.FAIL && bki2.getUseDuration() > 0 && !ze.isUsingItem()) {
            return anh8.getResult();
        }
        ze.setItemInHand(anf, bki2);
        if (this.isCreative()) {
            bki2.setCount(integer6);
            if (bki2.isDamageableItem() && bki2.getDamageValue() != integer7) {
                bki2.setDamageValue(integer7);
            }
        }
        if (bki2.isEmpty()) {
            ze.setItemInHand(anf, ItemStack.EMPTY);
        }
        if (!ze.isUsingItem()) {
            ze.refreshContainer(ze.inventoryMenu);
        }
        return anh8.getResult();
    }
    
    public InteractionResult useItemOn(final ServerPlayer ze, final Level bqb, final ItemStack bki, final InteractionHand anf, final BlockHitResult deh) {
        final BlockPos fu7 = deh.getBlockPos();
        final BlockState cfj8 = bqb.getBlockState(fu7);
        if (this.gameModeForPlayer == GameType.SPECTATOR) {
            final MenuProvider anj9 = cfj8.getMenuProvider(bqb, fu7);
            if (anj9 != null) {
                ze.openMenu(anj9);
                return InteractionResult.SUCCESS;
            }
            return InteractionResult.PASS;
        }
        else {
            final boolean boolean9 = !ze.getMainHandItem().isEmpty() || !ze.getOffhandItem().isEmpty();
            final boolean boolean10 = ze.isSecondaryUseActive() && boolean9;
            final ItemStack bki2 = bki.copy();
            if (!boolean10) {
                final InteractionResult ang12 = cfj8.use(bqb, ze, anf, deh);
                if (ang12.consumesAction()) {
                    CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(ze, fu7, bki2);
                    return ang12;
                }
            }
            if (bki.isEmpty() || ze.getCooldowns().isOnCooldown(bki.getItem())) {
                return InteractionResult.PASS;
            }
            final UseOnContext blv12 = new UseOnContext(ze, anf, deh);
            InteractionResult ang13;
            if (this.isCreative()) {
                final int integer14 = bki.getCount();
                ang13 = bki.useOn(blv12);
                bki.setCount(integer14);
            }
            else {
                ang13 = bki.useOn(blv12);
            }
            if (ang13.consumesAction()) {
                CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(ze, fu7, bki2);
            }
            return ang13;
        }
    }
    
    public void setLevel(final ServerLevel zd) {
        this.level = zd;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
