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

package com.hypixel.hytale.server.core.universe.world.connectedblocks;

import java.util.Objects;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.server.core.universe.world.World;
import java.util.Iterator;
import java.util.Set;
import java.util.Queue;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import com.hypixel.hytale.math.util.ChunkUtil;
import java.util.Map;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import java.util.ArrayDeque;
import java.util.Optional;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.vector.Vector3i;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;

public class ConnectedBlocksUtil
{
    private static final int MAX_UPDATE_DEPTH = 3;
    
    public static void setConnectedBlockAndNotifyNeighbors(final int blockTypeId, @Nonnull final RotationTuple blockTypeRotation, @Nonnull final Vector3i placementNormal, @Nonnull final Vector3i blockPosition, @Nonnull final WorldChunk worldChunkComponent, @Nonnull final BlockChunk blockChunkComponent) {
        final Vector3i coordinate = new Vector3i(blockPosition);
        final BlockType blockType = BlockType.getAssetMap().getAsset(blockTypeId);
        if (blockType == null) {
            return;
        }
        final BlockSection sectionAtY = blockChunkComponent.getSectionAtBlockY(blockPosition.y);
        final int filler = sectionAtY.getFiller(blockPosition.x, blockPosition.y, blockPosition.z);
        final int settings = 132;
        if (blockType.getConnectedBlockRuleSet() != null && filler == 0) {
            final int rotationIndex = blockTypeRotation.index();
            final Optional<ConnectedBlockResult> foundPattern = getDesiredConnectedBlockType(worldChunkComponent.getWorld(), coordinate, blockType, rotationIndex, placementNormal, true);
            if (foundPattern.isPresent() && (!foundPattern.get().blockTypeKey().equals(blockType.getId()) || foundPattern.get().rotationIndex != rotationIndex)) {
                final ConnectedBlockResult result = foundPattern.get();
                final int id = BlockType.getAssetMap().getIndex(result.blockTypeKey());
                final int rotation = result.rotationIndex();
                worldChunkComponent.setBlock(coordinate.x, coordinate.y, coordinate.z, id, BlockType.getAssetMap().getAsset(id), rotation, 0, settings);
            }
        }
        updateNeighborsWithDepth(worldChunkComponent, coordinate, placementNormal, settings);
    }
    
    private static void updateNeighborsWithDepth(@Nonnull final WorldChunk worldChunkComponent, @Nonnull final Vector3i startCoordinate, @Nonnull final Vector3i placementNormal, final int settings) {
        record QueueEntry(Vector3i coordinate, int depth) {}
        final Queue<QueueEntry> queue = new ArrayDeque<QueueEntry>();
        final Set<Vector3i> visited = new ObjectOpenHashSet<Vector3i>();
        queue.add(new QueueEntry(new Vector3i(startCoordinate), 0));
        while (!queue.isEmpty()) {
            final QueueEntry entry = queue.poll();
            final Vector3i coordinate = entry.coordinate;
            final int depth = entry.depth;
            final Map<Vector3i, ConnectedBlockResult> desiredChanges = new Object2ObjectOpenHashMap<Vector3i, ConnectedBlockResult>();
            notifyNeighborsAndCollectChanges(worldChunkComponent.getWorld(), coordinate, desiredChanges, placementNormal);
            for (final Map.Entry<Vector3i, ConnectedBlockResult> result : desiredChanges.entrySet()) {
                final Vector3i location = result.getKey();
                final ConnectedBlockResult connectedBlockResult = result.getValue();
                if (!visited.add(location.clone())) {
                    continue;
                }
                if (location.x == coordinate.x && location.y == coordinate.y && location.z == coordinate.z) {
                    continue;
                }
                WorldChunk newWorldChunk = worldChunkComponent;
                final long chunkIndex = ChunkUtil.indexChunkFromBlock(location.x, location.z);
                if (chunkIndex != newWorldChunk.getIndex()) {
                    newWorldChunk = worldChunkComponent.getWorld().getChunkIfLoaded(chunkIndex);
                    if (newWorldChunk == null) {
                        continue;
                    }
                }
                final int blockId = BlockType.getAssetMap().getIndex(connectedBlockResult.blockTypeKey());
                final BlockType block = BlockType.getAssetMap().getAsset(blockId);
                newWorldChunk.setBlock(location.x, location.y, location.z, blockId, block, connectedBlockResult.rotationIndex(), 0, settings);
                for (final Map.Entry<Vector3i, ObjectIntPair<String>> additionalEntry : connectedBlockResult.getAdditionalConnectedBlocks().entrySet()) {
                    final Vector3i offset = additionalEntry.getKey();
                    final ObjectIntPair<String> blockData = additionalEntry.getValue();
                    final Vector3i additionalLocation = new Vector3i(location).add(offset);
                    WorldChunk additionalChunk = newWorldChunk;
                    final long additionalChunkIndex = ChunkUtil.indexChunkFromBlock(additionalLocation.x, additionalLocation.z);
                    if (additionalChunkIndex != newWorldChunk.getIndex()) {
                        additionalChunk = worldChunkComponent.getWorld().getChunkIfLoaded(additionalChunkIndex);
                        if (additionalChunk == null) {
                            continue;
                        }
                    }
                    final int additionalBlockId = BlockType.getAssetMap().getIndex(blockData.first());
                    final BlockType additionalBlock = BlockType.getAssetMap().getAsset(additionalBlockId);
                    if (additionalBlock != null) {
                        additionalChunk.setBlock(additionalLocation.x, additionalLocation.y, additionalLocation.z, additionalBlockId, additionalBlock, blockData.rightInt(), 0, settings);
                    }
                }
                if (depth + 1 >= 3) {
                    continue;
                }
                queue.add(new QueueEntry(location.clone(), depth + 1));
            }
        }
    }
    
    public static void notifyNeighborsAndCollectChanges(@Nonnull final World world, @Nonnull final Vector3i origin, @Nonnull final Map<Vector3i, ConnectedBlockResult> desiredChanges, final Vector3i placementNormal) {
        final Vector3i coordinate = origin.clone();
        long chunkIndex = ChunkUtil.indexChunkFromBlock(origin.x, origin.z);
        WorldChunk chunk = world.getChunkIfLoaded(chunkIndex);
        for (int x1 = -1; x1 <= 1; ++x1) {
            for (int z1 = -1; z1 <= 1; ++z1) {
                for (int y1 = -1; y1 <= 1; ++y1) {
                    if (x1 != 0 || y1 != 0 || z1 != 0) {
                        coordinate.assign(origin).add(x1, y1, z1);
                        if (coordinate.y >= 0) {
                            if (coordinate.y < 320) {
                                if (!desiredChanges.containsKey(coordinate)) {
                                    final long neighborChunkIndex = ChunkUtil.indexChunkFromBlock(coordinate.x, coordinate.z);
                                    if (neighborChunkIndex != chunkIndex) {
                                        chunkIndex = neighborChunkIndex;
                                        chunk = world.getChunkIfLoaded(neighborChunkIndex);
                                    }
                                    if (chunk != null) {
                                        final BlockChunk blockChunk = chunk.getBlockChunk();
                                        if (blockChunk != null) {
                                            final BlockSection blockSection = blockChunk.getSectionAtBlockY(coordinate.y);
                                            if (blockSection != null) {
                                                final int neighborBlockId = blockSection.get(coordinate.x, coordinate.y, coordinate.z);
                                                final BlockType neighborBlockType = BlockType.getAssetMap().getAsset(neighborBlockId);
                                                if (neighborBlockType != null) {
                                                    final ConnectedBlockRuleSet ruleSet = neighborBlockType.getConnectedBlockRuleSet();
                                                    if (ruleSet != null) {
                                                        if (!ruleSet.onlyUpdateOnPlacement()) {
                                                            final int filler = blockSection.getFiller(coordinate.x, coordinate.y, coordinate.z);
                                                            final int existingRotation = blockSection.getRotationIndex(coordinate.x, coordinate.y, coordinate.z);
                                                            if (filler != 0) {
                                                                final int originX = coordinate.x - FillerBlockUtil.unpackX(filler);
                                                                final int originY = coordinate.y - FillerBlockUtil.unpackY(filler);
                                                                final int originZ = coordinate.z - FillerBlockUtil.unpackZ(filler);
                                                                coordinate.assign(originX, originY, originZ);
                                                            }
                                                            final Optional<ConnectedBlockResult> output = getDesiredConnectedBlockType(world, coordinate, neighborBlockType, existingRotation, placementNormal, false);
                                                            if (output.isPresent() && (!neighborBlockType.getId().equals(output.get().blockTypeKey()) || output.get().rotationIndex != existingRotation)) {
                                                                desiredChanges.put(coordinate.clone(), output.get());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    @Nonnull
    public static Optional<ConnectedBlockResult> getDesiredConnectedBlockType(@Nonnull final World world, @Nonnull final Vector3i coordinate, @Nonnull final BlockType currentBlockType, final int currentRotation, @Nonnull final Vector3i placementNormal, final boolean isPlacement) {
        final ConnectedBlockRuleSet ruleSet = currentBlockType.getConnectedBlockRuleSet();
        if (ruleSet == null) {
            return Optional.empty();
        }
        return ruleSet.getConnectedBlockType(world, coordinate, currentBlockType, currentRotation, placementNormal, isPlacement);
    }
    
    public static final class ConnectedBlockResult
    {
        private final String blockTypeKey;
        private final int rotationIndex;
        private final Map<Vector3i, ObjectIntPair<String>> additionalConnectedBlocks;
        
        public ConnectedBlockResult(@Nonnull final String blockTypeKey, final int rotationIndex) {
            this.additionalConnectedBlocks = new Object2ObjectOpenHashMap<Vector3i, ObjectIntPair<String>>();
            this.blockTypeKey = blockTypeKey;
            this.rotationIndex = rotationIndex;
        }
        
        @Nonnull
        public String blockTypeKey() {
            return this.blockTypeKey;
        }
        
        public int rotationIndex() {
            return this.rotationIndex;
        }
        
        @Nonnull
        public Map<Vector3i, ObjectIntPair<String>> getAdditionalConnectedBlocks() {
            return this.additionalConnectedBlocks;
        }
        
        public void addAdditionalBlock(@Nonnull final Vector3i offset, @Nonnull final String blockTypeKey, final int rotationIndex) {
            this.additionalConnectedBlocks.put(offset, ObjectIntPair.of(blockTypeKey, rotationIndex));
        }
        
        @Override
        public boolean equals(final Object obj) {
            if (obj == this) {
                return true;
            }
            if (obj == null || obj.getClass() != this.getClass()) {
                return false;
            }
            final ConnectedBlockResult that = (ConnectedBlockResult)obj;
            return Objects.equals(this.blockTypeKey, that.blockTypeKey) && this.rotationIndex == that.rotationIndex && Objects.equals(this.additionalConnectedBlocks, that.additionalConnectedBlocks);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(this.blockTypeKey, this.rotationIndex, this.additionalConnectedBlocks);
        }
        
        @Override
        public String toString() {
            return "ConnectedBlockResult[blockTypeKey=" + this.blockTypeKey + ", rotationIndex=" + this.rotationIndex + ", additionalConnectedBlocks=" + String.valueOf(this.additionalConnectedBlocks);
        }
    }
}
