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

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

import com.hypixel.hytale.protocol.ConnectedBlockRuleSetType;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlocksUtil;
import java.util.Optional;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import it.unimi.dsi.fastutil.objects.ObjectIntImmutablePair;
import com.hypixel.hytale.math.util.ChunkUtil;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.World;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.connectedblocks.ConnectedBlockRuleSet;

public class StairConnectedBlockRuleSet extends ConnectedBlockRuleSet implements StairLikeConnectedBlockRuleSet
{
    public static final String DEFAULT_MATERIAL_NAME = "Stair";
    public static final BuilderCodec<StairConnectedBlockRuleSet> CODEC;
    private ConnectedBlockOutput straight;
    private ConnectedBlockOutput cornerLeft;
    private ConnectedBlockOutput cornerRight;
    private ConnectedBlockOutput invertedCornerLeft;
    private ConnectedBlockOutput invertedCornerRight;
    private String materialName;
    private Int2ObjectMap<StairType> blockIdToStairType;
    protected Object2IntMap<StairType> stairTypeToBlockId;
    
    public StairConnectedBlockRuleSet() {
        this.materialName = "Stair";
    }
    
    @Override
    public boolean onlyUpdateOnPlacement() {
        return false;
    }
    
    @Override
    public void updateCachedBlockTypes(final BlockType baseBlockType, final BlockTypeAssetMap<String, BlockType> assetMap) {
        final int baseIndex = assetMap.getIndex(baseBlockType.getId());
        final Int2ObjectMap<StairType> blockIdToStairType = new Int2ObjectOpenHashMap<StairType>();
        final Object2IntMap<StairType> stairTypeToBlockId = new Object2IntOpenHashMap<StairType>();
        stairTypeToBlockId.defaultReturnValue(baseIndex);
        final ConnectedBlockOutput[] outputs = { this.straight, this.cornerLeft, this.cornerRight, this.invertedCornerLeft, this.invertedCornerRight };
        final StairType[] stairTypes = StairType.VALUES;
        for (int i = 0; i < outputs.length; ++i) {
            final ConnectedBlockOutput output = outputs[i];
            if (output != null) {
                final int index = output.resolve(baseBlockType, assetMap);
                if (index != -1) {
                    blockIdToStairType.put(index, stairTypes[i]);
                    stairTypeToBlockId.put(stairTypes[i], index);
                }
            }
        }
        this.blockIdToStairType = blockIdToStairType;
        this.stairTypeToBlockId = stairTypeToBlockId;
    }
    
    @Nullable
    protected static ObjectIntPair<StairType> getStairData(final World world, final Vector3i coordinate, @Nullable final String requiredMaterialName) {
        final WorldChunk chunk = world.getChunkIfLoaded(ChunkUtil.indexChunkFromBlock(coordinate.x, coordinate.z));
        if (chunk == null) {
            return null;
        }
        final int filler = chunk.getFiller(coordinate.x, coordinate.y, coordinate.z);
        if (filler != 0) {
            return null;
        }
        final int blockId = chunk.getBlock(coordinate);
        final BlockType blockType = BlockType.getAssetMap().getAsset(blockId);
        if (blockType == null) {
            return null;
        }
        final ConnectedBlockRuleSet ruleSet = blockType.getConnectedBlockRuleSet();
        if (!(ruleSet instanceof StairLikeConnectedBlockRuleSet)) {
            return null;
        }
        final StairLikeConnectedBlockRuleSet stairRuleSet = (StairLikeConnectedBlockRuleSet)ruleSet;
        final String otherMaterialName = stairRuleSet.getMaterialName();
        if (requiredMaterialName != null && otherMaterialName != null && !requiredMaterialName.equals(otherMaterialName)) {
            return null;
        }
        final StairType stairType = stairRuleSet.getStairType(blockId);
        if (stairType == null) {
            return null;
        }
        final int rotation = chunk.getRotationIndex(coordinate.x, coordinate.y, coordinate.z);
        return new ObjectIntImmutablePair<StairType>(stairType, rotation);
    }
    
    @Override
    public StairType getStairType(final int blockId) {
        return this.blockIdToStairType.get(blockId);
    }
    
    @Nullable
    @Override
    public String getMaterialName() {
        return this.materialName;
    }
    
    public BlockType getStairBlockType(final StairType stairType) {
        if (this.stairTypeToBlockId == null) {
            return null;
        }
        final int resultingBlockTypeIndex = this.stairTypeToBlockId.getInt(stairType);
        return BlockType.getAssetMap().getAsset(resultingBlockTypeIndex);
    }
    
    @Override
    public Optional<ConnectedBlocksUtil.ConnectedBlockResult> getConnectedBlockType(final World world, final Vector3i coordinate, final BlockType currentBlockType, final int rotation, final Vector3i placementNormal, final boolean isPlacement) {
        final RotationTuple currentRotation = RotationTuple.get(rotation);
        Rotation currentYaw = currentRotation.yaw();
        final Rotation currentPitch = currentRotation.pitch();
        final boolean upsideDown = currentPitch != Rotation.None;
        if (upsideDown) {
            currentYaw = currentYaw.flip();
        }
        final Vector3i mutablePos = new Vector3i();
        StairType resultingStair = StairType.STRAIGHT;
        final StairConnection frontConnection = getInvertedCornerConnection(world, this, coordinate, mutablePos, currentYaw, upsideDown);
        if (frontConnection != null) {
            resultingStair = frontConnection.getStairType(true);
        }
        final StairConnection backConnection = getCornerConnection(world, this, coordinate, mutablePos, rotation, currentYaw, upsideDown, 1);
        if (backConnection != null) {
            resultingStair = backConnection.getStairType(false);
        }
        if (upsideDown) {
            resultingStair = switch (resultingStair.ordinal()) {
                case 1 -> StairType.CORNER_RIGHT;
                case 2 -> StairType.CORNER_LEFT;
                case 3 -> StairType.INVERTED_CORNER_RIGHT;
                case 4 -> StairType.INVERTED_CORNER_LEFT;
                default -> resultingStair;
            };
        }
        final int resultingBlockTypeIndex = this.stairTypeToBlockId.getInt(resultingStair);
        final BlockType resultingBlockType = BlockType.getAssetMap().getAsset(resultingBlockTypeIndex);
        if (resultingBlockType == null) {
            return Optional.empty();
        }
        final String resultingBlockTypeKey = resultingBlockType.getId();
        return Optional.of(new ConnectedBlocksUtil.ConnectedBlockResult(resultingBlockTypeKey, rotation));
    }
    
    protected static StairConnection getCornerConnection(final World world, final StairLikeConnectedBlockRuleSet currentRuleSet, final Vector3i coordinate, final Vector3i mutablePos, final int rotation, final Rotation currentYaw, final boolean upsideDown, final int width) {
        StairConnection backConnection = null;
        mutablePos.assign(Vector3i.NORTH).scale(width);
        currentYaw.rotateY(mutablePos, mutablePos);
        mutablePos.add(coordinate.x, coordinate.y, coordinate.z);
        ObjectIntPair<StairType> backStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName());
        if (backStair == null && width > 1) {
            mutablePos.assign(Vector3i.NORTH).scale(width + 1);
            currentYaw.rotateY(mutablePos, mutablePos);
            mutablePos.add(coordinate.x, coordinate.y, coordinate.z);
            backStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName());
            if (backStair != null && backStair.first() == StairType.STRAIGHT) {
                backStair = null;
            }
        }
        if (backStair != null) {
            final StairType otherStairType = backStair.left();
            final RotationTuple otherStairRotation = RotationTuple.get(backStair.rightInt());
            Rotation otherYaw = otherStairRotation.yaw();
            final boolean otherUpsideDown = otherStairRotation.pitch() != Rotation.None;
            if (otherUpsideDown) {
                otherYaw = otherYaw.flip();
            }
            if (canConnectTo(currentYaw, otherYaw, upsideDown, otherUpsideDown)) {
                mutablePos.assign(Vector3i.SOUTH);
                otherYaw.rotateY(mutablePos, mutablePos);
                mutablePos.add(coordinate.x, coordinate.y, coordinate.z);
                final ObjectIntPair<StairType> sidewaysStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName());
                if (sidewaysStair == null || sidewaysStair.rightInt() != rotation) {
                    backConnection = getConnection(currentYaw, otherYaw, otherStairType, false);
                }
            }
        }
        return backConnection;
    }
    
    protected static StairConnection getInvertedCornerConnection(final World world, final StairLikeConnectedBlockRuleSet currentRuleSet, final Vector3i coordinate, final Vector3i mutablePos, final Rotation currentYaw, final boolean upsideDown) {
        StairConnection frontConnection = null;
        mutablePos.assign(Vector3i.SOUTH);
        currentYaw.rotateY(mutablePos, mutablePos);
        mutablePos.add(coordinate.x, coordinate.y, coordinate.z);
        final ObjectIntPair<StairType> frontStair = getStairData(world, mutablePos, currentRuleSet.getMaterialName());
        if (frontStair != null) {
            final StairType otherStairType = frontStair.left();
            final RotationTuple otherStairRotation = RotationTuple.get(frontStair.rightInt());
            Rotation otherYaw = otherStairRotation.yaw();
            final boolean otherUpsideDown = otherStairRotation.pitch() != Rotation.None;
            if (otherUpsideDown) {
                otherYaw = otherYaw.flip();
            }
            if (canConnectTo(currentYaw, otherYaw, upsideDown, otherUpsideDown)) {
                frontConnection = getConnection(currentYaw, otherYaw, otherStairType, true);
            }
        }
        return frontConnection;
    }
    
    private static boolean canConnectTo(final Rotation currentYaw, final Rotation otherYaw, final boolean upsideDown, final boolean otherUpsideDown) {
        return otherUpsideDown == upsideDown && otherYaw != currentYaw && otherYaw.add(Rotation.OneEighty) != currentYaw;
    }
    
    private static StairConnection getConnection(final Rotation currentYaw, final Rotation otherYaw, final StairType otherStairType, final boolean inverted) {
        if (otherYaw == currentYaw.add(Rotation.Ninety) && otherStairType != StairType.invertedCorner(inverted) && otherStairType != StairType.corner(!inverted)) {
            return StairConnection.CORNER_LEFT;
        }
        if (otherYaw == currentYaw.subtract(Rotation.Ninety) && otherStairType != StairType.invertedCorner(!inverted) && otherStairType != StairType.corner(inverted)) {
            return StairConnection.CORNER_RIGHT;
        }
        return null;
    }
    
    @Nullable
    @Override
    public com.hypixel.hytale.protocol.ConnectedBlockRuleSet toPacket(final BlockTypeAssetMap<String, BlockType> assetMap) {
        final com.hypixel.hytale.protocol.ConnectedBlockRuleSet packet = new com.hypixel.hytale.protocol.ConnectedBlockRuleSet();
        packet.type = ConnectedBlockRuleSetType.Stair;
        packet.stair = this.toProtocol(assetMap);
        return packet;
    }
    
    public com.hypixel.hytale.protocol.StairConnectedBlockRuleSet toProtocol(final BlockTypeAssetMap<String, BlockType> assetMap) {
        final com.hypixel.hytale.protocol.StairConnectedBlockRuleSet stairPacket = new com.hypixel.hytale.protocol.StairConnectedBlockRuleSet();
        stairPacket.straightBlockId = this.getBlockIdForStairType(StairType.STRAIGHT, assetMap);
        stairPacket.cornerLeftBlockId = this.getBlockIdForStairType(StairType.CORNER_LEFT, assetMap);
        stairPacket.cornerRightBlockId = this.getBlockIdForStairType(StairType.CORNER_RIGHT, assetMap);
        stairPacket.invertedCornerLeftBlockId = this.getBlockIdForStairType(StairType.INVERTED_CORNER_LEFT, assetMap);
        stairPacket.invertedCornerRightBlockId = this.getBlockIdForStairType(StairType.INVERTED_CORNER_RIGHT, assetMap);
        stairPacket.materialName = this.materialName;
        return stairPacket;
    }
    
    private int getBlockIdForStairType(final StairType stairType, final BlockTypeAssetMap<String, BlockType> assetMap) {
        final BlockType blockType = this.getStairBlockType(stairType);
        if (blockType == null) {
            return -1;
        }
        return assetMap.getIndex(blockType.getId());
    }
    
    static {
        // 
        // This method could not be decompiled.
        // 
        // Original Bytecode:
        // 
        //     2: invokedynamic   BootstrapMethod #0, get:()Ljava/util/function/Supplier;
        //     7: invokestatic    com/hypixel/hytale/codec/builder/BuilderCodec.builder:(Ljava/lang/Class;Ljava/util/function/Supplier;)Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    10: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //    13: dup            
        //    14: ldc_w           "Straight"
        //    17: getstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    20: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //    23: invokedynamic   BootstrapMethod #1, accept:()Ljava/util/function/BiConsumer;
        //    28: invokedynamic   BootstrapMethod #2, apply:()Ljava/util/function/Function;
        //    33: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    36: invokestatic    com/hypixel/hytale/codec/validation/Validators.nonNull:()Lcom/hypixel/hytale/codec/validation/Validator;
        //    39: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.addValidator:(Lcom/hypixel/hytale/codec/validation/Validator;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    42: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    45: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    48: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //    51: dup            
        //    52: ldc_w           "Corner_Left"
        //    55: getstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    58: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //    61: invokedynamic   BootstrapMethod #3, accept:()Ljava/util/function/BiConsumer;
        //    66: invokedynamic   BootstrapMethod #4, apply:()Ljava/util/function/Function;
        //    71: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    74: invokestatic    com/hypixel/hytale/codec/validation/Validators.nonNull:()Lcom/hypixel/hytale/codec/validation/Validator;
        //    77: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.addValidator:(Lcom/hypixel/hytale/codec/validation/Validator;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    80: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    83: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    86: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //    89: dup            
        //    90: ldc_w           "Corner_Right"
        //    93: getstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    96: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //    99: invokedynamic   BootstrapMethod #5, accept:()Ljava/util/function/BiConsumer;
        //   104: invokedynamic   BootstrapMethod #6, apply:()Ljava/util/function/Function;
        //   109: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //   112: invokestatic    com/hypixel/hytale/codec/validation/Validators.nonNull:()Lcom/hypixel/hytale/codec/validation/Validator;
        //   115: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.addValidator:(Lcom/hypixel/hytale/codec/validation/Validator;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //   118: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //   121: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //   124: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //   127: dup            
        //   128: ldc_w           "Inverted_Corner_Left"
        //   131: getstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //   134: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //   137: invokedynamic   BootstrapMethod #7, accept:()Ljava/util/function/BiConsumer;
        //   142: invokedynamic   BootstrapMethod #8, apply:()Ljava/util/function/Function;
        //   147: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //   150: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //   153: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //   156: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //   159: dup            
        //   160: ldc_w           "Inverted_Corner_Right"
        //   163: getstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/ConnectedBlockOutput.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //   166: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //   169: invokedynamic   BootstrapMethod #9, accept:()Ljava/util/function/BiConsumer;
        //   174: invokedynamic   BootstrapMethod #10, apply:()Ljava/util/function/Function;
        //   179: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //   182: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //   185: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //   188: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //   191: dup            
        //   192: ldc_w           "MaterialName"
        //   195: getstatic       com/hypixel/hytale/codec/Codec.STRING:Lcom/hypixel/hytale/codec/codecs/simple/StringCodec;
        //   198: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //   201: invokedynamic   BootstrapMethod #11, accept:()Ljava/util/function/BiConsumer;
        //   206: invokedynamic   BootstrapMethod #12, apply:()Ljava/util/function/Function;
        //   211: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Ljava/util/function/BiConsumer;Ljava/util/function/Function;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //   214: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //   217: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //   220: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.build:()Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //   223: putstatic       com/hypixel/hytale/server/core/universe/world/connectedblocks/builtin/StairConnectedBlockRuleSet.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //   226: return         
        // 
        // The error that occurred was:
        // 
        // java.lang.UnsupportedOperationException: The requested operation is not supported.
        //     at com.strobel.util.ContractUtils.unsupported(ContractUtils.java:27)
        //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:284)
        //     at com.strobel.assembler.metadata.TypeReference.getRawType(TypeReference.java:279)
        //     at com.strobel.assembler.metadata.TypeReference.makeGenericType(TypeReference.java:154)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:225)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
        //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:211)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitParameterizedType(TypeSubstitutionVisitor.java:25)
        //     at com.strobel.assembler.metadata.ParameterizedType.accept(ParameterizedType.java:103)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visit(TypeSubstitutionVisitor.java:40)
        //     at com.strobel.assembler.metadata.TypeSubstitutionVisitor.visitMethod(TypeSubstitutionVisitor.java:314)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2611)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1510)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:790)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferCall(TypeAnalysis.java:2689)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1040)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:782)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:778)
        //     at com.strobel.decompiler.ast.TypeAnalysis.doInferTypeForExpression(TypeAnalysis.java:1083)
        //     at com.strobel.decompiler.ast.TypeAnalysis.inferTypeForExpression(TypeAnalysis.java:815)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:684)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:667)
        //     at com.strobel.decompiler.ast.TypeAnalysis.runInference(TypeAnalysis.java:373)
        //     at com.strobel.decompiler.ast.TypeAnalysis.run(TypeAnalysis.java:95)
        //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:344)
        //     at com.strobel.decompiler.ast.AstOptimizer.optimize(AstOptimizer.java:42)
        //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:206)
        //     at com.strobel.decompiler.languages.java.ast.AstMethodBodyBuilder.createMethodBody(AstMethodBodyBuilder.java:93)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethodBody(AstBuilder.java:868)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createMethod(AstBuilder.java:761)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addTypeMembers(AstBuilder.java:638)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeCore(AstBuilder.java:605)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createTypeNoCache(AstBuilder.java:195)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.createType(AstBuilder.java:162)
        //     at com.strobel.decompiler.languages.java.ast.AstBuilder.addType(AstBuilder.java:137)
        //     at com.strobel.decompiler.languages.java.JavaLanguage.buildAst(JavaLanguage.java:71)
        //     at com.strobel.decompiler.languages.java.JavaLanguage.decompileType(JavaLanguage.java:59)
        //     at com.strobel.decompiler.DecompilerDriver.decompileType(DecompilerDriver.java:333)
        //     at com.strobel.decompiler.DecompilerDriver.decompileJar(DecompilerDriver.java:254)
        //     at com.strobel.decompiler.DecompilerDriver.main(DecompilerDriver.java:129)
        // 
        throw new IllegalStateException("An error occurred while decompiling this method.");
    }
    
    protected enum StairConnection
    {
        CORNER_LEFT, 
        CORNER_RIGHT;
        
        public StairType getStairType(final boolean inverted) {
            return switch (this.ordinal()) {
                default -> throw new MatchException(null, null);
                case 0 -> inverted ? StairType.INVERTED_CORNER_LEFT : StairType.CORNER_LEFT;
                case 1 -> inverted ? StairType.INVERTED_CORNER_RIGHT : StairType.CORNER_RIGHT;
            };
        }
    }
    
    public enum StairType
    {
        STRAIGHT, 
        CORNER_LEFT, 
        CORNER_RIGHT, 
        INVERTED_CORNER_LEFT, 
        INVERTED_CORNER_RIGHT;
        
        private static final StairType[] VALUES;
        
        public static StairType corner(final boolean right) {
            return right ? StairType.CORNER_RIGHT : StairType.CORNER_LEFT;
        }
        
        public static StairType invertedCorner(final boolean right) {
            return right ? StairType.INVERTED_CORNER_RIGHT : StairType.INVERTED_CORNER_LEFT;
        }
        
        public boolean isCorner() {
            return this == StairType.CORNER_LEFT || this == StairType.CORNER_RIGHT;
        }
        
        public boolean isInvertedCorner() {
            return this == StairType.INVERTED_CORNER_LEFT || this == StairType.INVERTED_CORNER_RIGHT;
        }
        
        public boolean isLeft() {
            return this == StairType.CORNER_LEFT || this == StairType.INVERTED_CORNER_LEFT;
        }
        
        public boolean isRight() {
            return this == StairType.CORNER_RIGHT || this == StairType.INVERTED_CORNER_RIGHT;
        }
        
        static {
            VALUES = values();
        }
    }
}
