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

import net.minecraft.world.level.block.state.StateHolder;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.Arrays;
import java.util.Map;
import com.mojang.datafixers.kinds.Applicative;
import com.mojang.datafixers.kinds.App;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mojang.serialization.Codec;
import org.apache.logging.log4j.LogManager;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.RuleTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.AlwaysTrueTest;
import net.minecraft.world.level.levelgen.structure.templatesystem.RandomBlockMatchTest;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.VineBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.ChunkPos;
import java.util.Random;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.StructureFeatureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlackstoneReplaceProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.LavaSubmergedBlockProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockAgeProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.ProcessorRule;
import java.util.List;
import net.minecraft.world.level.levelgen.structure.templatesystem.RuleProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.block.Blocks;
import com.google.common.collect.Lists;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor;
import java.util.function.Consumer;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.Dynamic;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.level.levelgen.feature.StructurePieceType;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.resources.ResourceLocation;
import org.apache.logging.log4j.Logger;

public class RuinedPortalPiece extends TemplateStructurePiece {
    private static final Logger LOGGER;
    private final ResourceLocation templateLocation;
    private final Rotation rotation;
    private final Mirror mirror;
    private final VerticalPlacement verticalPlacement;
    private final Properties properties;
    
    public RuinedPortalPiece(final BlockPos fu1, final VerticalPlacement b, final Properties a, final ResourceLocation uh, final StructureTemplate cve, final Rotation cap, final Mirror bzj, final BlockPos fu8) {
        super(StructurePieceType.RUINED_PORTAL, 0);
        this.templatePosition = fu1;
        this.templateLocation = uh;
        this.rotation = cap;
        this.mirror = bzj;
        this.verticalPlacement = b;
        this.properties = a;
        this.loadTemplate(cve, fu8);
    }
    
    public RuinedPortalPiece(final StructureManager cva, final CompoundTag le) {
        super(StructurePieceType.RUINED_PORTAL, le);
        this.templateLocation = new ResourceLocation(le.getString("Template"));
        this.rotation = Rotation.valueOf(le.getString("Rotation"));
        this.mirror = Mirror.valueOf(le.getString("Mirror"));
        this.verticalPlacement = VerticalPlacement.byName(le.getString("VerticalPlacement"));
        this.properties = (Properties)Properties.CODEC.parse(new Dynamic((DynamicOps)NbtOps.INSTANCE, le.get("Properties"))).getOrThrow(true, (Consumer)RuinedPortalPiece.LOGGER::error);
        final StructureTemplate cve4 = cva.getOrCreate(this.templateLocation);
        this.loadTemplate(cve4, new BlockPos(cve4.getSize().getX() / 2, 0, cve4.getSize().getZ() / 2));
    }
    
    @Override
    protected void addAdditionalSaveData(final CompoundTag le) {
        super.addAdditionalSaveData(le);
        le.putString("Template", this.templateLocation.toString());
        le.putString("Rotation", this.rotation.name());
        le.putString("Mirror", this.mirror.name());
        le.putString("VerticalPlacement", this.verticalPlacement.getName());
        Properties.CODEC.encodeStart((DynamicOps)NbtOps.INSTANCE, this.properties).resultOrPartial((Consumer)RuinedPortalPiece.LOGGER::error).ifPresent(lu -> le.put("Properties", lu));
    }
    
    private void loadTemplate(final StructureTemplate cve, final BlockPos fu) {
        final BlockIgnoreProcessor cui4 = this.properties.airPocket ? BlockIgnoreProcessor.STRUCTURE_BLOCK : BlockIgnoreProcessor.STRUCTURE_AND_AIR;
        final List<ProcessorRule> list5 = Lists.newArrayList();
        list5.add(getBlockReplaceRule(Blocks.GOLD_BLOCK, 0.3f, Blocks.AIR));
        list5.add(this.getLavaProcessorRule());
        if (!this.properties.cold) {
            list5.add(getBlockReplaceRule(Blocks.NETHERRACK, 0.07f, Blocks.MAGMA_BLOCK));
        }
        final StructurePlaceSettings cvb6 = new StructurePlaceSettings().setRotation(this.rotation).setMirror(this.mirror).setRotationPivot(fu).addProcessor(cui4).addProcessor(new RuleProcessor(list5)).addProcessor(new BlockAgeProcessor(this.properties.mossiness)).addProcessor(new LavaSubmergedBlockProcessor());
        if (this.properties.replaceWithBlackstone) {
            cvb6.addProcessor(BlackstoneReplaceProcessor.INSTANCE);
        }
        this.setup(cve, this.templatePosition, cvb6);
    }
    
    private ProcessorRule getLavaProcessorRule() {
        if (this.verticalPlacement == VerticalPlacement.ON_OCEAN_FLOOR) {
            return getBlockReplaceRule(Blocks.LAVA, Blocks.MAGMA_BLOCK);
        }
        if (this.properties.cold) {
            return getBlockReplaceRule(Blocks.LAVA, Blocks.NETHERRACK);
        }
        return getBlockReplaceRule(Blocks.LAVA, 0.2f, Blocks.MAGMA_BLOCK);
    }
    
    @Override
    public boolean postProcess(final WorldGenLevel bqu, final StructureFeatureManager bqq, final ChunkGenerator cha, final Random random, final BoundingBox ctd, final ChunkPos bph, final BlockPos fu) {
        if (!ctd.isInside(this.templatePosition)) {
            return true;
        }
        ctd.expand(this.template.getBoundingBox(this.placeSettings, this.templatePosition));
        final boolean boolean9 = super.postProcess(bqu, bqq, cha, random, ctd, bph, fu);
        this.spreadNetherrack(random, bqu);
        this.addNetherrackDripColumnsBelowPortal(random, bqu);
        if (this.properties.vines || this.properties.overgrown) {
            BlockPos.betweenClosedStream(this.getBoundingBox()).forEach(fu -> {
                if (this.properties.vines) {
                    this.maybeAddVines(random, bqu, fu);
                }
                if (this.properties.overgrown) {
                    this.maybeAddLeavesAbove(random, bqu, fu);
                }
                return;
            });
        }
        return boolean9;
    }
    
    @Override
    protected void handleDataMarker(final String string, final BlockPos fu, final LevelAccessor bqc, final Random random, final BoundingBox ctd) {
    }
    
    private void maybeAddVines(final Random random, final LevelAccessor bqc, final BlockPos fu) {
        final BlockState cfj5 = bqc.getBlockState(fu);
        if (cfj5.isAir() || cfj5.is(Blocks.VINE)) {
            return;
        }
        final Direction fz6 = Direction.Plane.HORIZONTAL.getRandomDirection(random);
        final BlockPos fu2 = fu.relative(fz6);
        final BlockState cfj6 = bqc.getBlockState(fu2);
        if (!cfj6.isAir()) {
            return;
        }
        if (!Block.isFaceFull(cfj5.getCollisionShape(bqc, fu), fz6)) {
            return;
        }
        final BooleanProperty cga9 = VineBlock.getPropertyForFace(fz6.getOpposite());
        bqc.setBlock(fu2, ((StateHolder<O, BlockState>)Blocks.VINE.defaultBlockState()).<Comparable, Boolean>setValue((Property<Comparable>)cga9, true), 3);
    }
    
    private void maybeAddLeavesAbove(final Random random, final LevelAccessor bqc, final BlockPos fu) {
        if (random.nextFloat() < 0.5f && bqc.getBlockState(fu).is(Blocks.NETHERRACK) && bqc.getBlockState(fu.above()).isAir()) {
            bqc.setBlock(fu.above(), ((StateHolder<O, BlockState>)Blocks.JUNGLE_LEAVES.defaultBlockState()).<Comparable, Boolean>setValue((Property<Comparable>)LeavesBlock.PERSISTENT, true), 3);
        }
    }
    
    private void addNetherrackDripColumnsBelowPortal(final Random random, final LevelAccessor bqc) {
        for (int integer4 = this.boundingBox.x0 + 1; integer4 < this.boundingBox.x1; ++integer4) {
            for (int integer5 = this.boundingBox.z0 + 1; integer5 < this.boundingBox.z1; ++integer5) {
                final BlockPos fu6 = new BlockPos(integer4, this.boundingBox.y0, integer5);
                if (bqc.getBlockState(fu6).is(Blocks.NETHERRACK)) {
                    this.addNetherrackDripColumn(random, bqc, fu6.below());
                }
            }
        }
    }
    
    private void addNetherrackDripColumn(final Random random, final LevelAccessor bqc, final BlockPos fu) {
        final BlockPos.MutableBlockPos a5 = fu.mutable();
        this.placeNetherrackOrMagma(random, bqc, a5);
        int integer6 = 8;
        while (integer6 > 0 && random.nextFloat() < 0.5f) {
            a5.move(Direction.DOWN);
            --integer6;
            this.placeNetherrackOrMagma(random, bqc, a5);
        }
    }
    
    private void spreadNetherrack(final Random random, final LevelAccessor bqc) {
        final boolean boolean4 = this.verticalPlacement == VerticalPlacement.ON_LAND_SURFACE || this.verticalPlacement == VerticalPlacement.ON_OCEAN_FLOOR;
        final Vec3i gr5 = this.boundingBox.getCenter();
        final int integer6 = gr5.getX();
        final int integer7 = gr5.getZ();
        final float[] arr8 = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.9f, 0.9f, 0.8f, 0.7f, 0.6f, 0.4f, 0.2f };
        final int integer8 = arr8.length;
        final int integer9 = (this.boundingBox.getXSpan() + this.boundingBox.getZSpan()) / 2;
        final int integer10 = random.nextInt(Math.max(1, 8 - integer9 / 2));
        final int integer11 = 3;
        final BlockPos.MutableBlockPos a13 = BlockPos.ZERO.mutable();
        for (int integer12 = integer6 - integer8; integer12 <= integer6 + integer8; ++integer12) {
            for (int integer13 = integer7 - integer8; integer13 <= integer7 + integer8; ++integer13) {
                final int integer14 = Math.abs(integer12 - integer6) + Math.abs(integer13 - integer7);
                final int integer15 = Math.max(0, integer14 + integer10);
                if (integer15 < integer8) {
                    final float float18 = arr8[integer15];
                    if (random.nextDouble() < float18) {
                        final int integer16 = getSurfaceY(bqc, integer12, integer13, this.verticalPlacement);
                        final int integer17 = boolean4 ? integer16 : Math.min(this.boundingBox.y0, integer16);
                        a13.set(integer12, integer17, integer13);
                        if (Math.abs(integer17 - this.boundingBox.y0) <= 3 && this.canBlockBeReplacedByNetherrackOrMagma(bqc, a13)) {
                            this.placeNetherrackOrMagma(random, bqc, a13);
                            if (this.properties.overgrown) {
                                this.maybeAddLeavesAbove(random, bqc, a13);
                            }
                            this.addNetherrackDripColumn(random, bqc, a13.below());
                        }
                    }
                }
            }
        }
    }
    
    private boolean canBlockBeReplacedByNetherrackOrMagma(final LevelAccessor bqc, final BlockPos fu) {
        final BlockState cfj4 = bqc.getBlockState(fu);
        return !cfj4.is(Blocks.AIR) && !cfj4.is(Blocks.OBSIDIAN) && (this.verticalPlacement == VerticalPlacement.IN_NETHER || !cfj4.is(Blocks.LAVA));
    }
    
    private void placeNetherrackOrMagma(final Random random, final LevelAccessor bqc, final BlockPos fu) {
        if (!this.properties.cold && random.nextFloat() < 0.07f) {
            bqc.setBlock(fu, Blocks.MAGMA_BLOCK.defaultBlockState(), 3);
        }
        else {
            bqc.setBlock(fu, Blocks.NETHERRACK.defaultBlockState(), 3);
        }
    }
    
    private static int getSurfaceY(final LevelAccessor bqc, final int integer2, final int integer3, final VerticalPlacement b) {
        return bqc.getHeight(getHeightMapType(b), integer2, integer3) - 1;
    }
    
    public static Heightmap.Types getHeightMapType(final VerticalPlacement b) {
        return (b == VerticalPlacement.ON_OCEAN_FLOOR) ? Heightmap.Types.OCEAN_FLOOR_WG : Heightmap.Types.WORLD_SURFACE_WG;
    }
    
    private static ProcessorRule getBlockReplaceRule(final Block bvr1, final float float2, final Block bvr3) {
        return new ProcessorRule(new RandomBlockMatchTest(bvr1, float2), AlwaysTrueTest.INSTANCE, bvr3.defaultBlockState());
    }
    
    private static ProcessorRule getBlockReplaceRule(final Block bvr1, final Block bvr2) {
        return new ProcessorRule(new BlockMatchTest(bvr1), AlwaysTrueTest.INSTANCE, bvr2.defaultBlockState());
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    public static class Properties {
        public static final Codec<Properties> CODEC;
        public boolean cold;
        public float mossiness;
        public boolean airPocket;
        public boolean overgrown;
        public boolean vines;
        public boolean replaceWithBlackstone;
        
        public Properties() {
            this.mossiness = 0.2f;
        }
        
        public <T> Properties(final boolean boolean1, final float float2, final boolean boolean3, final boolean boolean4, final boolean boolean5, final boolean boolean6) {
            this.mossiness = 0.2f;
            this.cold = boolean1;
            this.mossiness = float2;
            this.airPocket = boolean3;
            this.overgrown = boolean4;
            this.vines = boolean5;
            this.replaceWithBlackstone = boolean6;
        }
        
        static {
            CODEC = RecordCodecBuilder.create(instance -> ((RecordCodecBuilder.Instance)instance).group((App)Codec.BOOL.fieldOf("cold").forGetter(a -> a.cold), (App)Codec.FLOAT.fieldOf("mossiness").forGetter(a -> a.mossiness), (App)Codec.BOOL.fieldOf("air_pocket").forGetter(a -> a.airPocket), (App)Codec.BOOL.fieldOf("overgrown").forGetter(a -> a.overgrown), (App)Codec.BOOL.fieldOf("vines").forGetter(a -> a.vines), (App)Codec.BOOL.fieldOf("replace_with_blackstone").forGetter(a -> a.replaceWithBlackstone)).apply(instance, Properties::new));
        }
    }
    
    public enum VerticalPlacement {
        ON_LAND_SURFACE("on_land_surface"), 
        PARTLY_BURIED("partly_buried"), 
        ON_OCEAN_FLOOR("on_ocean_floor"), 
        IN_MOUNTAIN("in_mountain"), 
        UNDERGROUND("underground"), 
        IN_NETHER("in_nether");
        
        private static final Map<String, VerticalPlacement> BY_NAME;
        private final String name;
        
        private VerticalPlacement(final String string3) {
            this.name = string3;
        }
        
        public String getName() {
            return this.name;
        }
        
        public static VerticalPlacement byName(final String string) {
            return VerticalPlacement.BY_NAME.get(string);
        }
        
        static {
            BY_NAME = Arrays.<VerticalPlacement>stream(values()).collect(Collectors.toMap(VerticalPlacement::getName, b -> b));
        }
    }
}
