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

package com.hypixel.hytale.builtin.blockspawner;

import javax.annotation.Nullable;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import com.hypixel.hytale.server.core.universe.world.WorldConfig;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockRotationUtil;
import com.hypixel.hytale.math.Axis;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.VariantRotation;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple;
import com.hypixel.hytale.math.util.HashUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import java.util.logging.Level;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.builtin.blockphysics.PrefabBufferValidator;
import com.hypixel.hytale.component.system.ISystem;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import java.util.function.Function;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
import com.hypixel.hytale.assetstore.AssetStore;
import com.hypixel.hytale.server.core.asset.HytaleAssetStore;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.builtin.blockspawner.command.BlockSpawnerCommand;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.builtin.blockspawner.state.BlockSpawner;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class BlockSpawnerPlugin extends JavaPlugin
{
    private static final HytaleLogger LOGGER;
    private ComponentType<ChunkStore, BlockSpawner> blockSpawnerComponentType;
    private static BlockSpawnerPlugin INSTANCE;
    
    public static BlockSpawnerPlugin get() {
        return BlockSpawnerPlugin.INSTANCE;
    }
    
    public BlockSpawnerPlugin(@Nonnull final JavaPluginInit init) {
        super(init);
        BlockSpawnerPlugin.INSTANCE = this;
    }
    
    @Override
    protected void setup() {
        this.getCommandRegistry().registerCommand(new BlockSpawnerCommand());
        AssetRegistry.register(((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(BlockSpawnerTable.class, new DefaultAssetMap()).setPath()).setCodec(BlockSpawnerTable.CODEC)).setKeyFunction(BlockSpawnerTable::getId).loadsAfter(Item.class, BlockType.class)).build());
        this.blockSpawnerComponentType = this.getChunkStoreRegistry().registerComponent(BlockSpawner.class, "BlockSpawner", BlockSpawner.CODEC);
        this.getChunkStoreRegistry().registerSystem(new BlockSpawnerSystem());
        this.getChunkStoreRegistry().registerSystem(new MigrateBlockSpawner());
        this.getEventRegistry().registerGlobal(PrefabBufferValidator.ValidateBlockEvent.class, BlockSpawnerPlugin::validatePrefabBlock);
    }
    
    private static void validatePrefabBlock(final PrefabBufferValidator.ValidateBlockEvent validateBlockEvent) {
        final Holder<ChunkStore> holder = validateBlockEvent.holder();
        if (holder == null) {
            return;
        }
        final BlockSpawner spawner = holder.getComponent(BlockSpawner.getComponentType());
        if (spawner == null) {
            return;
        }
        final BlockType blockType = BlockType.getAssetMap().getAsset(validateBlockEvent.blockId());
        if (blockType == null) {
            return;
        }
        if (spawner.getBlockSpawnerId() == null) {
            validateBlockEvent.reason().append("\t Block ").append(blockType.getId()).append(" at ").append(validateBlockEvent.x()).append(", ").append(validateBlockEvent.y()).append(", ").append(validateBlockEvent.z()).append(" has no defined block spawner id").append('\n');
            return;
        }
        final BlockSpawnerTable blockSpawner = BlockSpawnerTable.getAssetMap().getAsset(spawner.getBlockSpawnerId());
        if (blockSpawner == null) {
            validateBlockEvent.reason().append("\t Block ").append(blockType.getId()).append(" at ").append(validateBlockEvent.x()).append(", ").append(validateBlockEvent.y()).append(", ").append(validateBlockEvent.z()).append(" has an invalid spawner id ").append(spawner.getBlockSpawnerId()).append('\n');
        }
    }
    
    public ComponentType<ChunkStore, BlockSpawner> getBlockSpawnerComponentType() {
        return this.blockSpawnerComponentType;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    private static class BlockSpawnerSystem extends RefSystem<ChunkStore>
    {
        private static final ComponentType<ChunkStore, BlockSpawner> COMPONENT_TYPE;
        private static final ComponentType<ChunkStore, BlockModule.BlockStateInfo> BLOCK_INFO_COMPONENT_TYPE;
        private static final Query<ChunkStore> QUERY;
        
        public BlockSpawnerSystem() {
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return BlockSpawnerSystem.QUERY;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final WorldConfig worldConfig = store.getExternalData().getWorld().getWorldConfig();
            if (worldConfig.getGameMode() == GameMode.Creative) {
                return;
            }
            final BlockSpawner state = commandBuffer.getComponent(ref, BlockSpawnerSystem.COMPONENT_TYPE);
            assert state != null;
            final BlockModule.BlockStateInfo info = commandBuffer.getComponent(ref, BlockSpawnerSystem.BLOCK_INFO_COMPONENT_TYPE);
            assert info != null;
            final String blockSpawnerId = state.getBlockSpawnerId();
            if (blockSpawnerId == null) {
                return;
            }
            final BlockSpawnerTable table = BlockSpawnerTable.getAssetMap().getAsset(blockSpawnerId);
            if (table == null) {
                BlockSpawnerPlugin.LOGGER.at(Level.WARNING).log("Failed to find BlockSpawner Asset by name: %s", blockSpawnerId);
                return;
            }
            final Ref<ChunkStore> chunk = info.getChunkRef();
            if (chunk == null) {
                return;
            }
            final WorldChunk wc = commandBuffer.getComponent(chunk, WorldChunk.getComponentType());
            final int x = ChunkUtil.worldCoordFromLocalCoord(wc.getX(), ChunkUtil.xFromBlockInColumn(info.getIndex()));
            final int y = ChunkUtil.yFromBlockInColumn(info.getIndex());
            final int z = ChunkUtil.worldCoordFromLocalCoord(wc.getZ(), ChunkUtil.zFromBlockInColumn(info.getIndex()));
            final long seed = worldConfig.getSeed();
            final double randomRnd = HashUtil.random(x, y, z, seed - 1699164769L);
            final BlockSpawnerEntry entry = table.getEntries().get(randomRnd);
            if (entry == null) {
                return;
            }
            final String blockKey = entry.getBlockName();
            final RotationTuple rotation = switch (entry.getRotationMode()) {
                default -> throw new MatchException(null, null);
                case NONE -> RotationTuple.NONE;
                case RANDOM -> {
                    final String key = entry.getBlockName();
                    final VariantRotation variantRotation = BlockType.getAssetMap().getAsset(key).getVariantRotation();
                    if (variantRotation == VariantRotation.None) {
                        yield RotationTuple.NONE;
                    }
                    final int randomHash = (int)HashUtil.rehash(x, y, z, seed - 1699164769L);
                    final Rotation rotationYaw = Rotation.NORMAL[(randomHash & 0xFFFF) % Rotation.NORMAL.length];
                    yield BlockRotationUtil.getRotated(RotationTuple.NONE, Axis.Y, rotationYaw, variantRotation);
                }
                case INHERIT -> {
                    final String key = entry.getBlockName();
                    final VariantRotation variantRotation = BlockType.getAssetMap().getAsset(key).getVariantRotation();
                    if (variantRotation == VariantRotation.None) {
                        yield RotationTuple.NONE;
                    }
                    final RotationTuple spawnerRotation = RotationTuple.get(wc.getRotationIndex(x, y, z));
                    final Rotation spawnerYaw = spawnerRotation.yaw();
                    yield BlockRotationUtil.getRotated(RotationTuple.NONE, Axis.Y, spawnerYaw, variantRotation);
                }
            };
            final Holder<ChunkStore> holder = entry.getBlockComponents();
            commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
            commandBuffer.run(_store -> {
                int flags = 4;
                if (holder != null) {
                    flags |= 0x2;
                }
                final int blockId = BlockType.getAssetMap().getIndex(blockKey);
                final BlockType blockType = BlockType.getAssetMap().getAsset(blockId);
                wc.setBlock(x, y, z, blockId, blockType, rotation.index(), 0, flags);
                if (holder != null) {
                    wc.setState(x, y, z, holder.clone());
                }
            });
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        static {
            COMPONENT_TYPE = BlockSpawner.getComponentType();
            BLOCK_INFO_COMPONENT_TYPE = BlockModule.BlockStateInfo.getComponentType();
            QUERY = Query.and(BlockSpawnerSystem.COMPONENT_TYPE, BlockSpawnerSystem.BLOCK_INFO_COMPONENT_TYPE);
        }
    }
    
    @Deprecated(forRemoval = true)
    public static class MigrateBlockSpawner extends BlockModule.MigrationSystem
    {
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final UnknownComponents<ChunkStore> unknown = holder.getComponent(ChunkStore.REGISTRY.getUnknownComponentType());
            assert unknown != null;
            final BlockSpawner blockSpawner = unknown.removeComponent("blockspawner", BlockSpawner.CODEC);
            if (blockSpawner != null) {
                holder.putComponent(BlockSpawner.getComponentType(), blockSpawner);
            }
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Nullable
        @Override
        public Query<ChunkStore> getQuery() {
            return ChunkStore.REGISTRY.getUnknownComponentType();
        }
    }
}
