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

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

import com.hypixel.hytale.math.vector.Vector3d;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import javax.annotation.Nullable;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import java.util.Iterator;
import org.bson.BsonArray;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import java.util.Map;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.math.util.MathUtil;
import org.bson.BsonValue;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBufferBlockEntry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
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.vector.Vector3i;
import com.hypixel.hytale.server.core.prefab.config.SelectionPrefabSerializer;
import com.hypixel.hytale.server.core.prefab.selection.buffer.impl.PrefabBuffer;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import org.bson.BsonInt32;
import org.bson.BsonDocument;

public class BsonPrefabBufferDeserializer implements PrefabBufferDeserializer<BsonDocument>
{
    public static final BsonPrefabBufferDeserializer INSTANCE;
    public static final BsonInt32 LEGACY_BLOCK_ID_VERSION;
    private static final BsonInt32 DEFAULT_SUPPORT_VALUE;
    private static final BsonInt32 DEFAULT_FILLER_VALUE;
    private static final BsonInt32 DEFAULT_ROTATION_VALUE;
    
    @Nonnull
    @Override
    public PrefabBuffer deserialize(final Path path, @Nonnull final BsonDocument document) {
        final BsonValue versionValue = document.get("version");
        final int version = (versionValue != null) ? versionValue.asInt32().getValue() : -1;
        if (version > 8) {
            throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8");
        }
        final int worldVersion = (version < 4) ? SelectionPrefabSerializer.readWorldVersion(document) : 0;
        final BsonValue entityVersionValue = document.get("entityVersion");
        final int entityVersion = (entityVersionValue != null) ? entityVersionValue.asInt32().getValue() : 0;
        if (version < 1) {
            throw new IllegalArgumentException("Prefab version " + version + " is no longer supported. Please re-save the prefab.");
        }
        final Vector3i anchor = new Vector3i();
        anchor.x = document.getInt32("anchorX").getValue();
        anchor.y = document.getInt32("anchorY").getValue();
        anchor.z = document.getInt32("anchorZ").getValue();
        final int blockIdVersion = document.getInt32("blockIdVersion", BsonPrefabBufferDeserializer.LEGACY_BLOCK_ID_VERSION).getValue();
        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 Int2ObjectOpenHashMap<Int2ObjectMap<PrefabBufferBlockEntry>> columnMap = new Int2ObjectOpenHashMap<Int2ObjectMap<PrefabBufferBlockEntry>>();
        final PrefabBuffer.Builder builder = PrefabBuffer.newBuilder();
        builder.setAnchor(anchor);
        final BsonValue blocksValue = document.get("blocks");
        if (blocksValue != null) {
            final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
            final BsonArray blocksArray = blocksValue.asArray();
            for (final BsonValue blockValue : blocksArray) {
                final BsonDocument blockDocument = blockValue.asDocument();
                final int realX = blockDocument.getInt32("x").getValue();
                final int realY = blockDocument.getInt32("y").getValue();
                final int realZ = blockDocument.getInt32("z").getValue();
                final int x = realX - anchor.x;
                final int y = realY - anchor.y;
                final int z = realZ - anchor.z;
                if (-32768 > x || x > 32767) {
                    throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + x + " < Short.MAX_VALUE");
                }
                if (-32768 > y || y > 32767) {
                    throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + y + " < Short.MAX_VALUE");
                }
                if (-32768 > z || z > 32767) {
                    throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + z + " < Short.MAX_VALUE");
                }
                final PrefabBufferBlockEntry blockEntry = builder.newBlockEntry(y);
                try {
                    deserializeBlockType(blockEntry, blockDocument, assetMap, blockMigration);
                }
                catch (final Throwable t) {
                    throw new IllegalStateException("Failed to load block type for " + String.valueOf(path) + " at " + realX + ", " + realY + ", " + realZ, t);
                }
                deserializeState(blockEntry, blockDocument, version, worldVersion);
                blockEntry.supportValue = (byte)blockDocument.getInt32("support", BsonPrefabBufferDeserializer.DEFAULT_SUPPORT_VALUE).getValue();
                blockEntry.filler = blockDocument.getInt32("filler", BsonPrefabBufferDeserializer.DEFAULT_FILLER_VALUE).getValue();
                blockEntry.rotation = blockDocument.getInt32("rotation", BsonPrefabBufferDeserializer.DEFAULT_ROTATION_VALUE).getValue();
                final int columnIndex = MathUtil.packInt(x, z);
                Int2ObjectMap<PrefabBufferBlockEntry> column = columnMap.get(columnIndex);
                if (column == null) {
                    columnMap.put(columnIndex, column = new Int2ObjectOpenHashMap<PrefabBufferBlockEntry>());
                }
                final PrefabBufferBlockEntry existing = column.putIfAbsent(y, blockEntry);
                if (existing != null) {
                    throw new IllegalStateException("Block is already present in column. Given: " + realX + ", " + realY + ", " + realZ + ", " + blockEntry.blockTypeKey + " - Existing: " + existing.y + ", " + existing.blockTypeKey);
                }
            }
        }
        final BsonValue fluidsValue = document.get("fluids");
        PrefabBufferBlockEntry entry = null;
        if (fluidsValue != null) {
            final IndexedLookupTableAssetMap<String, Fluid> assetMap2 = Fluid.getAssetMap();
            final BsonArray fluidsArray = fluidsValue.asArray();
            for (final BsonValue fluidValue : fluidsArray) {
                final BsonDocument fluidDocument = fluidValue.asDocument();
                final int realX2 = fluidDocument.getInt32("x").getValue();
                final int realY2 = fluidDocument.getInt32("y").getValue();
                final int realZ2 = fluidDocument.getInt32("z").getValue();
                final int x2 = realX2 - anchor.x;
                final int y2 = realY2 - anchor.y;
                final int z2 = realZ2 - anchor.z;
                if (-32768 > x2 || x2 > 32767) {
                    throw new IllegalArgumentException("Violation X: Short.MIN_VALUE < " + x2 + " < Short.MAX_VALUE");
                }
                if (-32768 > y2 || y2 > 32767) {
                    throw new IllegalArgumentException("Violation Y: Short.MIN_VALUE < " + y2 + " < Short.MAX_VALUE");
                }
                if (-32768 > z2 || z2 > 32767) {
                    throw new IllegalArgumentException("Violation Z: Short.MIN_VALUE < " + z2 + " < Short.MAX_VALUE");
                }
                final int columnIndex = MathUtil.packInt(x2, z2);
                Int2ObjectMap<PrefabBufferBlockEntry> column = columnMap.get(columnIndex);
                if (column == null) {
                    columnMap.put(columnIndex, column = new Int2ObjectOpenHashMap<PrefabBufferBlockEntry>());
                }
                final Int2ObjectMap<PrefabBufferBlockEntry> int2ObjectMap = column;
                final int key = y2;
                final PrefabBuffer.Builder obj3 = builder;
                Objects.requireNonNull(obj3);
                entry = int2ObjectMap.computeIfAbsent(key, obj3::newBlockEntry);
                final String fluidName = fluidDocument.getString("name").getValue();
                entry.fluidId = Fluid.getFluidIdOrUnknown(fluidName, "Unknown fluid '%s'", fluidName);
                entry.fluidLevel = (byte)fluidDocument.getInt32("level").getValue();
            }
        }
        final Int2ObjectOpenHashMap<List<Holder<EntityStore>>> entityMap = deserializeEntityHolders(document, anchor, version, entityVersion);
        columnMap.int2ObjectEntrySet().fastForEach(entry -> {
            final int columnIndex2 = entry.getIntKey();
            final int x3 = MathUtil.unpackLeft(columnIndex2);
            final int z3 = MathUtil.unpackRight(columnIndex2);
            final Int2ObjectMap<PrefabBufferBlockEntry> columnBlockMap = (Int2ObjectMap<PrefabBufferBlockEntry>)entry.getValue();
            final PrefabBufferBlockEntry[] entries = columnBlockMap.values().toArray(PrefabBufferBlockEntry[]::new);
            Arrays.sort(entries, Comparator.comparingInt(o -> o.y));
            final List<Holder<EntityStore>> entityColumn = entityMap.remove(columnIndex2);
            final Holder[] entityArray = (Holder[])((entityColumn != null && !entityColumn.isEmpty()) ? ((Holder[])entityColumn.toArray(Holder[]::new)) : null);
            builder.addColumn(x3, z3, entries, entityArray);
            return;
        });
        entityMap.int2ObjectEntrySet().fastForEach(entry -> {
            final int columnIndex3 = entry.getIntKey();
            final int x4 = MathUtil.unpackLeft(columnIndex3);
            final int z4 = MathUtil.unpackRight(columnIndex3);
            final List<Holder<EntityStore>> entityColumn2 = (List<Holder<EntityStore>>)entry.getValue();
            final Holder[] entityArray2 = (Holder[])(entityColumn2.isEmpty() ? null : ((Holder[])entityColumn2.toArray(Holder[]::new)));
            if (entityArray2 != null) {
                builder.addColumn(x4, z4, PrefabBufferBlockEntry.EMPTY_ARRAY, entityArray2);
            }
            return;
        });
        return builder.build();
    }
    
    private static void deserializeBlockType(@Nonnull final PrefabBufferBlockEntry blockEntry, @Nonnull final BsonDocument blockDocument, @Nonnull final BlockTypeAssetMap<String, BlockType> assetMap, @Nullable final Function<String, String> blockMigration) {
        final String blockType = blockDocument.getString("name").getValue();
        final int idx = blockType.indexOf(37);
        String blockTypeStr;
        if (idx != -1) {
            blockTypeStr = blockType.substring(idx + 1);
        }
        else {
            blockTypeStr = blockType;
        }
        blockEntry.blockTypeKey = blockTypeStr;
        if (blockMigration != null) {
            blockEntry.blockTypeKey = blockMigration.apply(blockEntry.blockTypeKey);
        }
        blockEntry.blockId = BlockType.getBlockIdOrUnknown(assetMap, blockEntry.blockTypeKey, "Failed to find block. Given %s", blockTypeStr);
        if (idx != -1) {
            final String chanceString = blockType.substring(0, idx);
            final float chancePercent = Float.parseFloat(chanceString);
            if (chancePercent < 0.0f) {
                throw new IllegalArgumentException("Chance is smaller than 0%. Given: " + chancePercent);
            }
            if (chancePercent > 100.0f) {
                throw new IllegalArgumentException("Chance is larger than 100%. Given: " + chancePercent);
            }
            blockEntry.chance = chancePercent / 100.0f;
        }
    }
    
    private static void deserializeState(@Nonnull final PrefabBufferBlockEntry blockEntry, @Nonnull final BsonDocument blockDocument, final int version, final int worldVersion) {
        if (version <= 2) {
            final BsonValue stateValue = blockDocument.get("state");
            if (stateValue != null) {
                blockEntry.state = SelectionPrefabSerializer.legacyStateDecode(stateValue.asDocument());
            }
        }
        else {
            final BsonValue stateValue = blockDocument.get("components");
            if (stateValue != null) {
                if (version < 4) {
                    blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion);
                }
                else {
                    blockEntry.state = ChunkStore.REGISTRY.deserialize(stateValue.asDocument());
                }
            }
        }
    }
    
    @Nonnull
    private static Int2ObjectOpenHashMap<List<Holder<EntityStore>>> deserializeEntityHolders(@Nonnull final BsonDocument document, @Nonnull final Vector3i anchor, final int version, final int entityVersion) {
        final BsonValue entitiesValue = document.get("entities");
        final Int2ObjectOpenHashMap<List<Holder<EntityStore>>> entityMap = new Int2ObjectOpenHashMap<List<Holder<EntityStore>>>();
        if (entitiesValue == null) {
            return entityMap;
        }
        final BsonArray entitiesArray = entitiesValue.asArray();
        for (int i = 0, size = entitiesArray.size(); i < size; ++i) {
            final BsonDocument entityDocument = entitiesArray.get(i).asDocument();
            try {
                Holder<EntityStore> entityHolder;
                if (version <= 1) {
                    entityHolder = SelectionPrefabSerializer.legacyEntityDecode(entityDocument, entityVersion);
                }
                else {
                    entityHolder = EntityStore.REGISTRY.deserialize(entityDocument);
                }
                final TransformComponent transformComponent = entityHolder.getComponent(TransformComponent.getComponentType());
                assert transformComponent != null;
                final Vector3d position = transformComponent.getPosition();
                position.add(-anchor.x, -anchor.y, -anchor.z);
                final int x = MathUtil.floor(position.getX()) & 0xFFFF;
                final int z = MathUtil.floor(position.getZ()) & 0xFFFF;
                final int columnIndex = MathUtil.packInt(x, z);
                List<Holder<EntityStore>> entityColumn = entityMap.get(columnIndex);
                if (entityColumn == null) {
                    entityMap.put(columnIndex, entityColumn = new ObjectArrayList<Holder<EntityStore>>());
                }
                entityColumn.add(entityHolder);
            }
            catch (final Exception e) {
                throw new IllegalStateException("Failed to load entity wrapper #" + i + ": " + String.valueOf(entityDocument), (Throwable)e);
            }
        }
        return entityMap;
    }
    
    static {
        INSTANCE = new BsonPrefabBufferDeserializer();
        LEGACY_BLOCK_ID_VERSION = new BsonInt32(8);
        DEFAULT_SUPPORT_VALUE = new BsonInt32(0);
        DEFAULT_FILLER_VALUE = new BsonInt32(0);
        DEFAULT_ROTATION_VALUE = new BsonInt32(0);
    }
}
