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

import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.entity.player.StackedContents;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ExperienceOrb;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import com.google.common.collect.Lists;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.Level;
import java.util.Collection;
import net.minecraft.world.entity.player.Player;
import java.util.List;
import net.minecraft.core.Direction;
import java.util.function.Function;
import javax.annotation.Nullable;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.AbstractFurnaceBlock;
import net.minecraft.util.Mth;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.ContainerHelper;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.Util;
import net.minecraft.SharedConstants;
import java.util.Iterator;
import net.minecraft.tags.Tag;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.item.Items;
import com.google.common.collect.Maps;
import net.minecraft.world.item.Item;
import java.util.Map;
import net.minecraft.world.item.crafting.AbstractCookingRecipe;
import net.minecraft.world.item.crafting.RecipeType;
import net.minecraft.resources.ResourceLocation;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.world.inventory.ContainerData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.NonNullList;
import net.minecraft.world.inventory.StackedContentsCompatible;
import net.minecraft.world.inventory.RecipeHolder;
import net.minecraft.world.WorldlyContainer;

public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeHolder, StackedContentsCompatible, TickableBlockEntity {
    private static final int[] SLOTS_FOR_UP;
    private static final int[] SLOTS_FOR_DOWN;
    private static final int[] SLOTS_FOR_SIDES;
    protected NonNullList<ItemStack> items;
    private int litTime;
    private int litDuration;
    private int cookingProgress;
    private int cookingTotalTime;
    protected final ContainerData dataAccess;
    private final Object2IntOpenHashMap<ResourceLocation> recipesUsed;
    protected final RecipeType<? extends AbstractCookingRecipe> recipeType;
    
    protected AbstractFurnaceBlockEntity(final BlockEntityType<?> cdm, final RecipeType<? extends AbstractCookingRecipe> bmx) {
        super(cdm);
        this.items = NonNullList.<ItemStack>withSize(3, ItemStack.EMPTY);
        this.dataAccess = new ContainerData() {
            @Override
            public int get(final int integer) {
                switch (integer) {
                    case 0: {
                        return AbstractFurnaceBlockEntity.this.litTime;
                    }
                    case 1: {
                        return AbstractFurnaceBlockEntity.this.litDuration;
                    }
                    case 2: {
                        return AbstractFurnaceBlockEntity.this.cookingProgress;
                    }
                    case 3: {
                        return AbstractFurnaceBlockEntity.this.cookingTotalTime;
                    }
                    default: {
                        return 0;
                    }
                }
            }
            
            @Override
            public void set(final int integer1, final int integer2) {
                switch (integer1) {
                    case 0: {
                        AbstractFurnaceBlockEntity.this.litTime = integer2;
                        break;
                    }
                    case 1: {
                        AbstractFurnaceBlockEntity.this.litDuration = integer2;
                        break;
                    }
                    case 2: {
                        AbstractFurnaceBlockEntity.this.cookingProgress = integer2;
                        break;
                    }
                    case 3: {
                        AbstractFurnaceBlockEntity.this.cookingTotalTime = integer2;
                        break;
                    }
                }
            }
            
            @Override
            public int getCount() {
                return 4;
            }
        };
        this.recipesUsed = (Object2IntOpenHashMap<ResourceLocation>)new Object2IntOpenHashMap();
        this.recipeType = bmx;
    }
    
    public static Map<Item, Integer> getFuel() {
        final Map<Item, Integer> map1 = Maps.newLinkedHashMap();
        add(map1, Items.LAVA_BUCKET, 20000);
        add(map1, Blocks.COAL_BLOCK, 16000);
        add(map1, Items.BLAZE_ROD, 2400);
        add(map1, Items.COAL, 1600);
        add(map1, Items.CHARCOAL, 1600);
        add(map1, ItemTags.LOGS, 300);
        add(map1, ItemTags.PLANKS, 300);
        add(map1, ItemTags.WOODEN_STAIRS, 300);
        add(map1, ItemTags.WOODEN_SLABS, 150);
        add(map1, ItemTags.WOODEN_TRAPDOORS, 300);
        add(map1, ItemTags.WOODEN_PRESSURE_PLATES, 300);
        add(map1, Blocks.OAK_FENCE, 300);
        add(map1, Blocks.BIRCH_FENCE, 300);
        add(map1, Blocks.SPRUCE_FENCE, 300);
        add(map1, Blocks.JUNGLE_FENCE, 300);
        add(map1, Blocks.DARK_OAK_FENCE, 300);
        add(map1, Blocks.ACACIA_FENCE, 300);
        add(map1, Blocks.OAK_FENCE_GATE, 300);
        add(map1, Blocks.BIRCH_FENCE_GATE, 300);
        add(map1, Blocks.SPRUCE_FENCE_GATE, 300);
        add(map1, Blocks.JUNGLE_FENCE_GATE, 300);
        add(map1, Blocks.DARK_OAK_FENCE_GATE, 300);
        add(map1, Blocks.ACACIA_FENCE_GATE, 300);
        add(map1, Blocks.NOTE_BLOCK, 300);
        add(map1, Blocks.BOOKSHELF, 300);
        add(map1, Blocks.LECTERN, 300);
        add(map1, Blocks.JUKEBOX, 300);
        add(map1, Blocks.CHEST, 300);
        add(map1, Blocks.TRAPPED_CHEST, 300);
        add(map1, Blocks.CRAFTING_TABLE, 300);
        add(map1, Blocks.DAYLIGHT_DETECTOR, 300);
        add(map1, ItemTags.BANNERS, 300);
        add(map1, Items.BOW, 300);
        add(map1, Items.FISHING_ROD, 300);
        add(map1, Blocks.LADDER, 300);
        add(map1, ItemTags.SIGNS, 200);
        add(map1, Items.WOODEN_SHOVEL, 200);
        add(map1, Items.WOODEN_SWORD, 200);
        add(map1, Items.WOODEN_HOE, 200);
        add(map1, Items.WOODEN_AXE, 200);
        add(map1, Items.WOODEN_PICKAXE, 200);
        add(map1, ItemTags.WOODEN_DOORS, 200);
        add(map1, ItemTags.BOATS, 1200);
        add(map1, ItemTags.WOOL, 100);
        add(map1, ItemTags.WOODEN_BUTTONS, 100);
        add(map1, Items.STICK, 100);
        add(map1, ItemTags.SAPLINGS, 100);
        add(map1, Items.BOWL, 100);
        add(map1, ItemTags.CARPETS, 67);
        add(map1, Blocks.DRIED_KELP_BLOCK, 4001);
        add(map1, Items.CROSSBOW, 300);
        add(map1, Blocks.BAMBOO, 50);
        add(map1, Blocks.DEAD_BUSH, 100);
        add(map1, Blocks.SCAFFOLDING, 400);
        add(map1, Blocks.LOOM, 300);
        add(map1, Blocks.BARREL, 300);
        add(map1, Blocks.CARTOGRAPHY_TABLE, 300);
        add(map1, Blocks.FLETCHING_TABLE, 300);
        add(map1, Blocks.SMITHING_TABLE, 300);
        add(map1, Blocks.COMPOSTER, 300);
        return map1;
    }
    
    private static boolean isNeverAFurnaceFuel(final Item bke) {
        return ItemTags.NON_FLAMMABLE_WOOD.contains(bke);
    }
    
    private static void add(final Map<Item, Integer> map, final Tag<Item> adf, final int integer) {
        for (final Item bke5 : adf.getValues()) {
            if (!isNeverAFurnaceFuel(bke5)) {
                map.put(bke5, integer);
            }
        }
    }
    
    private static void add(final Map<Item, Integer> map, final ItemLike bqa, final int integer) {
        final Item bke4 = bqa.asItem();
        if (!isNeverAFurnaceFuel(bke4)) {
            map.put(bke4, integer);
            return;
        }
        if (SharedConstants.IS_RUNNING_IN_IDE) {
            throw Util.<IllegalStateException>pauseInIde(new IllegalStateException("A developer tried to explicitly make fire resistant item " + bke4.getName(null).getString() + " a furnace fuel. That will not work!"));
        }
    }
    
    private boolean isLit() {
        return this.litTime > 0;
    }
    
    @Override
    public void load(final BlockState cfj, final CompoundTag le) {
        super.load(cfj, le);
        ContainerHelper.loadAllItems(le, this.items = NonNullList.<ItemStack>withSize(this.getContainerSize(), ItemStack.EMPTY));
        this.litTime = le.getShort("BurnTime");
        this.cookingProgress = le.getShort("CookTime");
        this.cookingTotalTime = le.getShort("CookTimeTotal");
        this.litDuration = this.getBurnDuration(this.items.get(1));
        final CompoundTag le2 = le.getCompound("RecipesUsed");
        for (final String string6 : le2.getAllKeys()) {
            this.recipesUsed.put(new ResourceLocation(string6), le2.getInt(string6));
        }
    }
    
    @Override
    public CompoundTag save(final CompoundTag le) {
        super.save(le);
        le.putShort("BurnTime", (short)this.litTime);
        le.putShort("CookTime", (short)this.cookingProgress);
        le.putShort("CookTimeTotal", (short)this.cookingTotalTime);
        ContainerHelper.saveAllItems(le, this.items);
        final CompoundTag le2 = new CompoundTag();
        this.recipesUsed.forEach((uh, integer) -> le2.putInt(uh.toString(), integer));
        le.put("RecipesUsed", le2);
        return le;
    }
    
    @Override
    public void tick() {
        final boolean boolean2 = this.isLit();
        boolean boolean3 = false;
        if (this.isLit()) {
            --this.litTime;
        }
        if (!this.level.isClientSide) {
            final ItemStack bki4 = this.items.get(1);
            if (this.isLit() || (!bki4.isEmpty() && !this.items.get(0).isEmpty())) {
                final Recipe<?> bmu5 = this.level.getRecipeManager().getRecipeFor(this.recipeType, this, this.level).orElse((AbstractCookingRecipe)null);
                if (!this.isLit() && this.canBurn(bmu5)) {
                    this.litTime = this.getBurnDuration(bki4);
                    this.litDuration = this.litTime;
                    if (this.isLit()) {
                        boolean3 = true;
                        if (!bki4.isEmpty()) {
                            final Item bke6 = bki4.getItem();
                            bki4.shrink(1);
                            if (bki4.isEmpty()) {
                                final Item bke7 = bke6.getCraftingRemainingItem();
                                this.items.set(1, (bke7 == null) ? ItemStack.EMPTY : new ItemStack(bke7));
                            }
                        }
                    }
                }
                if (this.isLit() && this.canBurn(bmu5)) {
                    ++this.cookingProgress;
                    if (this.cookingProgress == this.cookingTotalTime) {
                        this.cookingProgress = 0;
                        this.cookingTotalTime = this.getTotalCookTime();
                        this.burn(bmu5);
                        boolean3 = true;
                    }
                }
                else {
                    this.cookingProgress = 0;
                }
            }
            else if (!this.isLit() && this.cookingProgress > 0) {
                this.cookingProgress = Mth.clamp(this.cookingProgress - 2, 0, this.cookingTotalTime);
            }
            if (boolean2 != this.isLit()) {
                boolean3 = true;
                this.level.setBlock(this.worldPosition, ((StateHolder<O, BlockState>)this.level.getBlockState(this.worldPosition)).<Comparable, Boolean>setValue((Property<Comparable>)AbstractFurnaceBlock.LIT, this.isLit()), 3);
            }
        }
        if (boolean3) {
            this.setChanged();
        }
    }
    
    protected boolean canBurn(@Nullable final Recipe<?> bmu) {
        if (this.items.get(0).isEmpty() || bmu == null) {
            return false;
        }
        final ItemStack bki3 = bmu.getResultItem();
        if (bki3.isEmpty()) {
            return false;
        }
        final ItemStack bki4 = this.items.get(2);
        return bki4.isEmpty() || (bki4.sameItem(bki3) && ((bki4.getCount() < this.getMaxStackSize() && bki4.getCount() < bki4.getMaxStackSize()) || bki4.getCount() < bki3.getMaxStackSize()));
    }
    
    private void burn(@Nullable final Recipe<?> bmu) {
        if (bmu == null || !this.canBurn(bmu)) {
            return;
        }
        final ItemStack bki3 = this.items.get(0);
        final ItemStack bki4 = bmu.getResultItem();
        final ItemStack bki5 = this.items.get(2);
        if (bki5.isEmpty()) {
            this.items.set(2, bki4.copy());
        }
        else if (bki5.getItem() == bki4.getItem()) {
            bki5.grow(1);
        }
        if (!this.level.isClientSide) {
            this.setRecipeUsed(bmu);
        }
        if (bki3.getItem() == Blocks.WET_SPONGE.asItem() && !this.items.get(1).isEmpty() && this.items.get(1).getItem() == Items.BUCKET) {
            this.items.set(1, new ItemStack(Items.WATER_BUCKET));
        }
        bki3.shrink(1);
    }
    
    protected int getBurnDuration(final ItemStack bki) {
        if (bki.isEmpty()) {
            return 0;
        }
        final Item bke3 = bki.getItem();
        return getFuel().getOrDefault(bke3, 0);
    }
    
    protected int getTotalCookTime() {
        return this.level.getRecipeManager().getRecipeFor(this.recipeType, this, this.level).<Integer>map(AbstractCookingRecipe::getCookingTime).orElse(200);
    }
    
    public static boolean isFuel(final ItemStack bki) {
        return getFuel().containsKey(bki.getItem());
    }
    
    @Override
    public int[] getSlotsForFace(final Direction fz) {
        if (fz == Direction.DOWN) {
            return AbstractFurnaceBlockEntity.SLOTS_FOR_DOWN;
        }
        if (fz == Direction.UP) {
            return AbstractFurnaceBlockEntity.SLOTS_FOR_UP;
        }
        return AbstractFurnaceBlockEntity.SLOTS_FOR_SIDES;
    }
    
    @Override
    public boolean canPlaceItemThroughFace(final int integer, final ItemStack bki, @Nullable final Direction fz) {
        return this.canPlaceItem(integer, bki);
    }
    
    @Override
    public boolean canTakeItemThroughFace(final int integer, final ItemStack bki, final Direction fz) {
        if (fz == Direction.DOWN && integer == 1) {
            final Item bke5 = bki.getItem();
            if (bke5 != Items.WATER_BUCKET && bke5 != Items.BUCKET) {
                return false;
            }
        }
        return true;
    }
    
    @Override
    public int getContainerSize() {
        return this.items.size();
    }
    
    @Override
    public boolean isEmpty() {
        for (final ItemStack bki3 : this.items) {
            if (!bki3.isEmpty()) {
                return false;
            }
        }
        return true;
    }
    
    @Override
    public ItemStack getItem(final int integer) {
        return this.items.get(integer);
    }
    
    @Override
    public ItemStack removeItem(final int integer1, final int integer2) {
        return ContainerHelper.removeItem(this.items, integer1, integer2);
    }
    
    @Override
    public ItemStack removeItemNoUpdate(final int integer) {
        return ContainerHelper.takeItem(this.items, integer);
    }
    
    @Override
    public void setItem(final int integer, final ItemStack bki) {
        final ItemStack bki2 = this.items.get(integer);
        final boolean boolean5 = !bki.isEmpty() && bki.sameItem(bki2) && ItemStack.tagMatches(bki, bki2);
        this.items.set(integer, bki);
        if (bki.getCount() > this.getMaxStackSize()) {
            bki.setCount(this.getMaxStackSize());
        }
        if (integer == 0 && !boolean5) {
            this.cookingTotalTime = this.getTotalCookTime();
            this.cookingProgress = 0;
            this.setChanged();
        }
    }
    
    @Override
    public boolean stillValid(final Player bec) {
        return this.level.getBlockEntity(this.worldPosition) == this && bec.distanceToSqr(this.worldPosition.getX() + 0.5, this.worldPosition.getY() + 0.5, this.worldPosition.getZ() + 0.5) <= 64.0;
    }
    
    @Override
    public boolean canPlaceItem(final int integer, final ItemStack bki) {
        if (integer == 2) {
            return false;
        }
        if (integer == 1) {
            final ItemStack bki2 = this.items.get(1);
            return isFuel(bki) || (bki.getItem() == Items.BUCKET && bki2.getItem() != Items.BUCKET);
        }
        return true;
    }
    
    @Override
    public void clearContent() {
        this.items.clear();
    }
    
    @Override
    public void setRecipeUsed(@Nullable final Recipe<?> bmu) {
        if (bmu != null) {
            final ResourceLocation uh3 = bmu.getId();
            this.recipesUsed.addTo(uh3, 1);
        }
    }
    
    @Nullable
    @Override
    public Recipe<?> getRecipeUsed() {
        return null;
    }
    
    @Override
    public void awardUsedRecipes(final Player bec) {
    }
    
    public void awardUsedRecipesAndPopExperience(final Player bec) {
        final List<Recipe<?>> list3 = this.getRecipesToAwardAndPopExperience(bec.level, bec.position());
        bec.awardRecipes(list3);
        this.recipesUsed.clear();
    }
    
    public List<Recipe<?>> getRecipesToAwardAndPopExperience(final Level bqb, final Vec3 dem) {
        final List<Recipe<?>> list4 = Lists.newArrayList();
        for (final Object2IntMap.Entry<ResourceLocation> entry6 : this.recipesUsed.object2IntEntrySet()) {
            final List<AbstractCookingRecipe> list5;
            final Object2IntMap.Entry entry7;
            bqb.getRecipeManager().byKey((ResourceLocation)entry6.getKey()).ifPresent(bmu -> {
                list5.add(bmu);
                createExperience(bqb, dem, entry7.getIntValue(), bmu.getExperience());
                return;
            });
        }
        return list4;
    }
    
    private static void createExperience(final Level bqb, final Vec3 dem, final int integer, final float float4) {
        int integer2 = Mth.floor(integer * float4);
        final float float5 = Mth.frac(integer * float4);
        if (float5 != 0.0f && Math.random() < float5) {
            ++integer2;
        }
        while (integer2 > 0) {
            final int integer3 = ExperienceOrb.getExperienceValue(integer2);
            integer2 -= integer3;
            bqb.addFreshEntity(new ExperienceOrb(bqb, dem.x, dem.y, dem.z, integer3));
        }
    }
    
    @Override
    public void fillStackedContents(final StackedContents bee) {
        for (final ItemStack bki4 : this.items) {
            bee.accountStack(bki4);
        }
    }
    
    static {
        SLOTS_FOR_UP = new int[] { 0 };
        SLOTS_FOR_DOWN = new int[] { 2, 1 };
        SLOTS_FOR_SIDES = new int[] { 1 };
    }
}
