// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.builtin.buildertools.tooloperations;

import java.util.concurrent.ConcurrentHashMap;
import com.hypixel.hytale.builtin.buildertools.scriptedbrushes.BrushConfig;
import com.hypixel.hytale.server.core.prefab.selection.mask.BlockFilter;
import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Mirror;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Rotate;
import com.hypixel.hytale.protocol.packets.buildertools.BrushAxis;
import com.hypixel.hytale.math.block.BlockTorusUtil;
import com.hypixel.hytale.math.block.BlockDiamondUtil;
import com.hypixel.hytale.math.block.BlockInvertedDomeUtil;
import com.hypixel.hytale.math.block.BlockDomeUtil;
import com.hypixel.hytale.math.block.BlockPyramidUtil;
import com.hypixel.hytale.math.block.BlockConeUtil;
import com.hypixel.hytale.math.block.BlockCylinderUtil;
import com.hypixel.hytale.math.block.BlockSphereUtil;
import com.hypixel.hytale.math.block.BlockCubeUtil;
import com.hypixel.hytale.server.core.util.TargetUtil;
import java.util.ArrayList;
import java.util.List;
import com.hypixel.hytale.protocol.packets.buildertools.BrushOrigin;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.asset.type.buildertool.config.BrushData;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.protocol.packets.buildertools.BuilderToolOnUseInteraction;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.prefab.selection.mask.BlockMask;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.builtin.buildertools.tooloperations.transform.Transform;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.entity.entities.Player;
import java.util.Random;
import com.hypixel.hytale.server.core.asset.type.buildertool.config.BuilderTool;
import com.hypixel.hytale.builtin.buildertools.EditOperation;
import com.hypixel.hytale.server.core.prefab.selection.mask.BlockPattern;
import com.hypixel.hytale.protocol.packets.buildertools.BrushShape;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.builtin.buildertools.PrototypePlayerBuilderToolSettings;
import java.util.UUID;
import javax.annotation.Nonnull;
import java.util.Map;
import com.hypixel.hytale.function.predicate.TriIntObjPredicate;

public abstract class ToolOperation implements TriIntObjPredicate<Void>
{
    protected static final int RANDOM_MAX = 100;
    @Nonnull
    public static final Map<String, OperationFactory> OPERATIONS;
    @Nonnull
    public static final Map<UUID, PrototypePlayerBuilderToolSettings> PROTOTYPE_TOOL_SETTINGS;
    public static final double MAX_DISTANCE = 400.0;
    public static final int DEFAULT_BRUSH_SPACING = 0;
    protected final int x;
    protected final int y;
    protected final int z;
    protected final InteractionType interactionType;
    protected final int shapeRange;
    protected final int shapeHeight;
    protected final int shapeThickness;
    protected final boolean capped;
    protected final int originOffsetX;
    protected final int originOffsetY;
    protected final int originOffsetZ;
    protected final BrushShape shape;
    protected final BlockPattern pattern;
    @Nonnull
    protected final EditOperation edit;
    @Nonnull
    protected final BuilderTool.ArgData args;
    @Nonnull
    protected final Random random;
    @Nonnull
    protected final Player player;
    @Nonnull
    protected final Ref<EntityStore> playerRef;
    @Nonnull
    protected final BuilderToolsPlugin.BuilderState builderState;
    private final Transform transform;
    private final Vector3i vector;
    @Nullable
    private final BlockMask mask;
    
    public ToolOperation(@Nonnull final Ref<EntityStore> ref, @Nonnull final BuilderToolOnUseInteraction packet, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.vector = new Vector3i();
        this.playerRef = ref;
        final World world = componentAccessor.getExternalData().getWorld();
        final Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType());
        assert playerComponent != null;
        final PlayerRef playerRefComponent = componentAccessor.getComponent(ref, PlayerRef.getComponentType());
        assert playerRefComponent != null;
        this.player = playerComponent;
        this.builderState = BuilderToolsPlugin.getState(playerComponent, playerRefComponent);
        final UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType());
        assert uuidComponent != null;
        final UUID uuid = uuidComponent.getUuid();
        PrototypePlayerBuilderToolSettings playerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuid);
        if (playerBuilderToolSettings == null) {
            playerBuilderToolSettings = new PrototypePlayerBuilderToolSettings(uuid);
            ToolOperation.PROTOTYPE_TOOL_SETTINGS.put(uuid, playerBuilderToolSettings);
        }
        playerBuilderToolSettings.setShouldShowEditorSettings(packet.isShowEditNotifications);
        playerBuilderToolSettings.setMaxLengthOfIgnoredPaintOperations(packet.maxLengthToolIgnoreHistory);
        if (!packet.isHoldDownInteraction && (this instanceof PaintOperation || this instanceof SculptOperation)) {
            playerBuilderToolSettings.getIgnoredPaintOperations().clear();
            playerBuilderToolSettings.clearLastBrushPosition();
        }
        if (packet.isDoServerRaytraceForPosition && (this instanceof PaintOperation || this instanceof SculptOperation)) {
            final Vector3i targetBlockAvoidingPaint = this.getTargetBlockAvoidingPaint(ref, 400.0, componentAccessor, packet.raycastOriginX, packet.raycastOriginY, packet.raycastOriginZ, packet.raycastDirectionX, packet.raycastDirectionY, packet.raycastDirectionZ);
            if (targetBlockAvoidingPaint != null) {
                this.x = targetBlockAvoidingPaint.x + packet.offsetForPaintModeX;
                this.y = targetBlockAvoidingPaint.y + packet.offsetForPaintModeY;
                this.z = targetBlockAvoidingPaint.z + packet.offsetForPaintModeZ;
            }
            else {
                this.x = packet.x;
                this.y = packet.y;
                this.z = packet.z;
            }
        }
        else {
            this.x = packet.x;
            this.y = packet.y;
            this.z = packet.z;
        }
        this.interactionType = packet.type;
        final BuilderTool builderTool = BuilderTool.getActiveBuilderTool(playerComponent);
        final BuilderTool.ArgData itemArgData = builderTool.getItemArgData(playerComponent.getInventory().getItemInHand());
        this.args = itemArgData;
        final BuilderTool.ArgData args = itemArgData;
        BrushData.Values brush = args.brush();
        if (brush == null) {
            brush = new BrushData.Values(BrushData.DEFAULT);
        }
        this.transform = getTransform(ref, brush, this.vector, componentAccessor);
        this.shapeRange = brush.getWidth();
        this.shapeHeight = brush.getHeight();
        this.shapeThickness = brush.getThickness();
        this.capped = brush.isCapped();
        this.shape = brush.getShape();
        this.pattern = this.getPattern(packet, brush);
        this.mask = combineMasks(brush, this.builderState.getGlobalMask());
        final BrushOrigin shapeOrigin = brush.getOrigin();
        final boolean originRotation = brush.getOriginRotation();
        final Vector3i offsets = getOffsets(this.shapeRange, this.shapeHeight, originRotation, shapeOrigin, this.transform, this.vector, true);
        this.originOffsetX = offsets.getX();
        this.originOffsetY = offsets.getY();
        this.originOffsetZ = offsets.getZ();
        this.random = this.builderState.getRandom();
        final Vector3i brushMin = new Vector3i(this.x - this.shapeRange, this.y - this.shapeHeight, this.z - this.shapeRange);
        final Vector3i brushMax = new Vector3i(this.x + this.shapeRange, this.y + this.shapeHeight, this.z + this.shapeRange);
        this.edit = new EditOperation(world, this.x, this.y, this.z, this.shapeRange, brushMin, brushMax, this.mask);
    }
    
    @Nonnull
    public static PrototypePlayerBuilderToolSettings getOrCreatePrototypeSettings(final UUID playerUuid) {
        PrototypePlayerBuilderToolSettings settings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(playerUuid);
        if (settings == null) {
            settings = new PrototypePlayerBuilderToolSettings(playerUuid);
            ToolOperation.PROTOTYPE_TOOL_SETTINGS.put(playerUuid, settings);
        }
        return settings;
    }
    
    @Nonnull
    public static List<Vector3i> calculateInterpolatedPositions(@Nullable final Vector3i lastPosition, @Nonnull final Vector3i currentPosition, final int brushWidth, final int brushHeight, final int brushSpacing) {
        final ArrayList<Vector3i> positions = new ArrayList<Vector3i>();
        if (lastPosition == null) {
            positions.add(currentPosition);
            return positions;
        }
        final double dx = currentPosition.getX() - lastPosition.getX();
        final double dy = currentPosition.getY() - lastPosition.getY();
        final double dz = currentPosition.getZ() - lastPosition.getZ();
        final double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
        if (brushSpacing == 0) {
            final float maxBrushDimension = (float)Math.max(brushWidth, brushHeight);
            final float spacingThreshold = Math.max(1.0f, maxBrushDimension * 0.5f);
            if (distance <= spacingThreshold) {
                positions.add(currentPosition);
                return positions;
            }
            for (int steps = (int)Math.ceil(distance / spacingThreshold), i = 1; i <= steps; ++i) {
                final float t = i / (float)steps;
                final int interpX = (int)Math.round(lastPosition.getX() + dx * t);
                final int interpY = (int)Math.round(lastPosition.getY() + dy * t);
                final int interpZ = (int)Math.round(lastPosition.getZ() + dz * t);
                positions.add(new Vector3i(interpX, interpY, interpZ));
            }
        }
        else if (distance >= brushSpacing) {
            positions.add(currentPosition);
        }
        return positions;
    }
    
    @Nonnull
    public Vector3i getPosition() {
        return new Vector3i(this.x, this.y, this.z);
    }
    
    public int getBrushWidth() {
        return this.shapeRange;
    }
    
    public int getBrushHeight() {
        return this.shapeHeight;
    }
    
    public int getBrushSpacing() {
        final Object spacingValue = this.args.tool().get("BrushSpacing");
        if (spacingValue instanceof final Number n) {
            return n.intValue();
        }
        return 0;
    }
    
    public int getBrushDensity() {
        final Object densityValue = this.args.tool().get("BrushDensity");
        if (densityValue instanceof final Number number) {
            return number.intValue();
        }
        return 100;
    }
    
    public void executeAsBrushConfig(@Nonnull final PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings, @Nonnull final BuilderToolOnUseInteraction packet, final ComponentAccessor<EntityStore> componentAccessor) {
        final World world = componentAccessor.getExternalData().getWorld();
        prototypePlayerBuilderToolSettings.getBrushConfigCommandExecutor().execute(this.playerRef, world, new Vector3i(this.x, this.y, this.z), packet.isHoldDownInteraction, packet.type, bc -> {
            bc.setPattern(this.pattern);
            bc.setDensity(this.getBrushDensity());
            bc.setShapeHeight(this.shapeHeight);
            bc.setShapeWidth(this.shapeRange);
            bc.setShape(this.shape);
            bc.setCapped(this.capped);
            bc.modifyOriginOffset(new Vector3i(this.originOffsetX, this.originOffsetY, this.originOffsetZ));
            bc.setBrushMask(this.mask);
            bc.setShapeThickness(this.shapeThickness);
        }, componentAccessor);
    }
    
    private BlockPattern getPattern(@Nonnull final BuilderToolOnUseInteraction packet, @Nonnull final BrushData.Values brush) {
        if (packet.type == InteractionType.Primary) {
            return BlockPattern.EMPTY;
        }
        if ((this instanceof PaintOperation || this instanceof PaintOperation) && brush.getMaterial().equals(BlockPattern.EMPTY)) {
            return BlockPattern.parse("Rock_Stone");
        }
        return brush.getMaterial();
    }
    
    @Nullable
    public Vector3i getTargetBlockAvoidingPaint(@Nonnull final Ref<EntityStore> ref, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor, final float raycastOriginX, final float raycastOriginY, final float raycastOriginZ, final float raycastDirectionX, final float raycastDirectionY, final float raycastDirectionZ) {
        final World world = componentAccessor.getExternalData().getWorld();
        final UUIDComponent uuidComponent = componentAccessor.getComponent(ref, UUIDComponent.getComponentType());
        assert uuidComponent != null;
        final PrototypePlayerBuilderToolSettings prototypePlayerBuilderToolSettings = ToolOperation.PROTOTYPE_TOOL_SETTINGS.get(uuidComponent.getUuid());
        if (prototypePlayerBuilderToolSettings == null || prototypePlayerBuilderToolSettings.getIgnoredPaintOperations().isEmpty()) {
            return TargetUtil.getTargetBlock(world, (blockId, _fluidId) -> blockId != 0, raycastOriginX, raycastOriginY, raycastOriginZ, raycastDirectionX, raycastDirectionY, raycastDirectionZ, maxDistance);
        }
        return TargetUtil.getTargetBlockAvoidLocations(world, blockId -> blockId != 0, raycastOriginX, raycastOriginY, raycastOriginZ, raycastDirectionX, raycastDirectionY, raycastDirectionZ, maxDistance, prototypePlayerBuilderToolSettings.getIgnoredPaintOperations());
    }
    
    @Nonnull
    public EditOperation getEditOperation() {
        return this.edit;
    }
    
    @Override
    public final boolean test(int x, int y, int z, final Void aVoid) {
        if (this.transform == Transform.NONE) {
            return this.execute0(x, y + this.originOffsetY, z);
        }
        this.vector.assign(x - this.x, y - this.y, z - this.z);
        this.transform.apply(this.vector);
        x = this.x + this.originOffsetX + this.vector.x;
        y = this.y + this.originOffsetY + this.vector.y;
        z = this.z + this.originOffsetZ + this.vector.z;
        return this.execute0(x, y, z);
    }
    
    abstract boolean execute0(final int p0, final int p1, final int p2);
    
    public void execute(final ComponentAccessor<EntityStore> componentAccessor) {
        executeShapeOperation(this.x, this.y, this.z, this, this.shape, this.shapeRange, this.shapeHeight, this.shapeThickness, this.capped);
    }
    
    public void executeAt(final int posX, final int posY, final int posZ, final ComponentAccessor<EntityStore> componentAccessor) {
        executeShapeOperation(posX, posY, posZ, this, this.shape, this.shapeRange, this.shapeHeight, this.shapeThickness, this.capped);
    }
    
    public static void executeShapeOperation(final int x, final int y, final int z, @Nonnull final TriIntObjPredicate<Void> operation, @Nonnull final BrushShape shape, final int shapeRange, final int shapeHeight, final int shapeThickness, final boolean capped) {
        if (shapeRange <= 1 && shapeHeight <= 1) {
            operation.test(x, y, z, null);
        }
        else {
            final int radiusXZ = Math.max(shapeRange / 2, 1);
            final int halfHeight = Math.max(shapeHeight / 2, 1);
            switch (shape) {
                default: {
                    BlockCubeUtil.forEachBlock(x, y, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Sphere: {
                    BlockSphereUtil.forEachBlock(x, y, z, radiusXZ, halfHeight, radiusXZ, shapeThickness, null, operation);
                    break;
                }
                case Cylinder: {
                    BlockCylinderUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Cone: {
                    BlockConeUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case InvertedCone: {
                    BlockConeUtil.forEachBlockInverted(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Pyramid: {
                    BlockPyramidUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case InvertedPyramid: {
                    BlockPyramidUtil.forEachBlockInverted(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Dome: {
                    BlockDomeUtil.forEachBlock(x, y - halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case InvertedDome: {
                    BlockInvertedDomeUtil.forEachBlock(x, y + halfHeight, z, radiusXZ, shapeHeight, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Diamond: {
                    BlockDiamondUtil.forEachBlock(x, y, z, radiusXZ, shapeHeight / 2, radiusXZ, shapeThickness, capped, null, operation);
                    break;
                }
                case Torus: {
                    final int minorRadius = Math.max(1, shapeHeight / 4);
                    final int outerRadius = radiusXZ;
                    BlockTorusUtil.forEachBlock(x, y, z, outerRadius, minorRadius, shapeThickness, capped, null, operation);
                    break;
                }
            }
        }
    }
    
    @Nonnull
    private static Vector3i getOffsets(final int width, final int height, final boolean originRotation, final BrushOrigin origin, @Nonnull final Transform transform, @Nonnull final Vector3i vector, final boolean applyBottomOriginFix) {
        final int offsetY = height / 2;
        final int offsetXZ = originRotation ? (width / 2) : 0;
        vector.assign(0, offsetY, 0);
        transform.apply(vector);
        int ox = vector.getX();
        int oz = vector.getZ();
        vector.assign(offsetXZ, offsetY, -offsetXZ);
        transform.apply(vector);
        int oy = vector.getY();
        ox = ((origin == BrushOrigin.Center) ? 0 : ((origin == BrushOrigin.Bottom) ? ox : (-ox)));
        oy = ((origin == BrushOrigin.Center) ? 0 : ((origin == BrushOrigin.Bottom) ? (oy + (applyBottomOriginFix ? 1 : 0)) : (-oy)));
        oz = ((origin == BrushOrigin.Center) ? 0 : ((origin == BrushOrigin.Bottom) ? oz : (-oz)));
        return vector.assign(ox, oy, oz);
    }
    
    private static Transform getTransform(@Nonnull final Ref<EntityStore> ref, @Nonnull final BrushData.Values brushData, @Nonnull final Vector3i vector, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final Transform rotate = getRotation(ref, brushData, vector, componentAccessor);
        final Transform mirror = getMirror(ref, brushData, vector, componentAccessor);
        return rotate.then(mirror);
    }
    
    private static Transform getRotation(@Nonnull final Ref<EntityStore> ref, @Nonnull final BrushData.Values brushData, @Nonnull final Vector3i vector, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (brushData.getRotationAxis() != BrushAxis.Auto) {
            return Rotate.forAxisAndAngle(brushData.getRotationAxis(), brushData.getRotationAngle());
        }
        final HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());
        assert headRotationComponent != null;
        return Rotate.forDirection(headRotationComponent.getAxisDirection(vector), brushData.getRotationAngle());
    }
    
    private static Transform getMirror(@Nonnull final Ref<EntityStore> ref, @Nonnull final BrushData.Values brushData, @Nonnull final Vector3i vector, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (brushData.getMirrorAxis() != BrushAxis.Auto) {
            return Mirror.forAxis(brushData.getMirrorAxis());
        }
        final HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());
        assert headRotationComponent != null;
        return Mirror.forDirection(headRotationComponent.getAxisDirection(vector), false);
    }
    
    @Nonnull
    public static ToolOperation fromPacket(@Nonnull final Ref<EntityStore> ref, @Nonnull final Player player, @Nonnull final BuilderToolOnUseInteraction packet, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) throws Exception {
        final BuilderTool builderTool = BuilderTool.getActiveBuilderTool(player);
        if (builderTool == null) {
            throw new IllegalStateException("No builder tool active on player");
        }
        final String toolType = builderTool.getId();
        final OperationFactory factory = ToolOperation.OPERATIONS.get(toolType);
        if (factory == null) {
            throw new Exception("No tool found matching id " + toolType);
        }
        return factory.create(ref, player, packet, componentAccessor);
    }
    
    @Nullable
    public static BlockMask combineMasks(@Nullable final BrushData.Values brush, @Nullable final BlockMask globalMask) {
        if (brush == null) {
            return globalMask;
        }
        if (brush.shouldUseMaskCommands()) {
            final BlockMask mask = BlockMask.combine(brush.getParsedMaskCommands());
            if (mask != null) {
                mask.setInverted(brush.shouldInvertMask());
            }
            return mask;
        }
        final BlockMask brushMaskAbove = brush.getMaskAbove().withOptions(BlockFilter.FilterType.AboveBlock, false);
        final BlockMask brushMaskNot = brush.getMaskNot().withOptions(BlockFilter.FilterType.TargetBlock, true);
        final BlockMask brushMaskBelow = brush.getMaskBelow().withOptions(BlockFilter.FilterType.BelowBlock, false);
        final BlockMask brushMaskAdjacent = brush.getMaskAdjacent().withOptions(BlockFilter.FilterType.AdjacentBlock, false);
        final BlockMask brushMaskNeighbor = brush.getMaskNeighbor().withOptions(BlockFilter.FilterType.NeighborBlock, false);
        final BlockMask combinedMask = BlockMask.combine(brush.getMask(), brushMaskAbove, brushMaskNot, brushMaskBelow, brushMaskAdjacent, brushMaskNeighbor, globalMask);
        if (combinedMask != null) {
            combinedMask.setInverted(brush.shouldInvertMask());
        }
        return combinedMask;
    }
    
    static {
        OPERATIONS = new ConcurrentHashMap<String, OperationFactory>();
        PROTOTYPE_TOOL_SETTINGS = new ConcurrentHashMap<UUID, PrototypePlayerBuilderToolSettings>();
        ToolOperation.OPERATIONS.put("Flood", FloodOperation::new);
        ToolOperation.OPERATIONS.put("Noise", NoiseOperation::new);
        ToolOperation.OPERATIONS.put("Scatter", ScatterOperation::new);
        ToolOperation.OPERATIONS.put("Smooth", (ref, player1, packet, componentAccessor) -> new SmoothOperation(ref, packet, componentAccessor));
        ToolOperation.OPERATIONS.put("Tint", TintOperation::new);
        ToolOperation.OPERATIONS.put("Paint", PaintOperation::new);
        ToolOperation.OPERATIONS.put("Sculpt", SculptOperation::new);
        ToolOperation.OPERATIONS.put("Layers", LayersOperation::new);
        ToolOperation.OPERATIONS.put("LaserPointer", LaserPointerOperation::new);
    }
}
