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.level.block.state.BlockState;
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.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 CompoundTag jt) {
        super.load(jt);
        this.items = NonNullList.<ItemStack>withSize(this.getContainerSize(), ItemStack.EMPTY);
        if (!this.tryLoadLootTable(jt)) {
            ContainerHelper.loadAllItems(jt, this.items);
        }
        this.cooldownTime = jt.getInt("TransferCooldown");
    }
    
    @Override
    public CompoundTag save(final CompoundTag jt) {
        super.save(jt);
        if (!this.trySaveLootTable(jt)) {
            ContainerHelper.saveAllItems(jt, this.items);
        }
        jt.putInt("TransferCooldown", this.cooldownTime);
        return jt;
    }
    
    @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 ben) {
        this.unpackLootTable(null);
        this.getItems().set(integer, ben);
        if (ben.getCount() > this.getMaxStackSize()) {
            ben.setCount(this.getMaxStackSize());
        }
    }
    
    @Override
    protected Component getDefaultName() {
        return new TranslatableComponent("container.hopper", new Object[0]);
    }
    
    @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 ben3 : this.items) {
            if (ben3.isEmpty() || ben3.getCount() != ben3.getMaxStackSize()) {
                return false;
            }
        }
        return true;
    }
    
    private boolean ejectItems() {
        final Container aje2 = this.getAttachedContainer();
        if (aje2 == null) {
            return false;
        }
        final Direction fp3 = this.getBlockState().<Direction>getValue((Property<Direction>)HopperBlock.FACING).getOpposite();
        if (this.isFullContainer(aje2, fp3)) {
            return false;
        }
        for (int integer4 = 0; integer4 < this.getContainerSize(); ++integer4) {
            if (!this.getItem(integer4).isEmpty()) {
                final ItemStack ben5 = this.getItem(integer4).copy();
                final ItemStack ben6 = addItem(this, aje2, this.removeItem(integer4, 1), fp3);
                if (ben6.isEmpty()) {
                    aje2.setChanged();
                    return true;
                }
                this.setItem(integer4, ben5);
            }
        }
        return false;
    }
    
    private static IntStream getSlots(final Container aje, final Direction fp) {
        if (aje instanceof WorldlyContainer) {
            return IntStream.of(((WorldlyContainer)aje).getSlotsForFace(fp));
        }
        return IntStream.range(0, aje.getContainerSize());
    }
    
    private boolean isFullContainer(final Container aje, final Direction fp) {
        final ItemStack ben3;
        return getSlots(aje, fp).allMatch(integer -> {
            ben3 = aje.getItem(integer);
            return ben3.getCount() >= ben3.getMaxStackSize();
        });
    }
    
    private static boolean isEmptyContainer(final Container aje, final Direction fp) {
        return getSlots(aje, fp).allMatch(integer -> aje.getItem(integer).isEmpty());
    }
    
    public static boolean suckInItems(final Hopper bwz) {
        final Container aje2 = getSourceContainer(bwz);
        if (aje2 != null) {
            final Direction fp3 = Direction.DOWN;
            return !isEmptyContainer(aje2, fp3) && getSlots(aje2, fp3).anyMatch(integer -> tryTakeInItemFromSlot(bwz, aje2, integer, fp3));
        }
        for (final ItemEntity awb4 : getItemsAtAndAbove(bwz)) {
            if (addItem(bwz, awb4)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean tryTakeInItemFromSlot(final Hopper bwz, final Container aje, final int integer, final Direction fp) {
        final ItemStack ben5 = aje.getItem(integer);
        if (!ben5.isEmpty() && canTakeItemFromContainer(aje, ben5, integer, fp)) {
            final ItemStack ben6 = ben5.copy();
            final ItemStack ben7 = addItem(aje, bwz, aje.removeItem(integer, 1), null);
            if (ben7.isEmpty()) {
                aje.setChanged();
                return true;
            }
            aje.setItem(integer, ben6);
        }
        return false;
    }
    
    public static boolean addItem(final Container aje, final ItemEntity awb) {
        boolean boolean3 = false;
        final ItemStack ben4 = awb.getItem().copy();
        final ItemStack ben5 = addItem(null, aje, ben4, null);
        if (ben5.isEmpty()) {
            boolean3 = true;
            awb.remove();
        }
        else {
            awb.setItem(ben5);
        }
        return boolean3;
    }
    
    public static ItemStack addItem(@Nullable final Container aje1, final Container aje2, ItemStack ben, @Nullable final Direction fp) {
        if (aje2 instanceof WorldlyContainer && fp != null) {
            final WorldlyContainer aju5 = (WorldlyContainer)aje2;
            final int[] arr6 = aju5.getSlotsForFace(fp);
            for (int integer7 = 0; integer7 < arr6.length && !ben.isEmpty(); ben = tryMoveInItem(aje1, aje2, ben, arr6[integer7], fp), ++integer7) {}
        }
        else {
            for (int integer8 = aje2.getContainerSize(), integer9 = 0; integer9 < integer8 && !ben.isEmpty(); ben = tryMoveInItem(aje1, aje2, ben, integer9, fp), ++integer9) {}
        }
        return ben;
    }
    
    private static boolean canPlaceItemInContainer(final Container aje, final ItemStack ben, final int integer, @Nullable final Direction fp) {
        return aje.canPlaceItem(integer, ben) && (!(aje instanceof WorldlyContainer) || ((WorldlyContainer)aje).canPlaceItemThroughFace(integer, ben, fp));
    }
    
    private static boolean canTakeItemFromContainer(final Container aje, final ItemStack ben, final int integer, final Direction fp) {
        return !(aje instanceof WorldlyContainer) || ((WorldlyContainer)aje).canTakeItemThroughFace(integer, ben, fp);
    }
    
    private static ItemStack tryMoveInItem(@Nullable final Container aje1, final Container aje2, ItemStack ben, final int integer, @Nullable final Direction fp) {
        final ItemStack ben2 = aje2.getItem(integer);
        if (canPlaceItemInContainer(aje2, ben, integer, fp)) {
            boolean boolean7 = false;
            final boolean boolean8 = aje2.isEmpty();
            if (ben2.isEmpty()) {
                aje2.setItem(integer, ben);
                ben = ItemStack.EMPTY;
                boolean7 = true;
            }
            else if (canMergeItems(ben2, ben)) {
                final int integer2 = ben.getMaxStackSize() - ben2.getCount();
                final int integer3 = Math.min(ben.getCount(), integer2);
                ben.shrink(integer3);
                ben2.grow(integer3);
                boolean7 = (integer3 > 0);
            }
            if (boolean7) {
                if (boolean8 && aje2 instanceof HopperBlockEntity) {
                    final HopperBlockEntity bxa9 = (HopperBlockEntity)aje2;
                    if (!bxa9.isOnCustomCooldown()) {
                        int integer3 = 0;
                        if (aje1 instanceof HopperBlockEntity) {
                            final HopperBlockEntity bxa10 = (HopperBlockEntity)aje1;
                            if (bxa9.tickedGameTime >= bxa10.tickedGameTime) {
                                integer3 = 1;
                            }
                        }
                        bxa9.setCooldown(8 - integer3);
                    }
                }
                aje2.setChanged();
            }
        }
        return ben;
    }
    
    @Nullable
    private Container getAttachedContainer() {
        final Direction fp2 = this.getBlockState().<Direction>getValue((Property<Direction>)HopperBlock.FACING);
        return getContainerAt(this.getLevel(), this.worldPosition.relative(fp2));
    }
    
    @Nullable
    public static Container getSourceContainer(final Hopper bwz) {
        return getContainerAt(bwz.getLevel(), bwz.getLevelX(), bwz.getLevelY() + 1.0, bwz.getLevelZ());
    }
    
    public static List<ItemEntity> getItemsAtAndAbove(final Hopper bwz) {
        return bwz.getSuckShape().toAabbs().stream().flatMap(cvf -> bwz.getLevel().<Entity>getEntitiesOfClass(ItemEntity.class, cvf.move(bwz.getLevelX() - 0.5, bwz.getLevelY() - 0.5, bwz.getLevelZ() - 0.5), EntitySelector.ENTITY_STILL_ALIVE).stream()).collect(Collectors.toList());
    }
    
    @Nullable
    public static Container getContainerAt(final Level bjw, final BlockPos fk) {
        return getContainerAt(bjw, fk.getX() + 0.5, fk.getY() + 0.5, fk.getZ() + 0.5);
    }
    
    @Nullable
    public static Container getContainerAt(final Level bjw, final double double2, final double double3, final double double4) {
        Container aje8 = null;
        final BlockPos fk9 = new BlockPos(double2, double3, double4);
        final BlockState byj10 = bjw.getBlockState(fk9);
        final Block bph11 = byj10.getBlock();
        if (bph11 instanceof WorldlyContainerHolder) {
            aje8 = ((WorldlyContainerHolder)bph11).getContainer(byj10, bjw, fk9);
        }
        else if (bph11.isEntityBlock()) {
            final BlockEntity bwl12 = bjw.getBlockEntity(fk9);
            if (bwl12 instanceof Container) {
                aje8 = (Container)bwl12;
                if (aje8 instanceof ChestBlockEntity && bph11 instanceof ChestBlock) {
                    aje8 = ChestBlock.getContainer((ChestBlock)bph11, byj10, bjw, fk9, true);
                }
            }
        }
        if (aje8 == null) {
            final List<Entity> list12 = bjw.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()) {
                aje8 = (Container)list12.get(bjw.random.nextInt(list12.size()));
            }
        }
        return aje8;
    }
    
    private static boolean canMergeItems(final ItemStack ben1, final ItemStack ben2) {
        return ben1.getItem() == ben2.getItem() && ben1.getDamageValue() == ben2.getDamageValue() && ben1.getCount() <= ben1.getMaxStackSize() && ItemStack.tagMatches(ben1, ben2);
    }
    
    @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> fy) {
        this.items = fy;
    }
    
    public void entityInside(final Entity akq) {
        if (akq instanceof ItemEntity) {
            final BlockPos fk3 = this.getBlockPos();
            if (Shapes.joinIsNotEmpty(Shapes.create(akq.getBoundingBox().move(-fk3.getX(), -fk3.getY(), -fk3.getZ())), this.getSuckShape(), BooleanOp.AND)) {
                this.tryMoveItems(() -> addItem(this, (ItemEntity)akq));
            }
        }
    }
    
    @Override
    protected AbstractContainerMenu createMenu(final int integer, final Inventory ayi) {
        return new HopperMenu(integer, ayi, this);
    }
}
