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

package com.hypixel.hytale.server.core.universe.world.chunk.section;

import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.PaletteTypeEnum;
import io.netty.buffer.Unpooled;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import io.netty.buffer.ByteBufAllocator;
import com.hypixel.hytale.codec.ExtraInfo;
import io.netty.buffer.ByteBuf;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.EmptySectionPalette;
import com.hypixel.hytale.protocol.packets.world.SetFluids;
import com.hypixel.hytale.protocol.CachedPacket;
import java.util.concurrent.CompletableFuture;
import java.lang.ref.SoftReference;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.chunk.section.palette.ISectionPalette;
import java.util.concurrent.locks.StampedLock;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Component;

public class FluidSection implements Component<ChunkStore>
{
    public static final int LEVEL_DATA_SIZE = 16384;
    public static final int VERSION = 0;
    public static final BuilderCodec<FluidSection> CODEC;
    private final StampedLock lock;
    private int x;
    private int y;
    private int z;
    private boolean loaded;
    private ISectionPalette typePalette;
    @Nullable
    private byte[] levelData;
    private int nonZeroLevels;
    @Nonnull
    private IntOpenHashSet changedPositions;
    @Nonnull
    private IntOpenHashSet swapChangedPositions;
    @Nullable
    private transient SoftReference<CompletableFuture<CachedPacket<SetFluids>>> cachedPacket;
    
    public FluidSection() {
        this.lock = new StampedLock();
        this.loaded = false;
        this.typePalette = EmptySectionPalette.INSTANCE;
        this.levelData = null;
        this.nonZeroLevels = 0;
        this.changedPositions = new IntOpenHashSet(0);
        this.swapChangedPositions = new IntOpenHashSet(0);
        this.cachedPacket = null;
    }
    
    public static ComponentType<ChunkStore, FluidSection> getComponentType() {
        return LegacyModule.get().getFluidSectionComponentType();
    }
    
    public void preload(final int x, final int y, final int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    
    public void load(final int x, final int y, final int z) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.loaded = true;
    }
    
    private boolean setFluidRaw(final int x, final int y, final int z, final int fluidId) {
        return this.setFluidRaw(ChunkUtil.indexBlock(x, y, z), fluidId);
    }
    
    private boolean setFluidRaw(final int index, final int fluidId) {
        ISectionPalette.SetResult result = this.typePalette.set(index, fluidId);
        if (result == ISectionPalette.SetResult.REQUIRES_PROMOTE) {
            this.typePalette = this.typePalette.promote();
            result = this.typePalette.set(index, fluidId);
            if (result != ISectionPalette.SetResult.ADDED_OR_REMOVED) {
                throw new IllegalStateException("Promoted fluid section failed to correctly add the new fluid");
            }
        }
        else if (this.typePalette.shouldDemote()) {
            this.typePalette = this.typePalette.demote();
        }
        return result != ISectionPalette.SetResult.UNCHANGED;
    }
    
    public boolean setFluid(final int x, final int y, final int z, @Nonnull final Fluid fluid, final byte level) {
        return this.setFluid(ChunkUtil.indexBlock(x, y, z), Fluid.getAssetMap().getIndex(fluid.getId()), level);
    }
    
    public boolean setFluid(final int x, final int y, final int z, final int fluidId, final byte level) {
        return this.setFluid(ChunkUtil.indexBlock(x, y, z), fluidId, level);
    }
    
    public boolean setFluid(final int index, @Nonnull final Fluid fluid, final byte level) {
        return this.setFluid(index, Fluid.getAssetMap().getIndex(fluid.getId()), level);
    }
    
    public boolean setFluid(final int index, int fluidId, byte level) {
        level &= 0xF;
        if (level == 0) {
            fluidId = 0;
        }
        if (fluidId == 0) {
            level = 0;
        }
        final long stamp = this.lock.writeLock();
        try {
            boolean changed = this.setFluidRaw(index, fluidId);
            changed |= this.setFluidLevel(index, level);
            if (changed && this.loaded) {
                this.cachedPacket = null;
                this.changedPositions.add(index);
            }
            return changed;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    private boolean setFluidRaw(final int x, final int y, final int z, @Nonnull final Fluid fluid) {
        return this.setFluidRaw(ChunkUtil.indexBlock(x, y, z), fluid);
    }
    
    private boolean setFluidRaw(final int index, @Nonnull final Fluid fluid) {
        final IndexedLookupTableAssetMap<String, Fluid> assetMap = Fluid.getAssetMap();
        return this.setFluidRaw(index, assetMap.getIndex(fluid.getId()));
    }
    
    public int getFluidId(final int x, final int y, final int z) {
        return this.getFluidId(ChunkUtil.indexBlock(x, y, z));
    }
    
    public int getFluidId(final int index) {
        long stamp = this.lock.tryOptimisticRead();
        int currentId = this.typePalette.get(index);
        if (!this.lock.validate(stamp)) {
            stamp = this.lock.readLock();
            try {
                currentId = this.typePalette.get(index);
            }
            finally {
                this.lock.unlockRead(stamp);
            }
        }
        return currentId;
    }
    
    @Nullable
    public Fluid getFluid(final int x, final int y, final int z) {
        return this.getFluid(ChunkUtil.indexBlock(x, y, z));
    }
    
    @Nullable
    public Fluid getFluid(final int index) {
        final IndexedLookupTableAssetMap<String, Fluid> assetMap = Fluid.getAssetMap();
        return assetMap.getAsset(this.getFluidId(index));
    }
    
    private boolean setFluidLevel(final int x, final int y, final int z, final byte level) {
        return this.setFluidLevel(ChunkUtil.indexBlock(x, y, z), level);
    }
    
    private boolean setFluidLevel(final int index, byte level) {
        level &= 0xF;
        if (this.levelData == null) {
            if (level == 0) {
                return false;
            }
            this.levelData = new byte[16384];
        }
        final int byteIndex = index >> 1;
        final byte byteValue = this.levelData[byteIndex];
        final int value = byteValue >> (index & 0x1) * 4 & 0xF;
        if (value == level) {
            return false;
        }
        if (value == 0) {
            ++this.nonZeroLevels;
        }
        else if (level == 0) {
            --this.nonZeroLevels;
            if (this.nonZeroLevels <= 0) {
                this.levelData = null;
                return true;
            }
        }
        if ((index & 0x1) == 0x0) {
            this.levelData[byteIndex] = (byte)((byteValue & 0xF0) | level);
        }
        else {
            this.levelData[byteIndex] = (byte)((byteValue & 0xF) | level << 4);
        }
        return true;
    }
    
    public byte getFluidLevel(final int x, final int y, final int z) {
        return this.getFluidLevel(ChunkUtil.indexBlock(x, y, z));
    }
    
    public byte getFluidLevel(final int index) {
        long stamp = this.lock.tryOptimisticRead();
        final byte[] localData = this.levelData;
        byte result = 0;
        if (localData != null) {
            final int byteIndex = index >> 1;
            final byte byteValue = localData[byteIndex];
            result = (byte)(byteValue >> (index & 0x1) * 4 & 0xF);
        }
        if (!this.lock.validate(stamp)) {
            stamp = this.lock.readLock();
            try {
                if (this.levelData == null) {
                    return 0;
                }
                final int byteIndex = index >> 1;
                final byte byteValue = this.levelData[byteIndex];
                return (byte)(byteValue >> (index & 0x1) * 4 & 0xF);
            }
            finally {
                this.lock.unlockRead(stamp);
            }
        }
        return result;
    }
    
    public int getX() {
        return this.x;
    }
    
    public int getY() {
        return this.y;
    }
    
    public int getZ() {
        return this.z;
    }
    
    @Nonnull
    public IntOpenHashSet getAndClearChangedPositions() {
        final long stamp = this.lock.writeLock();
        try {
            this.swapChangedPositions.clear();
            final IntOpenHashSet tmp = this.changedPositions;
            this.changedPositions = this.swapChangedPositions;
            return this.swapChangedPositions = tmp;
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> clone() {
        return this;
    }
    
    private void serializeForPacket(@Nonnull final ByteBuf buf) {
        final long stamp = this.lock.readLock();
        try {
            buf.writeByte(this.typePalette.getPaletteType().ordinal());
            this.typePalette.serializeForPacket(buf);
            if (this.levelData != null) {
                buf.writeBoolean(true);
                buf.writeBytes(this.levelData);
            }
            else {
                buf.writeBoolean(false);
            }
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    private byte[] serialize(final ExtraInfo extraInfo) {
        final ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        final long stamp = this.lock.readLock();
        try {
            buf.writeByte(this.typePalette.getPaletteType().ordinal());
            this.typePalette.serialize(Fluid.KEY_SERIALIZER, buf);
            if (this.levelData != null) {
                buf.writeBoolean(true);
                buf.writeBytes(this.levelData);
            }
            else {
                buf.writeBoolean(false);
            }
            return ByteBufUtil.getBytesRelease(buf);
        }
        catch (final Throwable e) {
            buf.release();
            throw SneakyThrow.sneakyThrow(e);
        }
        finally {
            this.lock.unlockRead(stamp);
        }
    }
    
    private void deserialize(@Nonnull final byte[] bytes, final ExtraInfo extraInfo) {
        final ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        final PaletteTypeEnum type = PaletteTypeEnum.get(buf.readByte());
        (this.typePalette = (ISectionPalette)type.getConstructor().get()).deserialize(Fluid.KEY_DESERIALIZER, buf, 0);
        if (buf.readBoolean()) {
            buf.readBytes(this.levelData = new byte[16384]);
            this.nonZeroLevels = 0;
            for (int i = 0; i < 16384; ++i) {
                final byte v = this.levelData[i];
                if ((v & 0xF) != 0x0) {
                    ++this.nonZeroLevels;
                }
                if ((v & 0xF0) != 0x0) {
                    ++this.nonZeroLevels;
                }
            }
        }
        else {
            this.levelData = null;
        }
    }
    
    @Nonnull
    public CompletableFuture<CachedPacket<SetFluids>> getCachedPacket() {
        final SoftReference<CompletableFuture<CachedPacket<SetFluids>>> ref = this.cachedPacket;
        CompletableFuture<CachedPacket<SetFluids>> future = (ref != null) ? ref.get() : null;
        if (future != null) {
            return future;
        }
        future = CompletableFuture.supplyAsync(() -> {
            final ByteBuf buf = Unpooled.buffer(65536);
            this.serializeForPacket(buf);
            final byte[] data = ByteBufUtil.getBytesRelease(buf);
            final SetFluids packet = new SetFluids(this.x, this.y, this.z, data);
            return CachedPacket.cache(packet);
        });
        this.cachedPacket = new SoftReference<CompletableFuture<CachedPacket<SetFluids>>>(future);
        return future;
    }
    
    public boolean isEmpty() {
        return this.typePalette.isSolid(0) && this.nonZeroLevels == 0;
    }
    
    static {
        // 
        // This method could not be decompiled.
        // 
        // Original Bytecode:
        // 
        //     2: invokedynamic   BootstrapMethod #1, 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: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.versioned:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    13: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    16: iconst_0       
        //    17: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.codecVersion:(I)Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    20: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    23: new             Lcom/hypixel/hytale/codec/KeyedCodec;
        //    26: dup            
        //    27: ldc_w           "Data"
        //    30: getstatic       com/hypixel/hytale/codec/Codec.BYTE_ARRAY:Lcom/hypixel/hytale/codec/Codec;
        //    33: invokespecial   com/hypixel/hytale/codec/KeyedCodec.<init>:(Ljava/lang/String;Lcom/hypixel/hytale/codec/Codec;)V
        //    36: invokedynamic   BootstrapMethod #2, accept:()Lcom/hypixel/hytale/function/consumer/TriConsumer;
        //    41: invokedynamic   BootstrapMethod #3, apply:()Ljava/util/function/BiFunction;
        //    46: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.append:(Lcom/hypixel/hytale/codec/KeyedCodec;Lcom/hypixel/hytale/function/consumer/TriConsumer;Ljava/util/function/BiFunction;)Lcom/hypixel/hytale/codec/builder/BuilderField$FieldBuilder;
        //    49: invokevirtual   com/hypixel/hytale/codec/builder/BuilderField$FieldBuilder.add:()Lcom/hypixel/hytale/codec/builder/BuilderCodec$BuilderBase;
        //    52: checkcast       Lcom/hypixel/hytale/codec/builder/BuilderCodec$Builder;
        //    55: invokevirtual   com/hypixel/hytale/codec/builder/BuilderCodec$Builder.build:()Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    58: putstatic       com/hypixel/hytale/server/core/universe/world/chunk/section/FluidSection.CODEC:Lcom/hypixel/hytale/codec/builder/BuilderCodec;
        //    61: 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.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: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.");
    }
}
