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.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 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 xd) {
        this.gameModeForPlayer = GameType.NOT_SET;
        this.destroyPos = BlockPos.ZERO;
        this.delayedDestroyPos = BlockPos.ZERO;
        this.lastSentState = -1;
        this.level = xd;
    }
    
    public void setGameModeForPlayer(final GameType bjt) {
        (this.gameModeForPlayer = bjt).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 boolean isSurvival() {
        return this.gameModeForPlayer.isSurvival();
    }
    
    public boolean isCreative() {
        return this.gameModeForPlayer.isCreative();
    }
    
    public void updateGameMode(final GameType bjt) {
        if (this.gameModeForPlayer == GameType.NOT_SET) {
            this.gameModeForPlayer = bjt;
        }
        this.setGameModeForPlayer(this.gameModeForPlayer);
    }
    
    public void tick() {
        ++this.gameTicks;
        if (this.hasDelayedDestroy) {
            final BlockState byj2 = this.level.getBlockState(this.delayedDestroyPos);
            if (byj2.isAir()) {
                this.hasDelayedDestroy = false;
            }
            else {
                final float float3 = this.incrementDestroyProgress(byj2, this.delayedDestroyPos, this.delayedTickStart);
                if (float3 >= 1.0f) {
                    this.hasDelayedDestroy = false;
                    this.destroyBlock(this.delayedDestroyPos);
                }
            }
        }
        else if (this.isDestroyingBlock) {
            final BlockState byj2 = this.level.getBlockState(this.destroyPos);
            if (byj2.isAir()) {
                this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
                this.lastSentState = -1;
                this.isDestroyingBlock = false;
            }
            else {
                this.incrementDestroyProgress(byj2, this.destroyPos, this.destroyProgressStart);
            }
        }
    }
    
    private float incrementDestroyProgress(final BlockState byj, final BlockPos fk, final int integer) {
        final int integer2 = this.gameTicks - integer;
        final float float6 = byj.getDestroyProgress(this.player, this.player.level, fk) * (integer2 + 1);
        final int integer3 = (int)(float6 * 10.0f);
        if (integer3 != this.lastSentState) {
            this.level.destroyBlockProgress(this.player.getId(), fk, integer3);
            this.lastSentState = integer3;
        }
        return float6;
    }
    
    public void handleBlockBreakAction(final BlockPos fk, final ServerboundPlayerActionPacket.Action a, final Direction fp, final int integer) {
        final double double6 = this.player.getX() - (fk.getX() + 0.5);
        final double double7 = this.player.getY() - (fk.getY() + 0.5) + 1.5;
        final double double8 = this.player.getZ() - (fk.getZ() + 0.5);
        final double double9 = double6 * double6 + double7 * double7 + double8 * double8;
        if (double9 > 36.0) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, false, "too far"));
            return;
        }
        if (fk.getY() >= integer) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, false, "too high"));
            return;
        }
        if (a == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
            if (!this.level.mayInteract(this.player, fk)) {
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, false, "may not interact"));
                return;
            }
            if (this.isCreative()) {
                if (!this.level.extinguishFire(null, fk, fp)) {
                    this.destroyAndAck(fk, a, "creative destroy");
                }
                else {
                    this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, true, "fire put out"));
                }
                return;
            }
            if (this.player.blockActionRestricted(this.level, fk, this.gameModeForPlayer)) {
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, false, "block action restricted"));
                return;
            }
            this.level.extinguishFire(null, fk, fp);
            this.destroyProgressStart = this.gameTicks;
            float float14 = 1.0f;
            final BlockState byj15 = this.level.getBlockState(fk);
            if (!byj15.isAir()) {
                byj15.attack(this.level, fk, this.player);
                float14 = byj15.getDestroyProgress(this.player, this.player.level, fk);
            }
            if (!byj15.isAir() && float14 >= 1.0f) {
                this.destroyAndAck(fk, 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 = fk.immutable();
                final int integer2 = (int)(float14 * 10.0f);
                this.level.destroyBlockProgress(this.player.getId(), fk, integer2);
                this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, true, "actual start of destroying"));
                this.lastSentState = integer2;
            }
        }
        else if (a == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
            if (fk.equals(this.destroyPos)) {
                final int integer3 = this.gameTicks - this.destroyProgressStart;
                final BlockState byj15 = this.level.getBlockState(fk);
                if (!byj15.isAir()) {
                    final float float15 = byj15.getDestroyProgress(this.player, this.player.level, fk) * (integer3 + 1);
                    if (float15 >= 0.7f) {
                        this.isDestroyingBlock = false;
                        this.level.destroyBlockProgress(this.player.getId(), fk, -1);
                        this.destroyAndAck(fk, a, "destroyed");
                        return;
                    }
                    if (!this.hasDelayedDestroy) {
                        this.isDestroyingBlock = false;
                        this.hasDelayedDestroy = true;
                        this.delayedDestroyPos = fk;
                        this.delayedTickStart = this.destroyProgressStart;
                    }
                }
            }
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, true, "stopped destroying"));
        }
        else if (a == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
            this.isDestroyingBlock = false;
            if (!Objects.equals(this.destroyPos, fk)) {
                ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: " + this.destroyPos + " " + fk);
                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(), fk, -1);
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, true, "aborted destroying"));
        }
    }
    
    public void destroyAndAck(final BlockPos fk, final ServerboundPlayerActionPacket.Action a, final String string) {
        if (this.destroyBlock(fk)) {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, true, string));
        }
        else {
            this.player.connection.send(new ClientboundBlockBreakAckPacket(fk, this.level.getBlockState(fk), a, false, string));
        }
    }
    
    public boolean destroyBlock(final BlockPos fk) {
        final BlockState byj3 = this.level.getBlockState(fk);
        if (!this.player.getMainHandItem().getItem().canAttackBlock(byj3, this.level, fk, this.player)) {
            return false;
        }
        final BlockEntity bwl4 = this.level.getBlockEntity(fk);
        final Block bph5 = byj3.getBlock();
        if ((bph5 instanceof CommandBlock || bph5 instanceof StructureBlock || bph5 instanceof JigsawBlock) && !this.player.canUseGameMasterBlocks()) {
            this.level.sendBlockUpdated(fk, byj3, byj3, 3);
            return false;
        }
        if (this.player.blockActionRestricted(this.level, fk, this.gameModeForPlayer)) {
            return false;
        }
        bph5.playerWillDestroy(this.level, fk, byj3, this.player);
        final boolean boolean6 = this.level.removeBlock(fk, false);
        if (boolean6) {
            bph5.destroy(this.level, fk, byj3);
        }
        if (this.isCreative()) {
            return true;
        }
        final ItemStack ben7 = this.player.getMainHandItem();
        final ItemStack ben8 = ben7.copy();
        final boolean boolean7 = this.player.canDestroy(byj3);
        ben7.mineBlock(this.level, byj3, fk, this.player);
        if (boolean6 && boolean7) {
            bph5.playerDestroy(this.level, this.player, fk, byj3, bwl4, ben8);
        }
        return true;
    }
    
    public InteractionResult useItem(final Player ayj, final Level bjw, final ItemStack ben, final InteractionHand ajk) {
        if (this.gameModeForPlayer == GameType.SPECTATOR) {
            return InteractionResult.PASS;
        }
        if (ayj.getCooldowns().isOnCooldown(ben.getItem())) {
            return InteractionResult.PASS;
        }
        final int integer6 = ben.getCount();
        final int integer7 = ben.getDamageValue();
        final InteractionResultHolder<ItemStack> ajm8 = ben.use(bjw, ayj, ajk);
        final ItemStack ben2 = ajm8.getObject();
        if (ben2 == ben && ben2.getCount() == integer6 && ben2.getUseDuration() <= 0 && ben2.getDamageValue() == integer7) {
            return ajm8.getResult();
        }
        if (ajm8.getResult() == InteractionResult.FAIL && ben2.getUseDuration() > 0 && !ayj.isUsingItem()) {
            return ajm8.getResult();
        }
        ayj.setItemInHand(ajk, ben2);
        if (this.isCreative()) {
            ben2.setCount(integer6);
            if (ben2.isDamageableItem() && ben2.getDamageValue() != integer7) {
                ben2.setDamageValue(integer7);
            }
        }
        if (ben2.isEmpty()) {
            ayj.setItemInHand(ajk, ItemStack.EMPTY);
        }
        if (!ayj.isUsingItem()) {
            ((ServerPlayer)ayj).refreshContainer(ayj.inventoryMenu);
        }
        return ajm8.getResult();
    }
    
    public InteractionResult useItemOn(final Player ayj, final Level bjw, final ItemStack ben, final InteractionHand ajk, final BlockHitResult cvg) {
        final BlockPos fk7 = cvg.getBlockPos();
        final BlockState byj8 = bjw.getBlockState(fk7);
        if (this.gameModeForPlayer == GameType.SPECTATOR) {
            final MenuProvider ajo9 = byj8.getMenuProvider(bjw, fk7);
            if (ajo9 != null) {
                ayj.openMenu(ajo9);
                return InteractionResult.SUCCESS;
            }
            return InteractionResult.PASS;
        }
        else {
            final boolean boolean9 = !ayj.getMainHandItem().isEmpty() || !ayj.getOffhandItem().isEmpty();
            final boolean boolean10 = ayj.isSecondaryUseActive() && boolean9;
            if (!boolean10) {
                final InteractionResult ajl11 = byj8.use(bjw, ayj, ajk, cvg);
                if (ajl11.consumesAction()) {
                    return ajl11;
                }
            }
            if (ben.isEmpty() || ayj.getCooldowns().isOnCooldown(ben.getItem())) {
                return InteractionResult.PASS;
            }
            final UseOnContext bfz11 = new UseOnContext(ayj, ajk, cvg);
            if (this.isCreative()) {
                final int integer12 = ben.getCount();
                final InteractionResult ajl12 = ben.useOn(bfz11);
                ben.setCount(integer12);
                return ajl12;
            }
            return ben.useOn(bfz11);
        }
    }
    
    public void setLevel(final ServerLevel xd) {
        this.level = xd;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
