package net.minecraft.world.level.block.entity;

import java.util.stream.Stream;
import net.minecraft.world.inventory.HopperMenu;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.WorldlyContainerHolder;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.Predicate;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import javax.annotation.Nullable;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.WorldlyContainer;
import java.util.stream.IntStream;
import net.minecraft.world.Container;
import net.minecraft.core.Direction;
import java.util.Iterator;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.HopperBlock;
import java.util.function.Supplier;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.chat.Component;
import java.util.List;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.ContainerHelper;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.NonNullList;

public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper, TickableBlockEntity {
    private NonNullList<ItemStack> items;
    private int cooldownTime;
    private long tickedGameTime;
    
    public HopperBlockEntity() {
        super(BlockEntityType.HOPPER);
        this.items = NonNullList.<ItemStack>withSize(5, ItemStack.EMPTY);
        this.cooldownTime = -1;
    }
    
    @Override
    public void load(final BlockState cfj, final CompoundTag le) {
        super.load(cfj, le);
        this.items = NonNullList.<ItemStack>withSize(this.getContainerSize(), ItemStack.EMPTY);
        if (!this.tryLoadLootTable(le)) {
            ContainerHelper.loadAllItems(le, this.items);
        }
        this.cooldownTime = le.getInt("TransferCooldown");
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        super.save(le);
        if (!this.trySaveLootTable(le)) {
            ContainerHelper.saveAllItems(le, this.items);
        }
        le.putInt("TransferCooldown", this.cooldownTime);
        return le;
    }
    
    @Override
    public int getContainerSize() {
        return this.items.size();
    }
    
    @Override
    public ItemStack removeItem(final int integer1, final int integer2) {
        this.unpackLootTable(null);
        return ContainerHelper.removeItem(this.getItems(), integer1, integer2);
    }
    
    @Override
    public void setItem(final int integer, final ItemStack bki) {
        this.unpackLootTable(null);
        this.getItems().set(integer, bki);
        if (bki.getCount() > this.getMaxStackSize()) {
            bki.setCount(this.getMaxStackSize());
        }
    }
    
    @Override
    protected Component getDefaultName() {
        return new TranslatableComponent("container.hopper");
    }
    
    @Override
    public void tick() {
        if (this.level == null || this.level.isClientSide) {
            return;
        }
        --this.cooldownTime;
        this.tickedGameTime = this.level.getGameTime();
        if (!this.isOnCooldown()) {
            this.setCooldown(0);
            this.tryMoveItems(() -> suckInItems(this));
        }
    }
    
    private boolean tryMoveItems(final Supplier<Boolean> supplier) {
        if (this.level == null || this.level.isClientSide) {
            return false;
        }
        if (!this.isOnCooldown() && this.getBlockState().<Boolean>getValue((Property<Boolean>)HopperBlock.ENABLED)) {
            boolean boolean3 = false;
            if (!this.isEmpty()) {
                boolean3 = this.ejectItems();
            }
            if (!this.inventoryFull()) {
                boolean3 |= supplier.get();
            }
            if (boolean3) {
                this.setCooldown(8);
                this.setChanged();
                return true;
            }
        }
        return false;
    }
    
    private boolean inventoryFull() {
        for (final ItemStack bki3 : this.items) {
            if (bki3.isEmpty() || bki3.getCount() != bki3.getMaxStackSize()) {
                return false;
            }
        }
        return true;
    }
    
    private boolean ejectItems() {
        final Container amz2 = this.getAttachedContainer();
        if (amz2 == null) {
            return false;
        }
        final Direction fz3 = this.getBlockState().<Direction>getValue((Property<Direction>)HopperBlock.FACING).getOpposite();
        if (this.isFullContainer(amz2, fz3)) {
            return false;
        }
        for (int integer4 = 0; integer4 < this.getContainerSize(); ++integer4) {
            if (!this.getItem(integer4).isEmpty()) {
                final ItemStack bki5 = this.getItem(integer4).copy();
                final ItemStack bki6 = addItem(this, amz2, this.removeItem(integer4, 1), fz3);
                if (bki6.isEmpty()) {
                    amz2.setChanged();
                    return true;
                }
                this.setItem(integer4, bki5);
            }
        }
        return false;
    }
    
    private static IntStream getSlots(final Container amz, final Direction fz) {
        if (amz instanceof WorldlyContainer) {
            return IntStream.of(((WorldlyContainer)amz).getSlotsForFace(fz));
        }
        return IntStream.range(0, amz.getContainerSize());
    }
    
    private boolean isFullContainer(final Container amz, final Direction fz) {
        final ItemStack bki3;
        return getSlots(amz, fz).allMatch(integer -> {
            bki3 = amz.getItem(integer);
            return bki3.getCount() >= bki3.getMaxStackSize();
        });
    }
    
    private static boolean isEmptyContainer(final Container amz, final Direction fz) {
        return getSlots(amz, fz).allMatch(integer -> amz.getItem(integer).isEmpty());
    }
    
    public static boolean suckInItems(final Hopper cdz) {
        final Container amz2 = getSourceContainer(cdz);
        if (amz2 != null) {
            final Direction fz3 = Direction.DOWN;
            return !isEmptyContainer(amz2, fz3) && getSlots(amz2, fz3).anyMatch(integer -> tryTakeInItemFromSlot(cdz, amz2, integer, fz3));
        }
        for (final ItemEntity bbg4 : getItemsAtAndAbove(cdz)) {
            if (addItem(cdz, bbg4)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean tryTakeInItemFromSlot(final Hopper cdz, final Container amz, final int integer, final Direction fz) {
        final ItemStack bki5 = amz.getItem(integer);
        if (!bki5.isEmpty() && canTakeItemFromContainer(amz, bki5, integer, fz)) {
            final ItemStack bki6 = bki5.copy();
            final ItemStack bki7 = addItem(amz, cdz, amz.removeItem(integer, 1), null);
            if (bki7.isEmpty()) {
                amz.setChanged();
                return true;
            }
            amz.setItem(integer, bki6);
        }
        return false;
    }
    
    public static boolean addItem(final Container amz, final ItemEntity bbg) {
        boolean boolean3 = false;
        final ItemStack bki4 = bbg.getItem().copy();
        final ItemStack bki5 = addItem(null, amz, bki4, null);
        if (bki5.isEmpty()) {
            boolean3 = true;
            bbg.remove();
        }
        else {
            bbg.setItem(bki5);
        }
        return boolean3;
    }
    
    public static ItemStack addItem(@Nullable final Container amz1, final Container amz2, ItemStack bki, @Nullable final Direction fz) {
        if (amz2 instanceof WorldlyContainer && fz != null) {
            final WorldlyContainer anq5 = (WorldlyContainer)amz2;
            final int[] arr6 = anq5.getSlotsForFace(fz);
            for (int integer7 = 0; integer7 < arr6.length && !bki.isEmpty(); bki = tryMoveInItem(amz1, amz2, bki, arr6[integer7], fz), ++integer7) {}
        }
        else {
            for (int integer8 = amz2.getContainerSize(), integer9 = 0; integer9 < integer8 && !bki.isEmpty(); bki = tryMoveInItem(amz1, amz2, bki, integer9, fz), ++integer9) {}
        }
        return bki;
    }
    
    private static boolean canPlaceItemInContainer(final Container amz, final ItemStack bki, final int integer, @Nullable final Direction fz) {
        return amz.canPlaceItem(integer, bki) && (!(amz instanceof WorldlyContainer) || ((WorldlyContainer)amz).canPlaceItemThroughFace(integer, bki, fz));
    }
    
    private static boolean canTakeItemFromContainer(final Container amz, final ItemStack bki, final int integer, final Direction fz) {
        return !(amz instanceof WorldlyContainer) || ((WorldlyContainer)amz).canTakeItemThroughFace(integer, bki, fz);
    }
    
    private static ItemStack tryMoveInItem(@Nullable final Container amz1, final Container amz2, ItemStack bki, final int integer, @Nullable final Direction fz) {
        final ItemStack bki2 = amz2.getItem(integer);
        if (canPlaceItemInContainer(amz2, bki, integer, fz)) {
            boolean boolean7 = false;
            final boolean boolean8 = amz2.isEmpty();
            if (bki2.isEmpty()) {
                amz2.setItem(integer, bki);
                bki = ItemStack.EMPTY;
                boolean7 = true;
            }
            else if (canMergeItems(bki2, bki)) {
                final int integer2 = bki.getMaxStackSize() - bki2.getCount();
                final int integer3 = Math.min(bki.getCount(), integer2);
                bki.shrink(integer3);
                bki2.grow(integer3);
                boolean7 = (integer3 > 0);
            }
            if (boolean7) {
                if (boolean8 && amz2 instanceof HopperBlockEntity) {
                    final HopperBlockEntity cea9 = (HopperBlockEntity)amz2;
                    if (!cea9.isOnCustomCooldown()) {
                        int integer3 = 0;
                        if (amz1 instanceof HopperBlockEntity) {
                            final HopperBlockEntity cea10 = (HopperBlockEntity)amz1;
                            if (cea9.tickedGameTime >= cea10.tickedGameTime) {
                                integer3 = 1;
                            }
                        }
                        cea9.setCooldown(8 - integer3);
                    }
                }
                amz2.setChanged();
            }
        }
        return bki;
    }
    
    @Nullable
    private Container getAttachedContainer() {
        final Direction fz2 = this.getBlockState().<Direction>getValue((Property<Direction>)HopperBlock.FACING);
        return getContainerAt(this.getLevel(), this.worldPosition.relative(fz2));
    }
    
    @Nullable
    public static Container getSourceContainer(final Hopper cdz) {
        return getContainerAt(cdz.getLevel(), cdz.getLevelX(), cdz.getLevelY() + 1.0, cdz.getLevelZ());
    }
    
    public static List<ItemEntity> getItemsAtAndAbove(final Hopper cdz) {
        return cdz.getSuckShape().toAabbs().stream().flatMap(deg -> cdz.getLevel().<Entity>getEntitiesOfClass(ItemEntity.class, deg.move(cdz.getLevelX() - 0.5, cdz.getLevelY() - 0.5, cdz.getLevelZ() - 0.5), EntitySelector.ENTITY_STILL_ALIVE).stream()).collect(Collectors.toList());
    }
    
    @Nullable
    public static Container getContainerAt(final Level bqb, final BlockPos fu) {
        return getContainerAt(bqb, fu.getX() + 0.5, fu.getY() + 0.5, fu.getZ() + 0.5);
    }
    
    @Nullable
    public static Container getContainerAt(final Level bqb, final double double2, final double double3, final double double4) {
        Container amz8 = null;
        final BlockPos fu9 = new BlockPos(double2, double3, double4);
        final BlockState cfj10 = bqb.getBlockState(fu9);
        final Block bvr11 = cfj10.getBlock();
        if (bvr11 instanceof WorldlyContainerHolder) {
            amz8 = ((WorldlyContainerHolder)bvr11).getContainer(cfj10, bqb, fu9);
        }
        else if (bvr11.isEntityBlock()) {
            final BlockEntity cdl12 = bqb.getBlockEntity(fu9);
            if (cdl12 instanceof Container) {
                amz8 = (Container)cdl12;
                if (amz8 instanceof ChestBlockEntity && bvr11 instanceof ChestBlock) {
                    amz8 = ChestBlock.getContainer((ChestBlock)bvr11, cfj10, bqb, fu9, true);
                }
            }
        }
        if (amz8 == null) {
            final List<Entity> list12 = bqb.getEntities(null, new AABB(double2 - 0.5, double3 - 0.5, double4 - 0.5, double2 + 0.5, double3 + 0.5, double4 + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR);
            if (!list12.isEmpty()) {
                amz8 = (Container)list12.get(bqb.random.nextInt(list12.size()));
            }
        }
        return amz8;
    }
    
    private static boolean canMergeItems(final ItemStack bki1, final ItemStack bki2) {
        return bki1.getItem() == bki2.getItem() && bki1.getDamageValue() == bki2.getDamageValue() && bki1.getCount() <= bki1.getMaxStackSize() && ItemStack.tagMatches(bki1, bki2);
    }
    
    @Override
    public double getLevelX() {
        return this.worldPosition.getX() + 0.5;
    }
    
    @Override
    public double getLevelY() {
        return this.worldPosition.getY() + 0.5;
    }
    
    @Override
    public double getLevelZ() {
        return this.worldPosition.getZ() + 0.5;
    }
    
    private void setCooldown(final int integer) {
        this.cooldownTime = integer;
    }
    
    private boolean isOnCooldown() {
        return this.cooldownTime > 0;
    }
    
    private boolean isOnCustomCooldown() {
        return this.cooldownTime > 8;
    }
    
    @Override
    protected NonNullList<ItemStack> getItems() {
        return this.items;
    }
    
    @Override
    protected void setItems(final NonNullList<ItemStack> gi) {
        this.items = gi;
    }
    
    public void entityInside(final Entity aom) {
        if (aom instanceof ItemEntity) {
            final BlockPos fu3 = this.getBlockPos();
            if (Shapes.joinIsNotEmpty(Shapes.create(aom.getBoundingBox().move(-fu3.getX(), -fu3.getY(), -fu3.getZ())), this.getSuckShape(), BooleanOp.AND)) {
                this.tryMoveItems(() -> addItem(this, (ItemEntity)aom));
            }
        }
    }
    
    @Override
    protected AbstractContainerMenu createMenu(final int integer, final Inventory beb) {
        return new HopperMenu(integer, beb, this);
    }
}
