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

package com.hypixel.hytale.builtin.blockphysics;

import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFaceSupport;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule;
import java.util.Map;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RequiredBlockFaceSupport;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockFace;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection;
import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Ref;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;

public class BlockPhysicsUtil
{
    public static final int DOESNT_SATISFY = 0;
    public static final int IGNORE = -1;
    public static final int SATISFIES_SUPPORT = -2;
    public static final int WAITING_CHUNK = -3;
    
    @Nonnull
    public static Result applyBlockPhysics(@Nullable final ComponentAccessor<EntityStore> commandBuffer, @Nonnull final Ref<ChunkStore> chunkReference, @Nonnull final BlockPhysicsSystems.CachedAccessor chunkAccessor, final BlockSection blockSection, @Nonnull final BlockPhysics blockPhysics, @Nonnull final FluidSection fluidSection, final int blockX, final int blockY, final int blockZ, @Nonnull final BlockType blockType, final int rotation, final int filler) {
        if (filler != 0) {
            return Result.VALID;
        }
        int supportDistance = -1;
        if (blockType.getHitboxTypeIndex() != 0) {
            final BlockBoundingBoxes boundingBoxes = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex());
            if (boundingBoxes.protrudesUnitBox()) {
                final BlockBoundingBoxes.RotatedVariantBoxes rotatedBox = boundingBoxes.get(rotation);
                final Box boundingBox = rotatedBox.getBoundingBox();
                int minX = (int)boundingBox.min.x;
                int minY = (int)boundingBox.min.y;
                int minZ = (int)boundingBox.min.z;
                if (minX - boundingBox.min.x > 0.0) {
                    --minX;
                }
                if (minY - boundingBox.min.y > 0.0) {
                    --minY;
                }
                if (minZ - boundingBox.min.z > 0.0) {
                    --minZ;
                }
                int maxX = (int)boundingBox.max.x;
                int maxY = (int)boundingBox.max.y;
                int maxZ = (int)boundingBox.max.z;
                if (boundingBox.max.x - maxX > 0.0) {
                    ++maxX;
                }
                if (boundingBox.max.y - maxY > 0.0) {
                    ++maxY;
                }
                if (boundingBox.max.z - maxZ > 0.0) {
                    ++maxZ;
                }
                final int blockWidth = Math.max(maxX - minX, 1);
                final int blockHeight = Math.max(maxY - minY, 1);
                final int blockDepth = Math.max(maxZ - minZ, 1);
            Label_0647:
                for (int x = 0; x < blockWidth; ++x) {
                    for (int y = 0; y < blockHeight; ++y) {
                        for (int z = 0; z < blockDepth; ++z) {
                            final int fillerX = blockX + minX + x;
                            final int fillerY = blockY + minY + y;
                            final int fillerZ = blockZ + minZ + z;
                            BlockSection neighbourBlockSection;
                            FluidSection neighbourFluidSection;
                            BlockPhysics neighbourBlockPhysics;
                            if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, fillerX, fillerY, fillerZ)) {
                                neighbourBlockSection = blockSection;
                                neighbourFluidSection = fluidSection;
                                neighbourBlockPhysics = blockPhysics;
                            }
                            else {
                                final int nx = ChunkUtil.chunkCoordinate(fillerX);
                                final int ny = ChunkUtil.chunkCoordinate(fillerY);
                                final int nz = ChunkUtil.chunkCoordinate(fillerZ);
                                neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz);
                                neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz);
                                neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz);
                            }
                            if (neighbourBlockSection == null || neighbourFluidSection == null) {
                                return Result.WAITING_CHUNK;
                            }
                            final int neighbourFiller = FillerBlockUtil.pack(minX + x, minY + y, minZ + z);
                            final int neighbourRotation = neighbourBlockSection.getRotationIndex(fillerX, fillerY, fillerZ);
                            final int fillerSupportDistance = testBlockPhysics(chunkAccessor, neighbourBlockSection, neighbourBlockPhysics, neighbourFluidSection, fillerX, fillerY, fillerZ, blockType, neighbourRotation, neighbourFiller);
                            if (fillerSupportDistance != -1) {
                                switch (blockType.getBlockSupportsRequiredFor()) {
                                    case Any: {
                                        if (fillerSupportDistance == -2) {
                                            supportDistance = -2;
                                            break Label_0647;
                                        }
                                        if (fillerSupportDistance == 0) {
                                            supportDistance = 0;
                                            break;
                                        }
                                        if (supportDistance < fillerSupportDistance) {
                                            supportDistance = fillerSupportDistance;
                                            break;
                                        }
                                        break;
                                    }
                                    case All: {
                                        if (fillerSupportDistance == 0) {
                                            supportDistance = 0;
                                            break Label_0647;
                                        }
                                        if (fillerSupportDistance == -2) {
                                            supportDistance = -2;
                                            break;
                                        }
                                        if (supportDistance == -1 && supportDistance < fillerSupportDistance) {
                                            supportDistance = fillerSupportDistance;
                                            break;
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else {
                supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler);
            }
        }
        else {
            supportDistance = testBlockPhysics(chunkAccessor, blockSection, blockPhysics, fluidSection, blockX, blockY, blockZ, blockType, rotation, filler);
        }
        if (supportDistance == 0) {
            final World world = commandBuffer.getExternalData().getWorld();
            final Store<ChunkStore> chunkStore = world.getChunkStore().getStore();
            switch (blockType.getSupportDropType()) {
                case BREAK: {
                    BlockHarvestUtils.naturallyRemoveBlockByPhysics(new Vector3i(blockX, blockY, blockZ), blockType, filler, 256, chunkReference, commandBuffer, chunkStore);
                    break;
                }
                case DESTROY: {
                    BlockHarvestUtils.naturallyRemoveBlockByPhysics(new Vector3i(blockX, blockY, blockZ), blockType, filler, 2304, chunkReference, commandBuffer, chunkStore);
                    break;
                }
            }
            return Result.INVALID;
        }
        if (supportDistance == -1) {
            return Result.VALID;
        }
        if (supportDistance == -3) {
            return Result.WAITING_CHUNK;
        }
        final int currentSupport = blockPhysics.get(blockX, blockY, blockZ);
        if (supportDistance == -2) {
            if (currentSupport != 0) {
                blockPhysics.set(blockX, blockY, blockZ, 0);
                chunkAccessor.performBlockUpdate(blockX, blockY, blockZ);
            }
            return Result.VALID;
        }
        if (currentSupport == supportDistance) {
            chunkAccessor.performBlockUpdate(blockX, blockY, blockZ, supportDistance - 1);
        }
        else {
            blockPhysics.set(blockX, blockY, blockZ, supportDistance);
            chunkAccessor.performBlockUpdate(blockX, blockY, blockZ);
        }
        return Result.VALID;
    }
    
    public static int testBlockPhysics(@Nonnull final BlockPhysicsSystems.CachedAccessor chunkAccessor, final BlockSection blockSection, @Nullable final BlockPhysics blockPhysics, @Nonnull final FluidSection fluidSection, final int blockX, final int blockY, final int blockZ, @Nonnull final BlockType blockType, final int rotation, final int filler) {
        if (blockType.isUnknown()) {
            return -1;
        }
        final Map<BlockFace, RequiredBlockFaceSupport[]> requiredBlockFaceSupportMap = blockType.getSupport(rotation);
        if (requiredBlockFaceSupportMap == null || requiredBlockFaceSupportMap.isEmpty()) {
            return -1;
        }
        final Vector3i blockFillerOffset = new Vector3i(FillerBlockUtil.unpackX(filler), FillerBlockUtil.unpackY(filler), FillerBlockUtil.unpackZ(filler));
        final Vector3i neighbourFillerOffset = new Vector3i();
        final Fluid fluid = Fluid.getAssetMap().getAsset(fluidSection.getFluidId(blockX, blockY, blockZ));
        final BlockBoundingBoxes hitbox = BlockBoundingBoxes.getAssetMap().getAsset(blockType.getHitboxTypeIndex());
        final Box boundingBox = hitbox.get(rotation).getBoundingBox();
        final Vector3i origin = new Vector3i(blockX - FillerBlockUtil.unpackX(filler), blockY - FillerBlockUtil.unpackY(filler), blockZ - FillerBlockUtil.unpackZ(filler));
        boolean hasTestedForSupport = false;
        final int requiredSupportDistance = blockType.getMaxSupportDistance();
        int lowestSupportDistance = Integer.MAX_VALUE;
        for (final BlockFace blockFace : BlockFace.VALUES) {
            final RequiredBlockFaceSupport[] requiredBlockFaceSupports = requiredBlockFaceSupportMap.get(blockFace);
            if (requiredBlockFaceSupports != null) {
                if (requiredBlockFaceSupports.length != 0) {
                    final BlockFace[] connectingFaces = blockFace.getConnectingFaces();
                    final Vector3i[] connectingFaceOffsets = blockFace.getConnectingFaceOffsets();
                    for (int i = 0; i < connectingFaces.length; ++i) {
                        final BlockFace neighbourBlockFace = connectingFaces[i];
                        final Vector3i neighbourDirection = connectingFaceOffsets[i];
                        final int neighbourX = blockX + neighbourDirection.x;
                        final int neighbourY = blockY + neighbourDirection.y;
                        final int neighbourZ = blockZ + neighbourDirection.z;
                        if (!boundingBox.containsBlock(origin, neighbourX, neighbourY, neighbourZ)) {
                            BlockSection neighbourBlockSection;
                            FluidSection neighbourFluidSection;
                            BlockPhysics neighbourBlockPhysics;
                            if (ChunkUtil.isSameChunkSection(blockX, blockY, blockZ, neighbourX, neighbourY, neighbourZ)) {
                                neighbourBlockSection = blockSection;
                                neighbourFluidSection = fluidSection;
                                neighbourBlockPhysics = blockPhysics;
                            }
                            else {
                                final int nx = ChunkUtil.chunkCoordinate(neighbourX);
                                final int ny = ChunkUtil.chunkCoordinate(neighbourY);
                                final int nz = ChunkUtil.chunkCoordinate(neighbourZ);
                                neighbourBlockSection = chunkAccessor.getBlockSection(nx, ny, nz);
                                neighbourFluidSection = chunkAccessor.getFluidSection(nx, ny, nz);
                                neighbourBlockPhysics = chunkAccessor.getBlockPhysics(nx, ny, nz);
                            }
                            if (neighbourFluidSection == null || neighbourBlockSection == null) {
                                return -3;
                            }
                            final int neighbourFluidId = neighbourFluidSection.getFluidId(neighbourX, neighbourY, neighbourZ);
                            final int neighbourBlockId = neighbourBlockSection.get(neighbourX, neighbourY, neighbourZ);
                            final int neighbourFiller = neighbourBlockSection.getFiller(neighbourX, neighbourY, neighbourZ);
                            final int neighbourRotation = neighbourBlockSection.getRotationIndex(neighbourX, neighbourY, neighbourZ);
                            final BlockType neighbourBlockType = BlockType.getAssetMap().getAsset(neighbourBlockId);
                            final Fluid neighbourFluid = Fluid.getAssetMap().getAsset(neighbourFluidId);
                            neighbourFillerOffset.assign(FillerBlockUtil.unpackX(neighbourFiller), FillerBlockUtil.unpackY(neighbourFiller), FillerBlockUtil.unpackZ(neighbourFiller));
                            boolean doesSatisfySupport = false;
                            boolean failedSatisfySupport = false;
                            for (final RequiredBlockFaceSupport requiredBlockFaceSupport : requiredBlockFaceSupports) {
                                if (requiredBlockFaceSupport.isAppliedToFiller(blockFillerOffset)) {
                                    final boolean doesSatisfyRequirements = doesSatisfyRequirements(blockType, fluid, blockFillerOffset, neighbourFillerOffset, blockFace, neighbourBlockFace, neighbourBlockId, neighbourBlockType, neighbourRotation, neighbourFluidId, neighbourFluid, requiredBlockFaceSupport);
                                    if (doesSatisfyRequirements && requiredSupportDistance > 0 && requiredBlockFaceSupport.allowsSupportPropagation()) {
                                        final int supportDistance = (neighbourBlockPhysics != null) ? neighbourBlockPhysics.get(neighbourX, neighbourY, neighbourZ) : 0;
                                        if (supportDistance == 15) {
                                            lowestSupportDistance = 1;
                                        }
                                        else if (supportDistance < lowestSupportDistance) {
                                            lowestSupportDistance = supportDistance;
                                        }
                                    }
                                    switch (requiredBlockFaceSupport.getSupport()) {
                                        case IGNORED: {
                                            break;
                                        }
                                        case REQUIRED: {
                                            if (doesSatisfyRequirements) {
                                                doesSatisfySupport = true;
                                            }
                                            hasTestedForSupport = true;
                                            break;
                                        }
                                        case DISALLOWED: {
                                            if (doesSatisfyRequirements) {
                                                failedSatisfySupport = true;
                                            }
                                            hasTestedForSupport = true;
                                            break;
                                        }
                                        default: {
                                            throw new IllegalArgumentException("Unknown Support Match type: " + String.valueOf(requiredBlockFaceSupport.getMatchSelf()));
                                        }
                                    }
                                }
                            }
                            if (!failedSatisfySupport && doesSatisfySupport) {
                                return -2;
                            }
                        }
                    }
                }
            }
        }
        if (!hasTestedForSupport) {
            return -1;
        }
        if (lowestSupportDistance < Integer.MAX_VALUE && lowestSupportDistance >= 0) {
            final int supportDistance2 = lowestSupportDistance + 1;
            if (requiredSupportDistance >= supportDistance2) {
                return supportDistance2;
            }
        }
        return 0;
    }
    
    public static boolean doesSatisfyRequirements(@Nonnull final BlockType blockType, final Fluid fluid, final Vector3i blockFillerOffset, final Vector3i neighbourFillerOffset, final BlockFace blockFace, final BlockFace neighbourBlockFace, final int neighbourBlockId, @Nonnull final BlockType neighbourBlockType, final int neighbourRotation, final int neighbourFluidId, @Nonnull final Fluid neighbourFluid, @Nonnull final RequiredBlockFaceSupport requiredBlockFaceSupport) {
        final String neighbourBlockTypeKey = neighbourBlockType.getId();
        boolean hasSupport = true;
        final int blockSetId = requiredBlockFaceSupport.getBlockSetIndex();
        if (blockSetId >= 0 && !BlockSetModule.getInstance().blockInSet(blockSetId, neighbourBlockId)) {
            hasSupport = false;
        }
        final String requiredBlockTypeId = requiredBlockFaceSupport.getBlockTypeId();
        if (hasSupport && requiredBlockTypeId != null && !requiredBlockTypeId.equals(neighbourBlockTypeKey)) {
            hasSupport = false;
        }
        final String fluidId = requiredBlockFaceSupport.getFluidId();
        if (hasSupport && fluidId != null && (neighbourBlockType.getMaterial() != BlockMaterial.Empty || neighbourFluidId == 0 || !fluidId.equals(neighbourFluid.getId()))) {
            hasSupport = false;
        }
        final int tagIndex = requiredBlockFaceSupport.getTagIndex();
        if (tagIndex >= 0 && !BlockType.getAssetMap().getKeysForTag(tagIndex).contains(neighbourBlockTypeKey)) {
            hasSupport = false;
        }
        if (hasSupport && requiredBlockFaceSupport.getFaceType() != null) {
            hasSupport = doesMatchFaceType(neighbourFillerOffset, requiredBlockFaceSupport.getFaceType(), neighbourBlockFace, neighbourBlockType.getSupporting(neighbourRotation));
        }
        if (hasSupport && requiredBlockFaceSupport.getSelfFaceType() != null) {
            hasSupport = doesMatchFaceType(blockFillerOffset, requiredBlockFaceSupport.getSelfFaceType(), blockFace, blockType.getSupporting(neighbourRotation));
        }
        switch (requiredBlockFaceSupport.getMatchSelf()) {
            case IGNORED: {
                break;
            }
            case REQUIRED: {
                if (hasSupport) {
                    hasSupport = blockType.getId().equals(neighbourBlockTypeKey);
                    break;
                }
                break;
            }
            case DISALLOWED: {
                if (hasSupport) {
                    hasSupport = !blockType.getId().equals(neighbourBlockTypeKey);
                    break;
                }
                break;
            }
            default: {
                throw new IllegalArgumentException("Unknown MatchSelf type: " + String.valueOf(requiredBlockFaceSupport.getMatchSelf()));
            }
        }
        return hasSupport;
    }
    
    public static boolean doesMatchFaceType(final Vector3i fillerOffset, @Nonnull final String faceType, final BlockFace blockFace, @Nonnull final Map<BlockFace, BlockFaceSupport[]> supporting) {
        boolean faceHasSupport = false;
        final BlockFaceSupport[] blockFaceSupports = supporting.get(blockFace);
        if (blockFaceSupports != null) {
            for (final BlockFaceSupport blockFaceSupport : blockFaceSupports) {
                if (blockFaceSupport.providesSupportFromFiller(fillerOffset)) {
                    if (faceType.equals(blockFaceSupport.getFaceType())) {
                        faceHasSupport = true;
                        break;
                    }
                }
            }
        }
        return faceHasSupport;
    }
    
    public enum Result
    {
        INVALID, 
        VALID, 
        WAITING_CHUNK;
    }
}
