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

import net.minecraft.world.level.block.state.StateHolder;
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 net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.LevelAccessor;
import java.util.Comparator;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.StructureFeatureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.levelgen.feature.foliageplacers.FoliagePlacer;
import java.util.List;
import java.util.OptionalInt;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import java.util.Set;
import java.util.Random;
import net.minecraft.world.level.LevelSimulatedRW;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.LevelWriter;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.Block;
import net.minecraft.tags.Tag;
import net.minecraft.tags.BlockTags;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelSimulatedReader;
import com.mojang.serialization.Codec;
import net.minecraft.world.level.levelgen.feature.configurations.TreeConfiguration;

public class TreeFeature extends Feature<TreeConfiguration> {
    public TreeFeature(final Codec<TreeConfiguration> codec) {
        super(codec);
    }
    
    public static boolean isFree(final LevelSimulatedReader bqg, final BlockPos fu) {
        return validTreePos(bqg, fu) || bqg.isStateAtPosition(fu, cfj -> cfj.is(BlockTags.LOGS));
    }
    
    private static boolean isVine(final LevelSimulatedReader bqg, final BlockPos fu) {
        return bqg.isStateAtPosition(fu, cfj -> cfj.is(Blocks.VINE));
    }
    
    private static boolean isBlockWater(final LevelSimulatedReader bqg, final BlockPos fu) {
        return bqg.isStateAtPosition(fu, cfj -> cfj.is(Blocks.WATER));
    }
    
    public static boolean isAirOrLeaves(final LevelSimulatedReader bqg, final BlockPos fu) {
        return bqg.isStateAtPosition(fu, cfj -> cfj.isAir() || cfj.is(BlockTags.LEAVES));
    }
    
    private static boolean isGrassOrDirtOrFarmland(final LevelSimulatedReader bqg, final BlockPos fu) {
        final Block bvr2;
        return bqg.isStateAtPosition(fu, cfj -> {
            bvr2 = cfj.getBlock();
            return Feature.isDirt(bvr2) || bvr2 == Blocks.FARMLAND;
        });
    }
    
    private static boolean isReplaceablePlant(final LevelSimulatedReader bqg, final BlockPos fu) {
        final Material cxd2;
        return bqg.isStateAtPosition(fu, cfj -> {
            cxd2 = cfj.getMaterial();
            return cxd2 == Material.REPLACEABLE_PLANT;
        });
    }
    
    public static void setBlockKnownShape(final LevelWriter bqh, final BlockPos fu, final BlockState cfj) {
        bqh.setBlock(fu, cfj, 19);
    }
    
    public static boolean validTreePos(final LevelSimulatedReader bqg, final BlockPos fu) {
        return isAirOrLeaves(bqg, fu) || isReplaceablePlant(bqg, fu) || isBlockWater(bqg, fu);
    }
    
    private boolean doPlace(final LevelSimulatedRW bqf, final Random random, final BlockPos fu, final Set<BlockPos> set4, final Set<BlockPos> set5, final BoundingBox ctd, final TreeConfiguration cou) {
        final int integer9 = cou.trunkPlacer.getTreeHeight(random);
        final int integer10 = cou.foliagePlacer.foliageHeight(random, integer9, cou);
        final int integer11 = integer9 - integer10;
        final int integer12 = cou.foliagePlacer.foliageRadius(random, integer11);
        BlockPos fu2;
        if (!cou.fromSapling) {
            final int integer13 = bqf.getHeightmapPos(Heightmap.Types.OCEAN_FLOOR, fu).getY();
            final int integer14 = bqf.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, fu).getY();
            if (integer14 - integer13 > cou.maxWaterDepth) {
                return false;
            }
            int integer15;
            if (cou.heightmap == Heightmap.Types.OCEAN_FLOOR) {
                integer15 = integer13;
            }
            else if (cou.heightmap == Heightmap.Types.WORLD_SURFACE) {
                integer15 = integer14;
            }
            else {
                integer15 = bqf.getHeightmapPos(cou.heightmap, fu).getY();
            }
            fu2 = new BlockPos(fu.getX(), integer15, fu.getZ());
        }
        else {
            fu2 = fu;
        }
        if (fu2.getY() < 1 || fu2.getY() + integer9 + 1 > 256) {
            return false;
        }
        if (!isGrassOrDirtOrFarmland(bqf, fu2.below())) {
            return false;
        }
        final OptionalInt optionalInt14 = cou.minimumSize.minClippedHeight();
        final int integer14 = this.getMaxFreeTreeHeight(bqf, integer9, fu2, cou);
        if (integer14 < integer9 && (!optionalInt14.isPresent() || integer14 < optionalInt14.getAsInt())) {
            return false;
        }
        final List<FoliagePlacer.FoliageAttachment> list16 = cou.trunkPlacer.placeTrunk(bqf, random, integer14, fu2, set4, ctd, cou);
        final int integer16;
        list16.forEach(b -> cou.foliagePlacer.createFoliage(bqf, random, cou, integer16, b, integer10, integer12, set5, ctd));
        return true;
    }
    
    private int getMaxFreeTreeHeight(final LevelSimulatedReader bqg, final int integer, final BlockPos fu, final TreeConfiguration cou) {
        final BlockPos.MutableBlockPos a6 = new BlockPos.MutableBlockPos();
        for (int integer2 = 0; integer2 <= integer + 1; ++integer2) {
            for (int integer3 = cou.minimumSize.getSizeAtHeight(integer, integer2), integer4 = -integer3; integer4 <= integer3; ++integer4) {
                for (int integer5 = -integer3; integer5 <= integer3; ++integer5) {
                    a6.setWithOffset(fu, integer4, integer2, integer5);
                    if (!isFree(bqg, a6) || (!cou.ignoreVines && isVine(bqg, a6))) {
                        return integer2 - 2;
                    }
                }
            }
        }
        return integer;
    }
    
    @Override
    protected void setBlock(final LevelWriter bqh, final BlockPos fu, final BlockState cfj) {
        setBlockKnownShape(bqh, fu, cfj);
    }
    
    @Override
    public final boolean place(final WorldGenLevel bqu, final StructureFeatureManager bqq, final ChunkGenerator cha, final Random random, final BlockPos fu, final TreeConfiguration cou) {
        final Set<BlockPos> set8 = Sets.newHashSet();
        final Set<BlockPos> set9 = Sets.newHashSet();
        final Set<BlockPos> set10 = Sets.newHashSet();
        final BoundingBox ctd11 = BoundingBox.getUnknownBox();
        final boolean boolean12 = this.doPlace(bqu, random, fu, set8, set9, ctd11, cou);
        if (ctd11.x0 > ctd11.x1 || !boolean12 || set8.isEmpty()) {
            return false;
        }
        if (!cou.decorators.isEmpty()) {
            final List<BlockPos> list13 = Lists.newArrayList(set8);
            final List<BlockPos> list14 = Lists.newArrayList(set9);
            list13.sort(Comparator.comparingInt(Vec3i::getY));
            list14.sort(Comparator.comparingInt(Vec3i::getY));
            cou.decorators.forEach(cqm -> cqm.place(bqu, random, list13, list14, set10, ctd11));
        }
        final DiscreteVoxelShape dev13 = this.updateLeaves(bqu, ctd11, set8, set10);
        StructureTemplate.updateShapeAtEdge(bqu, 3, dev13, ctd11.x0, ctd11.y0, ctd11.z0);
        return true;
    }
    
    private DiscreteVoxelShape updateLeaves(final LevelAccessor bqc, final BoundingBox ctd, final Set<BlockPos> set3, final Set<BlockPos> set4) {
        final List<Set<BlockPos>> list6 = Lists.newArrayList();
        final DiscreteVoxelShape dev7 = new BitSetDiscreteVoxelShape(ctd.getXSpan(), ctd.getYSpan(), ctd.getZSpan());
        final int integer8 = 6;
        for (int integer9 = 0; integer9 < 6; ++integer9) {
            list6.add(Sets.newHashSet());
        }
        final BlockPos.MutableBlockPos a9 = new BlockPos.MutableBlockPos();
        for (final BlockPos fu11 : Lists.<BlockPos>newArrayList(set4)) {
            if (ctd.isInside(fu11)) {
                dev7.setFull(fu11.getX() - ctd.x0, fu11.getY() - ctd.y0, fu11.getZ() - ctd.z0, true, true);
            }
        }
        for (final BlockPos fu11 : Lists.<BlockPos>newArrayList(set3)) {
            if (ctd.isInside(fu11)) {
                dev7.setFull(fu11.getX() - ctd.x0, fu11.getY() - ctd.y0, fu11.getZ() - ctd.z0, true, true);
            }
            for (final Direction fz15 : Direction.values()) {
                a9.setWithOffset(fu11, fz15);
                if (!set3.contains(a9)) {
                    final BlockState cfj16 = bqc.getBlockState(a9);
                    if (cfj16.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE)) {
                        list6.get(0).add(a9.immutable());
                        setBlockKnownShape(bqc, a9, ((StateHolder<O, BlockState>)cfj16).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, 1));
                        if (ctd.isInside(a9)) {
                            dev7.setFull(a9.getX() - ctd.x0, a9.getY() - ctd.y0, a9.getZ() - ctd.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 fu12 : set5) {
                if (ctd.isInside(fu12)) {
                    dev7.setFull(fu12.getX() - ctd.x0, fu12.getY() - ctd.y0, fu12.getZ() - ctd.z0, true, true);
                }
                for (final Direction fz16 : Direction.values()) {
                    a9.setWithOffset(fu12, fz16);
                    if (!set5.contains(a9)) {
                        if (!set6.contains(a9)) {
                            final BlockState cfj17 = bqc.getBlockState(a9);
                            if (cfj17.<Comparable>hasProperty((Property<Comparable>)BlockStateProperties.DISTANCE)) {
                                final int integer11 = cfj17.<Integer>getValue((Property<Integer>)BlockStateProperties.DISTANCE);
                                if (integer11 > integer10 + 1) {
                                    final BlockState cfj18 = ((StateHolder<O, BlockState>)cfj17).<Comparable, Integer>setValue((Property<Comparable>)BlockStateProperties.DISTANCE, integer10 + 1);
                                    setBlockKnownShape(bqc, a9, cfj18);
                                    if (ctd.isInside(a9)) {
                                        dev7.setFull(a9.getX() - ctd.x0, a9.getY() - ctd.y0, a9.getZ() - ctd.z0, true, true);
                                    }
                                    set6.add(a9.immutable());
                                }
                            }
                        }
                    }
                }
            }
        }
        return dev7;
    }
}
