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

package com.hypixel.hytale.server.core.prefab.selection.buffer;

import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import io.netty.buffer.Unpooled;
import com.hypixel.hytale.server.core.util.io.ByteBufUtil;
import javax.annotation.Nullable;
import org.bson.BsonDocument;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import java.util.Map;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer;
import com.hypixel.hytale.server.core.util.BsonUtil;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBufferBlockEntry;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.function.Function;
import java.util.Objects;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration;
import com.hypixel.hytale.math.block.BlockUtil;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import io.netty.buffer.ByteBuf;

public class BinaryPrefabBufferCodec implements PrefabBufferCodec<ByteBuf>
{
    public static final BinaryPrefabBufferCodec INSTANCE;
    public static final int VERSION = 21;
    private static final int MASK_CHANCE = 1;
    private static final int MASK_COMPONENTS = 2;
    private static final int MASK_FLUID = 4;
    private static final int MASK_SUPPORT_VALUE = 8;
    private static final int MASK_FILLER = 16;
    private static final int MASK_ROTATION = 32;
    
    @Nonnull
    @Override
    public PrefabBuffer deserialize(final Path path, @Nonnull final ByteBuf buffer) {
        final int version = buffer.readUnsignedShort();
        if (version == 18553) {
            throw new UpdateBinaryPrefabException("Old prefab format!");
        }
        if (21 < version) {
            throw new IllegalStateException("Prefab version is newer than supported. Given: " + version);
        }
        final int worldVersion = (version < 17) ? buffer.readUnsignedShort() : 0;
        if (version == 11) {
            buffer.readUnsignedShort();
        }
        int entityVersion = (version >= 14 && version < 17) ? buffer.readUnsignedShort() : 0;
        final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
        int blockIdVersion = 8;
        if (version >= 13) {
            blockIdVersion = buffer.readShort();
        }
        Vector3i anchor = Vector3i.ZERO;
        if (version >= 16) {
            final long packedAnchor = buffer.readLong();
            anchor = new Vector3i(BlockUtil.unpackX(packedAnchor), BlockUtil.unpackY(packedAnchor), BlockUtil.unpackZ(packedAnchor));
        }
        Function<String, String> blockMigration = null;
        final Map<Integer, BlockMigration> blockMigrationMap = BlockMigration.getAssetMap().getAssetMap();
        int v = blockIdVersion;
        for (BlockMigration migration = blockMigrationMap.get(v); migration != null; migration = blockMigrationMap.get(++v)) {
            if (blockMigration == null) {
                final BlockMigration obj = migration;
                Objects.requireNonNull(obj);
                blockMigration = obj::getMigration;
            }
            else {
                final Function<String, String> function = blockMigration;
                final BlockMigration obj2 = migration;
                Objects.requireNonNull(obj2);
                blockMigration = (Function<String, String>)function.andThen((Function<? super String, ?>)obj2::getMigration);
            }
        }
        final int blockNameCount = buffer.readInt();
        final Int2ObjectOpenHashMap<BlockIdEntry> blockIdMapping = new Int2ObjectOpenHashMap<BlockIdEntry>(blockNameCount);
        for (int i = 0; i < blockNameCount; ++i) {
            try {
                final int readId = buffer.readInt();
                final BlockIdEntry block = this.deserializeBlock(buffer, assetMap, blockMigration);
                blockIdMapping.put(readId, block);
            }
            catch (final Exception e) {
                throw new IllegalStateException("Failed to deserialize block name #" + i, (Throwable)e);
            }
        }
        final IndexedLookupTableAssetMap<String, Fluid> fluidMap = Fluid.getAssetMap();
        final int fluidNameCount = (version >= 18) ? buffer.readInt() : 0;
        final Int2ObjectOpenHashMap<FluidIdEntry> fluidIdMapping = new Int2ObjectOpenHashMap<FluidIdEntry>(fluidNameCount);
        for (int j = 0; j < fluidNameCount; ++j) {
            try {
                final int readId2 = buffer.readInt();
                final FluidIdEntry fluid = this.deserializeFluid(buffer, fluidMap);
                fluidIdMapping.put(readId2, fluid);
            }
            catch (final Exception e2) {
                throw new IllegalStateException("Failed to deserialize block name #" + j, (Throwable)e2);
            }
        }
        final PrefabBuffer.Builder builder = PrefabBuffer.newBuilder();
        builder.setAnchor(anchor);
        for (int columnCount = buffer.readInt(), k = 0; k < columnCount; ++k) {
            final int columnIndex = buffer.readInt();
            final int blocks = buffer.readInt();
            final PrefabBufferBlockEntry[] blockEntries = new PrefabBufferBlockEntry[blocks];
            for (int l = 0; l < blocks; ++l) {
                final int y = buffer.readShort();
                final int readId3 = buffer.readInt();
                final BlockIdEntry block2 = blockIdMapping.get(readId3);
                final int mask = buffer.readUnsignedByte();
                final boolean hasChance = (mask & 0x1) == 0x1;
                final boolean hasState = (mask & 0x2) == 0x2;
                final boolean hasFluid = (mask & 0x4) == 0x4;
                final boolean hasSupportValue = (mask & 0x8) == 0x8;
                final boolean hasFiller = (mask & 0x10) == 0x10;
                final boolean hasRotation = (mask & 0x20) == 0x20;
                final float chance = hasChance ? buffer.readFloat() : 1.0f;
                Holder<ChunkStore> holder = null;
                if (hasState) {
                    final BsonDocument doc = BsonUtil.readFromBinaryStream(buffer);
                    if (version < 15) {
                        holder = SelectionPrefabSerializer.legacyStateDecode(doc);
                    }
                    else if (version < 17) {
                        holder = ChunkStore.REGISTRY.deserialize(doc, worldVersion);
                    }
                    else {
                        holder = ChunkStore.REGISTRY.deserialize(doc);
                    }
                }
                byte supportValue = 0;
                if (hasSupportValue) {
                    supportValue = (byte)(buffer.readByte() & 0xF);
                }
                int filler = 0;
                if (hasFiller) {
                    filler = buffer.readUnsignedShort();
                }
                int rotation = 0;
                if (hasRotation) {
                    rotation = buffer.readUnsignedByte();
                }
                int fluidId = 0;
                byte fluidLevel = 0;
                if (hasFluid) {
                    final int id = buffer.readInt();
                    fluidId = fluidIdMapping.get(id).id;
                    fluidLevel = buffer.readByte();
                }
                blockEntries[l] = new PrefabBufferBlockEntry(y, block2.id, block2.key, chance, holder, fluidId, fluidLevel, supportValue, rotation, filler);
            }
            final int entityCount = buffer.readUnsignedShort();
            Holder<EntityStore>[] entityHolders = null;
            if (entityCount > 0) {
                entityHolders = new Holder[entityCount];
                for (int m = 0; m < entityCount; ++m) {
                    try {
                        if (version >= 12 && version < 14) {
                            entityVersion = buffer.readUnsignedShort();
                        }
                        final BsonDocument entityDocument = BsonUtil.readFromBinaryStream(buffer);
                        Holder<EntityStore> entityHolder;
                        if (version < 14) {
                            entityHolder = SelectionPrefabSerializer.legacyEntityDecode(entityDocument, entityVersion);
                        }
                        else if (version < 17) {
                            entityHolder = EntityStore.REGISTRY.deserialize(entityDocument, entityVersion);
                        }
                        else {
                            entityHolder = EntityStore.REGISTRY.deserialize(entityDocument);
                        }
                        entityHolders[m] = entityHolder;
                    }
                    catch (final Exception e3) {
                        throw new IllegalStateException("Failed to deserialize entity wrapper #" + k, (Throwable)e3);
                    }
                }
            }
            final int x = MathUtil.unpackLeft(columnIndex);
            final int z = MathUtil.unpackRight(columnIndex);
            builder.addColumn(x, z, blockEntries, entityHolders);
        }
        return builder.build();
    }
    
    @Nonnull
    private BlockIdEntry deserializeBlock(@Nonnull final ByteBuf buffer, @Nonnull final BlockTypeAssetMap<String, BlockType> assetMap, @Nullable final Function<String, String> blockMigration) {
        String blockTypeKey;
        final String blockTypeString = blockTypeKey = ByteBufUtil.readUTF(buffer);
        if (blockMigration != null) {
            blockTypeKey = blockMigration.apply(blockTypeKey);
        }
        final int blockId = BlockType.getBlockIdOrUnknown(assetMap, blockTypeKey, "Failed to find block '%s'", blockTypeString);
        return new BlockIdEntry(blockId, blockTypeKey);
    }
    
    @Nonnull
    private FluidIdEntry deserializeFluid(@Nonnull final ByteBuf buffer, @Nonnull final IndexedLookupTableAssetMap<String, Fluid> assetMap) {
        final String fluidName = ByteBufUtil.readUTF(buffer);
        final int fluidId = Fluid.getFluidIdOrUnknown(assetMap, fluidName, "Failed to find fluid '%s'", fluidName);
        return new FluidIdEntry(fluidId, fluidName);
    }
    
    @Nonnull
    @Override
    public ByteBuf serialize(@Nonnull final PrefabBuffer prefabBuffer) {
        final PrefabBuffer.PrefabBufferAccessor access = prefabBuffer.newAccess();
        final Int2ObjectOpenHashMap<String> blockNameMapping = new Int2ObjectOpenHashMap<String>();
        final Int2ObjectOpenHashMap<String> fluidNameMapping = new Int2ObjectOpenHashMap<String>();
        final int[] counts = new int[3];
        access.forEachRaw((x, z, blocks, o) -> {
            final int n4;
            ++counts[n4];
            final int n5;
            counts[n5] += blocks;
            return true;
        }, (x, y, z, mask, blockId, chance, holder, support, rotation, filler, o) -> {
            if (blockNameMapping.containsKey(blockId)) {
                return;
            }
            else {
                final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
                BlockType blockType = assetMap.getAsset(blockId);
                if (blockType == null) {
                    blockType = BlockType.UNKNOWN;
                }
                blockNameMapping.put(blockId, blockType.getId().toString());
                return;
            }
        }, (x, y, z, fluidId, level, o) -> {
            if (fluidNameMapping.containsKey(fluidId)) {
                return;
            }
            else {
                final IndexedLookupTableAssetMap<String, Fluid> assetMap2 = Fluid.getAssetMap();
                Fluid fluidType = assetMap2.getAsset(fluidId);
                if (fluidType == null) {
                    fluidType = Fluid.UNKNOWN;
                }
                fluidNameMapping.put(fluidId, fluidType.getId());
                return;
            }
        }, (x, z, entityHolders, o) -> {
            if (entityHolders != null) {
                final int n21;
                counts[n21] += entityHolders.length;
            }
            return;
        }, null);
        final ByteBuf buffer = Unpooled.buffer(4 + blockNameMapping.size() * 261 + counts[0] * 8 + counts[1] * 13 + counts[2] * 2048);
        buffer.writeShort(21);
        buffer.writeShort(BlockMigration.getAssetMap().getAssetCount());
        buffer.writeLong(BlockUtil.pack(prefabBuffer.getAnchorX(), prefabBuffer.getAnchorY(), prefabBuffer.getAnchorZ()));
        buffer.writeInt(blockNameMapping.size());
        blockNameMapping.int2ObjectEntrySet().fastForEach(entry -> {
            buffer.writeInt(entry.getIntKey());
            ByteBufUtil.writeUTF(buffer, (String)entry.getValue());
            return;
        });
        buffer.writeInt(fluidNameMapping.size());
        fluidNameMapping.int2ObjectEntrySet().fastForEach(entry -> {
            buffer.writeInt(entry.getIntKey());
            ByteBufUtil.writeUTF(buffer, (String)entry.getValue());
            return;
        });
        buffer.writeInt(access.getColumnCount());
        access.forEachRaw((x, z, blocks, o) -> {
            buffer.writeInt(MathUtil.packInt(x, z));
            buffer.writeInt(blocks);
            return true;
        }, (x, y, z, entryMask, blockId, chance, holder, supportValue, rotation, filler, o) -> {
            buffer.writeShort((short)y);
            buffer.writeInt(blockId);
            final boolean hasChance = chance < 1.0f;
            final boolean hasComponents = holder != null;
            int mask = 0;
            if (hasChance) {
                mask |= 0x1;
            }
            if (hasComponents) {
                mask |= 0x2;
            }
            if ((entryMask & 0xC0) != 0x0) {
                mask |= 0x4;
            }
            if (supportValue != 0) {
                mask |= 0x8;
            }
            if (filler != 0) {
                mask |= 0x10;
            }
            if (rotation != 0) {
                mask |= 0x20;
            }
            buffer.writeByte(mask);
            if (hasChance) {
                buffer.writeFloat(chance);
            }
            if (hasComponents) {
                try {
                    BsonUtil.writeToBinaryStream(buffer, ChunkStore.REGISTRY.serialize(holder));
                }
                catch (final Throwable t8) {
                    new IllegalStateException(String.format("Exception while writing %d, %d, %d state!", x, y, z), t8);
                    throw;
                }
            }
            if (supportValue != 0) {
                buffer.writeByte(supportValue);
            }
            if (filler != 0) {
                buffer.writeShort(filler);
            }
            if (rotation != 0) {
                buffer.writeByte(rotation);
            }
            return;
        }, (x, y, z, fluidId, level, o) -> {
            buffer.writeInt(fluidId);
            buffer.writeByte(level);
            return;
        }, (x, z, entityHolders, o) -> {
            final int entities = (entityHolders != null) ? entityHolders.length : 0;
            buffer.writeShort(entities);
            for (int i = 0; i < entities; ++i) {
                final Holder<EntityStore> entityHolder = entityHolders[i];
                try {
                    final BsonDocument document = EntityStore.REGISTRY.serialize(entityHolder);
                    BsonUtil.writeToBinaryStream(buffer, document);
                }
                catch (final Exception e) {
                    new IllegalStateException(String.format("Failed to write EntityWrapper at %d, %d #%d", x, z, i), e);
                    throw;
                }
            }
            return;
        }, null);
        return buffer;
    }
    
    static {
        INSTANCE = new BinaryPrefabBufferCodec();
    }
    
    private static class BlockIdEntry
    {
        public int id;
        public String key;
        
        public BlockIdEntry(final int id, final String key) {
            this.id = id;
            this.key = key;
        }
    }
    
    private static class FluidIdEntry
    {
        public int id;
        public String key;
        
        public FluidIdEntry(final int id, final String key) {
            this.id = id;
            this.key = key;
        }
    }
}
