package net.minecraft.server.commands;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.Collections;
import net.minecraft.world.level.block.Blocks;
import com.mojang.brigadier.Message;
import net.minecraft.core.Vec3i;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import java.util.Iterator;
import net.minecraft.server.level.ServerLevel;
import java.util.List;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.Clearable;
import net.minecraft.world.level.LevelReader;
import net.minecraft.core.BlockPos;
import com.google.common.collect.Lists;
import javax.annotation.Nullable;
import net.minecraft.world.level.block.state.pattern.BlockInWorld;
import java.util.function.Predicate;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.arguments.ArgumentType;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.commands.Commands;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.commands.arguments.blocks.BlockInput;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;

public class FillCommand {
    private static final Dynamic2CommandExceptionType ERROR_AREA_TOO_LARGE;
    private static final BlockInput HOLLOW_CORE;
    private static final SimpleCommandExceptionType ERROR_FAILED;
    
    public static void register(final CommandDispatcher<CommandSourceStack> commandDispatcher) {
        commandDispatcher.register((LiteralArgumentBuilder)((LiteralArgumentBuilder)Commands.literal("fill").requires(cz -> cz.hasPermission(2))).then(Commands.argument("from", (com.mojang.brigadier.arguments.ArgumentType<Object>)BlockPosArgument.blockPos()).then(Commands.argument("to", (com.mojang.brigadier.arguments.ArgumentType<Object>)BlockPosArgument.blockPos()).then(((RequiredArgumentBuilder)((RequiredArgumentBuilder)((RequiredArgumentBuilder)((RequiredArgumentBuilder)((RequiredArgumentBuilder)Commands.argument("block", (com.mojang.brigadier.arguments.ArgumentType<Object>)BlockStateArgument.block()).executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.REPLACE, null))).then(((LiteralArgumentBuilder)Commands.literal("replace").executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.REPLACE, null))).then(Commands.argument("filter", (com.mojang.brigadier.arguments.ArgumentType<Object>)BlockPredicateArgument.blockPredicate()).executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.REPLACE, BlockPredicateArgument.getBlockPredicate((CommandContext<CommandSourceStack>)commandContext, "filter")))))).then(Commands.literal("keep").executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.REPLACE, cfn -> cfn.getLevel().isEmptyBlock(cfn.getPos()))))).then(Commands.literal("outline").executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.OUTLINE, null)))).then(Commands.literal("hollow").executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.HOLLOW, null)))).then(Commands.literal("destroy").executes(commandContext -> fillBlocks((CommandSourceStack)commandContext.getSource(), new BoundingBox(BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "from"), BlockPosArgument.getLoadedBlockPos((CommandContext<CommandSourceStack>)commandContext, "to")), BlockStateArgument.getBlock((CommandContext<CommandSourceStack>)commandContext, "block"), Mode.DESTROY, null)))))));
    }
    
    private static int fillBlocks(final CommandSourceStack cz, final BoundingBox ctd, final BlockInput ec, final Mode a, @Nullable final Predicate<BlockInWorld> predicate) throws CommandSyntaxException {
        final int integer6 = ctd.getXSpan() * ctd.getYSpan() * ctd.getZSpan();
        if (integer6 > 32768) {
            throw FillCommand.ERROR_AREA_TOO_LARGE.create(32768, integer6);
        }
        final List<BlockPos> list7 = Lists.newArrayList();
        final ServerLevel zd8 = cz.getLevel();
        int integer7 = 0;
        for (final BlockPos fu11 : BlockPos.betweenClosed(ctd.x0, ctd.y0, ctd.z0, ctd.x1, ctd.y1, ctd.z1)) {
            if (predicate != null && !predicate.test(new BlockInWorld(zd8, fu11, true))) {
                continue;
            }
            final BlockInput ec2 = a.filter.filter(ctd, fu11, ec, zd8);
            if (ec2 == null) {
                continue;
            }
            final BlockEntity cdl13 = zd8.getBlockEntity(fu11);
            Clearable.tryClear(cdl13);
            if (!ec2.place(zd8, fu11, 2)) {
                continue;
            }
            list7.add(fu11.immutable());
            ++integer7;
        }
        for (final BlockPos fu11 : list7) {
            final Block bvr12 = zd8.getBlockState(fu11).getBlock();
            zd8.blockUpdated(fu11, bvr12);
        }
        if (integer7 == 0) {
            throw FillCommand.ERROR_FAILED.create();
        }
        cz.sendSuccess(new TranslatableComponent("commands.fill.success", new Object[] { integer7 }), true);
        return integer7;
    }
    
    static {
        ERROR_AREA_TOO_LARGE = new Dynamic2CommandExceptionType((object1, object2) -> new TranslatableComponent("commands.fill.toobig", new Object[] { object1, object2 }));
        HOLLOW_CORE = new BlockInput(Blocks.AIR.defaultBlockState(), Collections.<Property<?>>emptySet(), null);
        ERROR_FAILED = new SimpleCommandExceptionType((Message)new TranslatableComponent("commands.fill.failed"));
    }
    
    enum Mode {
        REPLACE((ctd, fu, ec, zd) -> ec), 
        OUTLINE((ctd, fu, ec, zd) -> {
            if (fu.getX() == ctd.x0 || fu.getX() == ctd.x1 || fu.getY() == ctd.y0 || fu.getY() == ctd.y1 || fu.getZ() == ctd.z0 || fu.getZ() == ctd.z1) {
                return ec;
            }
            else {
                return null;
            }
        }), 
        HOLLOW((ctd, fu, ec, zd) -> {
            if (fu.getX() == ctd.x0 || fu.getX() == ctd.x1 || fu.getY() == ctd.y0 || fu.getY() == ctd.y1 || fu.getZ() == ctd.z0 || fu.getZ() == ctd.z1) {
                return ec;
            }
            else {
                return FillCommand.HOLLOW_CORE;
            }
        }), 
        DESTROY((ctd, fu, ec, zd) -> {
            zd.destroyBlock(fu, true);
            return ec;
        });
        
        public final SetBlockCommand.Filter filter;
        
        private Mode(final SetBlockCommand.Filter a) {
            this.filter = a;
        }
    }
}
