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

package com.hypixel.hytale.server.core.util;

import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.function.predicate.TriIntPredicate;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.function.consumer.TriIntConsumer;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;

public class FillerBlockUtil
{
    public static final float THRESHOLD = 0.0f;
    public static final int NO_FILLER = 0;
    private static final int BITS_PER_AXIS = 5;
    private static final int MASK = 31;
    private static final int INVERT = -32;
    
    public static void forEachFillerBlock(@Nonnull final BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull final TriIntConsumer consumer) {
        forEachFillerBlock(0.0f, blockBoundingBoxes, consumer);
    }
    
    public static void forEachFillerBlock(final float threshold, @Nonnull final BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull final TriIntConsumer consumer) {
        if (threshold < 0.0f || threshold >= 1.0f) {
            throw new IllegalArgumentException("Threshold must be between 0 and 1");
        }
        final Box boundingBox = blockBoundingBoxes.getBoundingBox();
        int minX = (int)boundingBox.min.x;
        int minY = (int)boundingBox.min.y;
        int minZ = (int)boundingBox.min.z;
        if (minX - boundingBox.min.x > threshold) {
            --minX;
        }
        if (minY - boundingBox.min.y > threshold) {
            --minY;
        }
        if (minZ - boundingBox.min.z > threshold) {
            --minZ;
        }
        int maxX = (int)boundingBox.max.x;
        int maxY = (int)boundingBox.max.y;
        int maxZ = (int)boundingBox.max.z;
        if (boundingBox.max.x - maxX > threshold) {
            ++maxX;
        }
        if (boundingBox.max.y - maxY > threshold) {
            ++maxY;
        }
        if (boundingBox.max.z - maxZ > threshold) {
            ++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);
        for (int x = 0; x < blockWidth; ++x) {
            for (int y = 0; y < blockHeight; ++y) {
                for (int z = 0; z < blockDepth; ++z) {
                    consumer.accept(minX + x, minY + y, minZ + z);
                }
            }
        }
    }
    
    public static boolean testFillerBlocks(@Nonnull final BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull final TriIntPredicate predicate) {
        return testFillerBlocks(0.0f, blockBoundingBoxes, predicate);
    }
    
    public static boolean testFillerBlocks(final float threshold, @Nonnull final BlockBoundingBoxes.RotatedVariantBoxes blockBoundingBoxes, @Nonnull final TriIntPredicate predicate) {
        if (threshold < 0.0f || threshold >= 1.0f) {
            throw new IllegalArgumentException("Threshold must be between 0 and 1");
        }
        final Box boundingBox = blockBoundingBoxes.getBoundingBox();
        int minX = (int)boundingBox.min.x;
        int minY = (int)boundingBox.min.y;
        int minZ = (int)boundingBox.min.z;
        if (minX - boundingBox.min.x > threshold) {
            --minX;
        }
        if (minY - boundingBox.min.y > threshold) {
            --minY;
        }
        if (minZ - boundingBox.min.z > threshold) {
            --minZ;
        }
        int maxX = (int)boundingBox.max.x;
        int maxY = (int)boundingBox.max.y;
        int maxZ = (int)boundingBox.max.z;
        if (boundingBox.max.x - maxX > threshold) {
            ++maxX;
        }
        if (boundingBox.max.y - maxY > threshold) {
            ++maxY;
        }
        if (boundingBox.max.z - maxZ > threshold) {
            ++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);
        for (int x = 0; x < blockWidth; ++x) {
            for (int y = 0; y < blockHeight; ++y) {
                for (int z = 0; z < blockDepth; ++z) {
                    if (!predicate.test(minX + x, minY + y, minZ + z)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    public static <A, B> ValidationResult validateBlock(final int x, final int y, final int z, final int blockId, final int rotation, final int filler, final A a, final B b, @Nonnull final FillerFetcher<A, B> fetcher) {
        if (blockId == 0) {
            return ValidationResult.OK;
        }
        final BlockTypeAssetMap<String, BlockType> blockTypeAssetMap = BlockType.getAssetMap();
        final BlockType blockType = blockTypeAssetMap.getAsset(blockId);
        if (blockType == null) {
            return ValidationResult.OK;
        }
        final String id = blockType.getId();
        final IndexedLookupTableAssetMap<String, BlockBoundingBoxes> hitboxAssetMap = BlockBoundingBoxes.getAssetMap();
        if (filler != 0) {
            final int fillerX = unpackX(filler);
            final int fillerY = unpackY(filler);
            final int fillerZ = unpackZ(filler);
            final int baseBlockId = fetcher.getBlock(a, b, x - fillerX, y - fillerY, z - fillerZ);
            final BlockType baseBlock = blockTypeAssetMap.getAsset(baseBlockId);
            if (baseBlock == null) {
                return ValidationResult.INVALID_BLOCK;
            }
            final String baseId = baseBlock.getId();
            final BlockBoundingBoxes hitbox = hitboxAssetMap.getAsset(baseBlock.getHitboxTypeIndex());
            if (hitbox == null) {
                return ValidationResult.OK;
            }
            final int baseFiller = fetcher.getFiller(a, b, x - fillerX, y - fillerY, z - fillerZ);
            final int baseRotation = fetcher.getRotationIndex(a, b, x - fillerX, y - fillerY, z - fillerZ);
            if (baseFiller != 0 || baseRotation != rotation || !id.equals(baseId) || !hitbox.get(baseRotation).getBoundingBox().containsBlock(fillerX, fillerY, fillerZ)) {
                return ValidationResult.INVALID_BLOCK;
            }
            return ValidationResult.OK;
        }
        else {
            final BlockBoundingBoxes hitbox2 = hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex());
            if (hitbox2 == null || !hitbox2.protrudesUnitBox()) {
                return ValidationResult.OK;
            }
            final boolean result = testFillerBlocks(hitbox2.get(rotation), (x1, y1, z1) -> {
                if (x1 == 0 && y1 == 0 && z1 == 0) {
                    return true;
                }
                else {
                    final int worldX = x + x1;
                    final int worldY = y + y1;
                    final int worldZ = z + z1;
                    final int fillerBlockId = fetcher.getBlock(a, b, worldX, worldY, worldZ);
                    final BlockType fillerBlock = blockTypeAssetMap.getAsset(fillerBlockId);
                    final int expectedFiller = pack(x1, y1, z1);
                    if (fetcher.getFiller(a, b, worldX, worldY, worldZ) != expectedFiller) {
                        return false;
                    }
                    else if (fetcher.getRotationIndex(a, b, worldX, worldY, worldZ) != rotation) {
                        return false;
                    }
                    else if (fillerBlock == null) {
                        return false;
                    }
                    else {
                        final String blockTypeKey = fillerBlock.getId();
                        return blockTypeKey.equals(id);
                    }
                }
            });
            return result ? ValidationResult.OK : ValidationResult.INVALID_FILLER;
        }
    }
    
    public static int pack(final int x, final int y, final int z) {
        return (x & 0x1F) | (z & 0x1F) << 5 | (y & 0x1F) << 10;
    }
    
    public static int unpackX(final int val) {
        int result = val & 0x1F;
        if ((result & 0x10) != 0x0) {
            result |= 0xFFFFFFE0;
        }
        return result;
    }
    
    public static int unpackY(final int val) {
        int result = val >> 10 & 0x1F;
        if ((result & 0x10) != 0x0) {
            result |= 0xFFFFFFE0;
        }
        return result;
    }
    
    public static int unpackZ(final int val) {
        int result = val >> 5 & 0x1F;
        if ((result & 0x10) != 0x0) {
            result |= 0xFFFFFFE0;
        }
        return result;
    }
    
    public enum ValidationResult
    {
        OK, 
        INVALID_BLOCK, 
        INVALID_FILLER;
    }
    
    public interface FillerFetcher<A, B>
    {
        int getBlock(final A p0, final B p1, final int p2, final int p3, final int p4);
        
        int getFiller(final A p0, final B p1, final int p2, final int p3, final int p4);
        
        int getRotationIndex(final A p0, final B p1, final int p2, final int p3, final int p4);
    }
}
