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

package com.hypixel.hytale.server.core.prefab.config;

import com.hypixel.hytale.codec.lookup.ACodecMapCodec;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import javax.annotation.Nullable;
import com.hypixel.hytale.codec.DirectDecodeCodec;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.data.unknown.TempUnknownComponent;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import java.util.List;
import java.util.function.Consumer;
import java.util.ArrayList;
import org.bson.BsonString;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.component.Holder;
import org.bson.BsonArray;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import java.util.Map;
import org.bson.BsonValue;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.logging.Level;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import java.util.function.Function;
import java.util.Objects;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockMigration;
import com.hypixel.hytale.server.core.prefab.selection.buffer.BsonPrefabBufferDeserializer;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import javax.annotation.Nonnull;
import org.bson.BsonInt32;
import org.bson.BsonDocument;
import java.util.Comparator;

public class SelectionPrefabSerializer
{
    public static final int VERSION = 8;
    private static final Comparator<BsonDocument> COMPARE_BLOCK_POSITION;
    private static final BsonInt32 DEFAULT_SUPPORT_VALUE;
    private static final BsonInt32 DEFAULT_FILLER_VALUE;
    private static final BsonInt32 DEFAULT_ROTATION_VALUE;
    
    private SelectionPrefabSerializer() {
    }
    
    @Nonnull
    public static BlockSelection deserialize(@Nonnull final BsonDocument doc) {
        final BsonValue versionValue = doc.get("version");
        final int version = (versionValue != null) ? versionValue.asInt32().getValue() : -1;
        if (version <= 0) {
            throw new IllegalArgumentException("Prefab version is too old: " + version);
        }
        if (version > 8) {
            throw new IllegalArgumentException("Prefab version is too new: " + version + " by expected 8");
        }
        final int worldVersion = (version < 4) ? readWorldVersion(doc) : 0;
        final BsonValue entityVersionValue = doc.get("entityVersion");
        final int entityVersion = (entityVersionValue != null) ? entityVersionValue.asInt32().getValue() : 0;
        final int anchorX = doc.getInt32("anchorX").getValue();
        final int anchorY = doc.getInt32("anchorY").getValue();
        final int anchorZ = doc.getInt32("anchorZ").getValue();
        final BlockSelection selection = new BlockSelection();
        selection.setAnchor(anchorX, anchorY, anchorZ);
        final int blockIdVersion = doc.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 BsonValue blocksValue = doc.get("blocks");
        if (blocksValue != null) {
            final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
            final BsonArray bsonArray = blocksValue.asArray();
            for (int i = 0; i < bsonArray.size(); ++i) {
                final BsonDocument innerObj = bsonArray.get(i).asDocument();
                final int x = innerObj.getInt32("x").getValue();
                final int y = innerObj.getInt32("y").getValue();
                final int z = innerObj.getInt32("z").getValue();
                String blockTypeStr = innerObj.getString("name").getValue();
                boolean legacyStripName = false;
                if (version <= 4) {
                    final Fluid.ConversionResult result = Fluid.convertBlockToFluid(blockTypeStr);
                    if (result != null) {
                        legacyStripName = true;
                        selection.addFluidAtLocalPos(x, y, z, result.fluidId, result.fluidLevel);
                        if (result.blockTypeStr == null) {
                            continue;
                        }
                    }
                }
                int support = 0;
                if (version >= 6) {
                    support = innerObj.getInt32("support", SelectionPrefabSerializer.DEFAULT_SUPPORT_VALUE).getValue();
                }
                else if (blockTypeStr.contains("|Deco")) {
                    legacyStripName = true;
                    support = 15;
                }
                else if (blockTypeStr.contains("|Support=")) {
                    legacyStripName = true;
                    final int start = blockTypeStr.indexOf("|Support=") + "|Support=".length();
                    int end = blockTypeStr.indexOf(124, start);
                    if (end == -1) {
                        end = blockTypeStr.length();
                    }
                    support = Integer.parseInt(blockTypeStr, start, end, 10);
                }
                else {
                    support = 0;
                }
                int filler = 0;
                if (version >= 7) {
                    filler = innerObj.getInt32("filler", SelectionPrefabSerializer.DEFAULT_FILLER_VALUE).getValue();
                }
                else if (blockTypeStr.contains("|Filler=")) {
                    legacyStripName = true;
                    final int start2 = blockTypeStr.indexOf("|Filler=") + "|Filler=".length();
                    final int firstComma = blockTypeStr.indexOf(44, start2);
                    if (firstComma == -1) {
                        throw new IllegalArgumentException("Invalid filler metadata! Missing comma");
                    }
                    final int secondComma = blockTypeStr.indexOf(44, firstComma + 1);
                    if (secondComma == -1) {
                        throw new IllegalArgumentException("Invalid filler metadata! Missing second comma");
                    }
                    int end2 = blockTypeStr.indexOf(124, start2);
                    if (end2 == -1) {
                        end2 = blockTypeStr.length();
                    }
                    final int fillerX = Integer.parseInt(blockTypeStr, start2, firstComma, 10);
                    final int fillerY = Integer.parseInt(blockTypeStr, firstComma + 1, secondComma, 10);
                    final int fillerZ = Integer.parseInt(blockTypeStr, secondComma + 1, end2, 10);
                    filler = FillerBlockUtil.pack(fillerX, fillerY, fillerZ);
                }
                else {
                    filler = 0;
                }
                int rotation = 0;
                if (version >= 8) {
                    rotation = innerObj.getInt32("rotation", SelectionPrefabSerializer.DEFAULT_ROTATION_VALUE).getValue();
                }
                else {
                    Rotation yaw = Rotation.None;
                    Rotation pitch = Rotation.None;
                    final Rotation roll = Rotation.None;
                    if (blockTypeStr.contains("|Yaw=")) {
                        legacyStripName = true;
                        final int start3 = blockTypeStr.indexOf("|Yaw=") + "|Yaw=".length();
                        int end3 = blockTypeStr.indexOf(124, start3);
                        if (end3 == -1) {
                            end3 = blockTypeStr.length();
                        }
                        yaw = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start3, end3, 10));
                    }
                    if (blockTypeStr.contains("|Pitch=")) {
                        legacyStripName = true;
                        final int start3 = blockTypeStr.indexOf("|Pitch=") + "|Pitch=".length();
                        int end3 = blockTypeStr.indexOf(124, start3);
                        if (end3 == -1) {
                            end3 = blockTypeStr.length();
                        }
                        pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start3, end3, 10));
                    }
                    if (blockTypeStr.contains("|Roll=")) {
                        legacyStripName = true;
                        final int start3 = blockTypeStr.indexOf("|Roll=") + "|Roll=".length();
                        int end3 = blockTypeStr.indexOf(124, start3);
                        if (end3 == -1) {
                            end3 = blockTypeStr.length();
                        }
                        pitch = Rotation.ofDegrees(Integer.parseInt(blockTypeStr, start3, end3, 10));
                    }
                    rotation = RotationTuple.index(yaw, pitch, roll);
                }
                if (legacyStripName) {
                    final int endOfName = blockTypeStr.indexOf(124);
                    if (endOfName != -1) {
                        blockTypeStr = blockTypeStr.substring(0, endOfName);
                    }
                }
                String blockTypeKey = blockTypeStr;
                if (blockMigration != null) {
                    blockTypeKey = blockMigration.apply(blockTypeKey);
                }
                final int blockId = BlockType.getBlockIdOrUnknown(assetMap, blockTypeKey, "Failed to find block '%s' in unknown legacy prefab!", blockTypeStr);
                Holder<ChunkStore> wrapper = null;
                if (version <= 2) {
                    final BsonValue stateValue = innerObj.get("state");
                    if (stateValue != null) {
                        wrapper = legacyStateDecode(stateValue.asDocument());
                    }
                }
                else {
                    final BsonValue stateValue = innerObj.get("components");
                    if (stateValue != null) {
                        if (version < 4) {
                            wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument(), worldVersion);
                        }
                        else {
                            wrapper = ChunkStore.REGISTRY.deserialize(stateValue.asDocument());
                        }
                    }
                }
                selection.addBlockAtLocalPos(x, y, z, blockId, rotation, filler, support, wrapper);
            }
        }
        final BsonValue fluidsValue = doc.get("fluids");
        if (fluidsValue != null) {
            final IndexedLookupTableAssetMap<String, Fluid> assetMap2 = Fluid.getAssetMap();
            final BsonArray bsonArray2 = fluidsValue.asArray();
            for (int j = 0; j < bsonArray2.size(); ++j) {
                final BsonDocument innerObj2 = bsonArray2.get(j).asDocument();
                final int x2 = innerObj2.getInt32("x").getValue();
                final int y2 = innerObj2.getInt32("y").getValue();
                final int z2 = innerObj2.getInt32("z").getValue();
                final String fluidName = innerObj2.getString("name").getValue();
                final int fluidId = Fluid.getFluidIdOrUnknown(assetMap2, fluidName, "Failed to find fluid '%s' in unknown legacy prefab!", fluidName);
                final byte fluidLevel = (byte)innerObj2.getInt32("level").getValue();
                selection.addFluidAtLocalPos(x2, y2, z2, fluidId, fluidLevel);
            }
        }
        final BsonValue entitiesValues = doc.get("entities");
        if (entitiesValues != null) {
            final BsonArray entities = entitiesValues.asArray();
            for (int j = 0; j < entities.size(); ++j) {
                final BsonDocument bsonDocument = entities.get(j).asDocument();
                if (version <= 1) {
                    try {
                        selection.addEntityHolderRaw(legacyEntityDecode(bsonDocument, entityVersion));
                    }
                    catch (final Throwable t) {
                        HytaleLogger.getLogger().at(Level.WARNING).withCause(t).log("Exception when loading entity state %s", bsonDocument);
                    }
                }
                else {
                    selection.addEntityHolderRaw(EntityStore.REGISTRY.deserialize(bsonDocument));
                }
            }
        }
        return selection;
    }
    
    @Nonnull
    public static BsonDocument serialize(@Nonnull final BlockSelection prefab) {
        Objects.requireNonNull(prefab, "null prefab");
        final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
        final IndexedLookupTableAssetMap<String, Fluid> fluidMap = Fluid.getAssetMap();
        final BsonDocument out = new BsonDocument();
        out.put("version", new BsonInt32(8));
        out.put("blockIdVersion", new BsonInt32(BlockMigration.getAssetMap().getAssetCount()));
        out.put("anchorX", new BsonInt32(prefab.getAnchorX()));
        out.put("anchorY", new BsonInt32(prefab.getAnchorY()));
        out.put("anchorZ", new BsonInt32(prefab.getAnchorZ()));
        final BsonArray contentOut = new BsonArray();
        prefab.forEachBlock((x, y, z, block) -> {
            final BsonDocument innerObj = new BsonDocument();
            innerObj.put("x", new BsonInt32(x));
            innerObj.put("y", new BsonInt32(y));
            innerObj.put("z", new BsonInt32(z));
            innerObj.put("name", new BsonString(assetMap.getAsset(block.blockId()).getId().toString()));
            if (block.holder() != null) {
                innerObj.put("components", ChunkStore.REGISTRY.serialize(block.holder()));
            }
            if (block.supportValue() != 0) {
                innerObj.put("support", new BsonInt32(block.supportValue()));
            }
            if (block.filler() != 0) {
                innerObj.put("filler", new BsonInt32(block.filler()));
            }
            if (block.rotation() != 0) {
                innerObj.put("rotation", new BsonInt32(block.rotation()));
            }
            contentOut.add(innerObj);
            return;
        });
        contentOut.sort((a, b) -> {
            final BsonDocument aDoc = a.asDocument();
            final BsonDocument bDoc = b.asDocument();
            return SelectionPrefabSerializer.COMPARE_BLOCK_POSITION.compare(aDoc, bDoc);
        });
        out.put("blocks", contentOut);
        final BsonArray fluidContentOut = new BsonArray();
        prefab.forEachFluid((x, y, z, fluid, level) -> {
            final BsonDocument innerObj2 = new BsonDocument();
            innerObj2.put("x", new BsonInt32(x));
            innerObj2.put("y", new BsonInt32(y));
            innerObj2.put("z", new BsonInt32(z));
            innerObj2.put("name", new BsonString(fluidMap.getAsset(fluid).getId()));
            innerObj2.put("level", new BsonInt32(level));
            fluidContentOut.add(innerObj2);
            return;
        });
        fluidContentOut.sort((a, b) -> {
            final BsonDocument aDoc2 = a.asDocument();
            final BsonDocument bDoc2 = b.asDocument();
            return SelectionPrefabSerializer.COMPARE_BLOCK_POSITION.compare(aDoc2, bDoc2);
        });
        if (!fluidContentOut.isEmpty()) {
            out.put("fluids", fluidContentOut);
        }
        final List<BsonDocument> entityList = new ArrayList<BsonDocument>();
        prefab.forEachEntity(holder -> entityList.add(EntityStore.REGISTRY.serialize(holder)));
        if (!entityList.isEmpty()) {
            final BsonArray entities = new BsonArray();
            final List<BsonDocument> list = entityList;
            final BsonArray obj = entities;
            Objects.requireNonNull(obj);
            list.forEach(obj::add);
            out.put("entities", entities);
        }
        return out;
    }
    
    public static int readWorldVersion(@Nonnull final BsonDocument document) {
        int worldVersion;
        if (document.containsKey("worldVersion")) {
            worldVersion = document.getInt32("worldVersion").getValue();
        }
        else if (document.containsKey("worldver")) {
            worldVersion = document.getInt32("worldver").getValue();
        }
        else {
            worldVersion = 5;
        }
        if (worldVersion == 18553) {
            throw new IllegalArgumentException("WorldChunk version old format! Update!");
        }
        if (worldVersion > 23) {
            throw new IllegalArgumentException("WorldChunk version is newer than we understand! Version: " + worldVersion + ", Latest Version: 23");
        }
        return worldVersion;
    }
    
    @Nullable
    public static Holder<EntityStore> legacyEntityDecode(@Nonnull final BsonDocument document, final int version) {
        final String entityTypeStr = document.getString("EntityType").getValue();
        final Class<? extends Entity> entityType = EntityModule.get().getClass(entityTypeStr);
        if (entityType == null) {
            final UnknownComponents unknownComponents = new UnknownComponents();
            unknownComponents.addComponent(entityTypeStr, new TempUnknownComponent(document));
            return EntityStore.REGISTRY.newHolder(Archetype.of(EntityStore.REGISTRY.getUnknownComponentType()), new Component[] { unknownComponents });
        }
        final Function<World, ? extends Entity> constructor = EntityModule.get().getConstructor(entityType);
        if (constructor == null) {
            return null;
        }
        final DirectDecodeCodec<? extends Entity> codec = EntityModule.get().getCodec(entityType);
        Objects.requireNonNull(codec, "Unable to create entity because there is no associated codec");
        final Entity entity = (Entity)constructor.apply(null);
        codec.decode(document, entity, new ExtraInfo(version));
        return entity.toHolder();
    }
    
    @Nonnull
    public static Holder<ChunkStore> legacyStateDecode(@Nonnull final BsonDocument document) {
        final ExtraInfo extraInto = ExtraInfo.THREAD_LOCAL.get();
        final String type = BlockState.TYPE_STRUCTURE.getNow(document, extraInto);
        final Class<? extends BlockState> blockStateClass = (Class<? extends BlockState>)BlockState.CODEC.getClassFor(type);
        if (blockStateClass != null) {
            try {
                final BlockState t = BlockState.CODEC.decode(document, extraInto);
                final Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
                final ComponentType<ChunkStore, ? extends BlockState> componentType = BlockStateModule.get().getComponentType(blockStateClass);
                if (componentType == null) {
                    throw new IllegalArgumentException("Unable to find component type for: " + String.valueOf(blockStateClass));
                }
                holder.addComponent((ComponentType<ChunkStore, BlockState>)componentType, t);
                return holder;
            }
            catch (final ACodecMapCodec.UnknownIdException ex) {}
        }
        final Holder<ChunkStore> holder2 = ChunkStore.REGISTRY.newHolder();
        final UnknownComponents<ChunkStore> unknownComponents = new UnknownComponents<ChunkStore>();
        unknownComponents.addComponent(type, new TempUnknownComponent<ChunkStore>(document));
        holder2.addComponent(ChunkStore.REGISTRY.getUnknownComponentType(), unknownComponents);
        return holder2;
    }
    
    static {
        COMPARE_BLOCK_POSITION = Comparator.comparingInt(doc -> doc.getInt32("x").getValue()).thenComparingInt(doc -> doc.getInt32("z").getValue()).thenComparingInt(doc -> doc.getInt32("y").getValue());
        DEFAULT_SUPPORT_VALUE = new BsonInt32(0);
        DEFAULT_FILLER_VALUE = new BsonInt32(0);
        DEFAULT_ROTATION_VALUE = new BsonInt32(0);
    }
}
