package net.minecraft.world.level.levelgen.feature;

import net.minecraft.world.level.block.state.AbstractStateHolder;
import net.minecraft.world.level.levelgen.feature.treedecorators.TreeDecorator;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import java.util.Iterator;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import java.util.List;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import java.util.Comparator;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.minecraft.world.level.levelgen.ChunkGeneratorSettings;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import java.util.Set;
import java.util.Random;
import net.minecraft.world.level.LevelWriter;
import net.minecraft.world.level.LevelSimulatedRW;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.tags.BlockTags;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelSimulatedReader;
import com.mojang.datafixers.Dynamic;
import java.util.function.Function;
import net.minecraft.world.level.levelgen.feature.configurations.TreeConfiguration;

public abstract class AbstractTreeFeature<T extends TreeConfiguration> extends Feature<T> {
    public AbstractTreeFeature(final Function<Dynamic<?>, ? extends T> function) {
        super(function);
    }
    
    protected static boolean isFree(final LevelSimulatedReader bkc, final BlockPos fk) {
        final Block bph2;
        return bkc.isStateAtPosition(fk, byj -> {
            bph2 = byj.getBlock();
            return byj.isAir() || byj.is(BlockTags.LEAVES) || Feature.isDirt(bph2) || bph2.is(BlockTags.LOGS) || bph2.is(BlockTags.SAPLINGS) || bph2 == Blocks.VINE;
        });
    }
    
    public static boolean isAir(final LevelSimulatedReader bkc, final BlockPos fk) {
        return bkc.isStateAtPosition(fk, BlockState::isAir);
    }
    
    protected static boolean isDirt(final LevelSimulatedReader bkc, final BlockPos fk) {
        final Block bph2;
        return bkc.isStateAtPosition(fk, byj -> {
            bph2 = byj.getBlock();
            return Feature.isDirt(bph2) && bph2 != Blocks.GRASS_BLOCK && bph2 != Blocks.MYCELIUM;
        });
    }
    
    protected static boolean isVine(final LevelSimulatedReader bkc, final BlockPos fk) {
        return bkc.isStateAtPosition(fk, byj -> byj.getBlock() == Blocks.VINE);
    }
    
    public static boolean isBlockWater(final LevelSimulatedReader bkc, final BlockPos fk) {
        return bkc.isStateAtPosition(fk, byj -> byj.getBlock() == Blocks.WATER);
    }
    
    public static boolean isAirOrLeaves(final LevelSimulatedReader bkc, final BlockPos fk) {
        return bkc.isStateAtPosition(fk, byj -> byj.isAir() || byj.is(BlockTags.LEAVES));
    }
    
    public static boolean isGrassOrDirt(final LevelSimulatedReader bkc, final BlockPos fk) {
        return bkc.isStateAtPosition(fk, byj -> Feature.isDirt(byj.getBlock()));
    }
    
    protected static boolean isGrassOrDirtOrFarmland(final LevelSimulatedReader bkc, final BlockPos fk) {
        final Block bph2;
        return bkc.isStateAtPosition(fk, byj -> {
            bph2 = byj.getBlock();
            return Feature.isDirt(bph2) || bph2 == Blocks.FARMLAND;
        });
    }
    
    public static boolean isReplaceablePlant(final LevelSimulatedReader bkc, final BlockPos fk) {
        final Material coo2;
        return bkc.isStateAtPosition(fk, byj -> {
            coo2 = byj.getMaterial();
            return coo2 == Material.REPLACEABLE_PLANT;
        });
    }
    
    protected void setDirtAt(final LevelSimulatedRW bkb, final BlockPos fk) {
        if (!isDirt(bkb, fk)) {
            this.setBlock(bkb, fk, Blocks.DIRT.defaultBlockState());
        }
    }
    
    protected boolean placeLog(final LevelSimulatedRW bkb, final Random random, final BlockPos fk, final Set<BlockPos> set, final BoundingBox clb, final TreeConfiguration chl) {
        if (isAirOrLeaves(bkb, fk) || isReplaceablePlant(bkb, fk) || isBlockWater(bkb, fk)) {
            this.setBlock(bkb, fk, chl.trunkProvider.getState(random, fk), clb);
            set.add(fk.immutable());
            return true;
        }
        return false;
    }
    
    protected boolean placeLeaf(final LevelSimulatedRW bkb, final Random random, final BlockPos fk, final Set<BlockPos> set, final BoundingBox clb, final TreeConfiguration chl) {
        if (isAirOrLeaves(bkb, fk) || isReplaceablePlant(bkb, fk) || isBlockWater(bkb, fk)) {
            this.setBlock(bkb, fk, chl.leavesProvider.getState(random, fk), clb);
            set.add(fk.immutable());
            return true;
        }
        return false;
    }
    
    @Override
    protected void setBlock(final LevelWriter bke, final BlockPos fk, final BlockState byj) {
        this.setBlockKnownShape(bke, fk, byj);
    }
    
    protected final void setBlock(final LevelWriter bke, final BlockPos fk, final BlockState byj, final BoundingBox clb) {
        this.setBlockKnownShape(bke, fk, byj);
        clb.expand(new BoundingBox(fk, fk));
    }
    
    private void setBlockKnownShape(final LevelWriter bke, final BlockPos fk, final BlockState byj) {
        bke.setBlock(fk, byj, 19);
    }
    
    @Override
    public final boolean place(final LevelAccessor bjx, final ChunkGenerator<? extends ChunkGeneratorSettings> caa, final Random random, final BlockPos fk, final T chl) {
        final Set<BlockPos> set7 = Sets.newHashSet();
        final Set<BlockPos> set8 = Sets.newHashSet();
        final Set<BlockPos> set9 = Sets.newHashSet();
        final BoundingBox clb10 = BoundingBox.getUnknownBox();
        final boolean boolean11 = this.doPlace(bjx, random, fk, set7, set8, clb10, chl);
        if (clb10.x0 > clb10.x1 || !boolean11 || set7.isEmpty()) {
            return false;
        }
        if (!chl.decorators.isEmpty()) {
            final List<BlockPos> list12 = Lists.newArrayList(set7);
            final List<BlockPos> list13 = Lists.newArrayList(set8);
            list12.sort(Comparator.comparingInt(Vec3i::getY));
            list13.sort(Comparator.comparingInt(Vec3i::getY));
            chl.decorators.forEach(cit -> cit.place(bjx, random, list12, list13, set9, clb10));
        }
        final DiscreteVoxelShape cvu12 = this.updateLeaves(bjx, clb10, set7, set9);
        StructureTemplate.updateShapeAtEdge(bjx, 3, cvu12, clb10.x0, clb10.y0, clb10.z0);
        return true;
    }
    
    private DiscreteVoxelShape updateLeaves(final LevelAccessor bjx, final BoundingBox clb, final Set<BlockPos> set3, final Set<BlockPos> set4) {
        final List<Set<BlockPos>> list6 = Lists.newArrayList();
        final DiscreteVoxelShape cvu7 = new BitSetDiscreteVoxelShape(clb.getXSpan(), clb.getYSpan(), clb.getZSpan());
        final int integer8 = 6;
        for (int integer9 = 0; integer9 < 6; ++integer9) {
            list6.add(Sets.newHashSet());
        }
        try (final BlockPos.PooledMutableBlockPos b9 = BlockPos.PooledMutableBlockPos.acquire()) {
            for (final BlockPos fk12 : Lists.<BlockPos>newArrayList(set4)) {
                if (clb.isInside(fk12)) {
                    cvu7.setFull(fk12.getX() - clb.x0, fk12.getY() - clb.y0, fk12.getZ() - clb.z0, true, true);
                }
            }
            for (final BlockPos fk12 : Lists.<BlockPos>newArrayList(set3)) {
                if (clb.isInside(fk12)) {
                    cvu7.setFull(fk12.getX() - clb.x0, fk12.getY() - clb.y0, fk12.getZ() - clb.z0, true, true);
                }
                for (final Direction fp16 : Direction.values()) {
                    b9.set((Vec3i)fk12).move(fp16);
                    if (!set3.contains(b9)) {
                        final BlockState byj17 = bjx.getBlockState(b9);
                        if (byj17.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE)) {
                            list6.get(0).add(b9.immutable());
                            this.setBlockKnownShape(bjx, b9, ((AbstractStateHolder<O, BlockState>)byj17).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, 1));
                            if (clb.isInside(b9)) {
                                cvu7.setFull(b9.getX() - clb.x0, b9.getY() - clb.y0, b9.getZ() - clb.z0, true, true);
                            }
                        }
                    }
                }
            }
            for (int integer10 = 1; integer10 < 6; ++integer10) {
                final Set<BlockPos> set5 = list6.get(integer10 - 1);
                final Set<BlockPos> set6 = list6.get(integer10);
                for (final BlockPos fk13 : set5) {
                    if (clb.isInside(fk13)) {
                        cvu7.setFull(fk13.getX() - clb.x0, fk13.getY() - clb.y0, fk13.getZ() - clb.z0, true, true);
                    }
                    for (final Direction fp17 : Direction.values()) {
                        b9.set((Vec3i)fk13).move(fp17);
                        if (!set5.contains(b9)) {
                            if (!set6.contains(b9)) {
                                final BlockState byj18 = bjx.getBlockState(b9);
                                if (byj18.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE)) {
                                    final int integer11 = byj18.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE);
                                    if (integer11 > integer10 + 1) {
                                        final BlockState byj19 = ((AbstractStateHolder<O, BlockState>)byj18).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, integer10 + 1);
                                        this.setBlockKnownShape(bjx, b9, byj19);
                                        if (clb.isInside(b9)) {
                                            cvu7.setFull(b9.getX() - clb.x0, b9.getY() - clb.y0, b9.getZ() - clb.z0, true, true);
                                        }
                                        set6.add(b9.immutable());
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return cvu7;
    }
    
    protected abstract boolean doPlace(final LevelSimulatedRW bkb, final Random random, final BlockPos fk, final Set<BlockPos> set4, final Set<BlockPos> set5, final BoundingBox clb, final T chl);
}
