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

package com.hypixel.hytale.server.core.universe.world.meta;

import com.hypixel.hytale.protocol.Packet;
import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.metrics.MetricResults;
import com.hypixel.hytale.metrics.MetricsRegistry;
import com.hypixel.hytale.component.system.MetricSystem;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.dependency.RootDependency;
import com.hypixel.hytale.component.dependency.Dependency;
import java.util.Set;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.server.core.universe.world.meta.state.DestroyableBlockState;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.DisableProcessingAssert;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import java.util.function.Predicate;
import com.hypixel.hytale.component.spatial.SpatialStructure;
import org.bson.BsonValue;
import org.bson.BsonDocument;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.plugin.PluginState;
import com.hypixel.hytale.server.core.universe.world.meta.state.SendableBlockState;
import com.hypixel.hytale.server.core.universe.world.chunk.state.TickableBlockState;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData;
import com.hypixel.hytale.component.system.ISystem;
import com.hypixel.hytale.server.core.modules.block.system.ItemContainerStateSpatialSystem;
import com.hypixel.hytale.component.spatial.KDTree;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentType;
import java.util.Map;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

@Deprecated(forRemoval = true)
public class BlockStateModule extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    private static BlockStateModule instance;
    @Deprecated
    private final Map<Class<? extends BlockState>, ComponentType<ChunkStore, ? extends BlockState>> classToComponentType;
    private ResourceType<ChunkStore, SpatialResource<Ref<ChunkStore>, ChunkStore>> itemContainerSpatialResourceType;
    
    public static BlockStateModule get() {
        return BlockStateModule.instance;
    }
    
    public ResourceType<ChunkStore, SpatialResource<Ref<ChunkStore>, ChunkStore>> getItemContainerSpatialResourceType() {
        return this.itemContainerSpatialResourceType;
    }
    
    public BlockStateModule(@Nonnull final JavaPluginInit init) {
        super(init);
        this.classToComponentType = new ConcurrentHashMap<Class<? extends BlockState>, ComponentType<ChunkStore, ? extends BlockState>>();
        BlockStateModule.instance = this;
    }
    
    @Override
    protected void setup() {
        this.registerBlockState(ItemContainerState.class, "container", ItemContainerState.CODEC, ItemContainerState.ItemContainerStateData.class, ItemContainerState.ItemContainerStateData.CODEC);
        this.itemContainerSpatialResourceType = this.getChunkStoreRegistry().registerSpatialResource(() -> new KDTree(Ref::isValid));
        this.getChunkStoreRegistry().registerSystem(new ItemContainerStateSpatialSystem(this.itemContainerSpatialResourceType));
        this.getChunkStoreRegistry().registerSystem(new ItemContainerStateRefSystem());
    }
    
    @Nullable
    public <T extends BlockState> BlockStateRegistration registerBlockState(@Nonnull final Class<T> clazz, @Nonnull final String key, final Codec<T> codec) {
        return this.registerBlockState(clazz, key, codec, null, (Codec<StateData>)null);
    }
    
    @Nullable
    public <T extends BlockState, D extends StateData> BlockStateRegistration registerBlockState(@Nonnull final Class<T> clazz, @Nonnull final String key, @Nullable final Codec<T> codec, final Class<D> dataClass, @Nullable final Codec<D> dataCodec) {
        if (this.isDisabled()) {
            return null;
        }
        BlockState.CODEC.register(key, clazz, codec);
        if (dataCodec != null) {
            StateData.CODEC.register(key, dataClass, dataCodec);
        }
        ComponentType<ChunkStore, T> componentType;
        if (codec != null) {
            componentType = this.getChunkStoreRegistry().registerComponent(clazz, key, (BuilderCodec<T>)(BuilderCodec)codec);
        }
        else {
            componentType = this.getChunkStoreRegistry().registerComponent(clazz, () -> {
                throw new UnsupportedOperationException("Not implemented!");
            });
        }
        this.classToComponentType.put(clazz, componentType);
        this.getChunkStoreRegistry().registerSystem(new LegacyLateInitBlockStateSystem<Object>(componentType), true);
        this.getChunkStoreRegistry().registerSystem(new LegacyBlockStateHolderSystem<Object>(componentType), true);
        this.getChunkStoreRegistry().registerSystem(new LegacyBlockStateRefSystem<Object>(componentType), true);
        if (TickableBlockState.class.isAssignableFrom(clazz)) {
            this.getChunkStoreRegistry().registerSystem(new LegacyTickingBlockStateSystem<Object>(componentType), true);
        }
        if (SendableBlockState.class.isAssignableFrom(clazz)) {
            this.getChunkStoreRegistry().registerSystem((ISystem<ChunkStore>)new LegacyLoadPacketBlockStateSystem((ComponentType<ChunkStore, BlockState>)componentType), true);
            this.getChunkStoreRegistry().registerSystem((ISystem<ChunkStore>)new LegacyUnloadPacketBlockStateSystem((ComponentType<ChunkStore, BlockState>)componentType), true);
        }
        return new BlockStateRegistration(clazz, () -> this.getState() == PluginState.ENABLED, () -> this.unregisterBlockState((Class<BlockState>)clazz, (Class<StateData>)dataClass));
    }
    
    public <T extends BlockState, D extends StateData> void unregisterBlockState(final Class<T> clazz, @Nullable final Class<D> dataClass) {
        if (HytaleServer.get().isShuttingDown()) {
            return;
        }
        BlockState.CODEC.remove((Class<?>)clazz);
        ChunkStore.REGISTRY.unregisterComponent((ComponentType<ChunkStore, Component>)this.classToComponentType.remove(clazz));
        if (dataClass != null) {
            StateData.CODEC.remove((Class<?>)dataClass);
        }
    }
    
    @Nullable
    public <T extends BlockState> T createBlockState(final Class<T> clazz, final WorldChunk chunk, final Vector3i pos, final BlockType blockType) {
        final String id = BlockState.CODEC.getIdFor((Class<?>)clazz);
        return (T)this.createBlockState(id, chunk, pos, blockType);
    }
    
    @Nullable
    public BlockState createBlockState(final String key, final WorldChunk chunk, final Vector3i pos, final BlockType blockType) {
        final Codec<? extends BlockState> codec = BlockState.CODEC.getCodecFor(key);
        if (codec == null) {
            this.getLogger().at(Level.WARNING).log("Failed to create BlockState for '%s' null codec", key);
            return null;
        }
        final BlockState blockState = (BlockState)codec.decode(new BsonDocument());
        if (blockState == null) {
            this.getLogger().at(Level.WARNING).log("Failed to create BlockState for '%s' null value from supplier", key);
            return null;
        }
        blockState.setPosition(chunk, pos);
        if (!blockState.initialize(blockType)) {
            return null;
        }
        blockState.initialized.set(true);
        return blockState;
    }
    
    @Nullable
    public <T extends BlockState> ComponentType<ChunkStore, T> getComponentType(@Nullable final Class<T> entityClass) {
        if (this.isDisabled()) {
            return null;
        }
        if (entityClass == null) {
            return null;
        }
        return (ComponentType)this.classToComponentType.get(entityClass);
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(BlockStateModule.class).depends(BlockModule.class).build();
    }
    
    public static class LegacyBlockStateHolderSystem<T extends BlockState> extends HolderSystem<ChunkStore> implements DisableProcessingAssert
    {
        private final ComponentType<ChunkStore, T> componentType;
        
        public LegacyBlockStateHolderSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
            final T blockState = holder.getComponent(this.componentType);
            switch (reason) {
                case REMOVE: {
                    if (blockState instanceof final DestroyableBlockState destroyableBlockState) {
                        destroyableBlockState.onDestroy();
                    }
                    blockState.unloadFromWorld();
                    break;
                }
                case UNLOAD: {
                    blockState.onUnload();
                    blockState.unloadFromWorld();
                    break;
                }
            }
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
    }
    
    public static class LegacyBlockStateRefSystem<T extends BlockState> extends RefSystem<ChunkStore> implements DisableProcessingAssert
    {
        private static final HytaleLogger LOGGER;
        private final ComponentType<ChunkStore, T> componentType;
        
        public LegacyBlockStateRefSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final T blockState = store.getComponent(ref, this.componentType);
            final int index = blockState.getIndex();
            final WorldChunk chunk = blockState.getChunk();
            if (chunk == null) {
                final Vector3i position = blockState.getBlockPosition();
                final int chunkX = MathUtil.floor(position.getX()) >> 5;
                final int chunkZ = MathUtil.floor(position.getZ()) >> 5;
                final World world = store.getExternalData().getWorld();
                final WorldChunk worldChunk = world.getChunkIfInMemory(ChunkUtil.indexChunk(chunkX, chunkZ));
                if (worldChunk != null && !worldChunk.not(ChunkFlag.INIT)) {
                    if (worldChunk.not(ChunkFlag.TICKING)) {
                        commandBuffer.run(_store -> {
                            final Holder<ChunkStore> holder = _store.removeEntity(ref, RemoveReason.UNLOAD);
                            worldChunk.getBlockComponentChunk().addEntityHolder(index, holder);
                            return;
                        });
                    }
                    final int x = ChunkUtil.xFromBlockInColumn(index);
                    final int y = ChunkUtil.yFromBlockInColumn(index);
                    final int z = ChunkUtil.zFromBlockInColumn(index);
                    blockState.setPosition(worldChunk, new Vector3i(x, y, z));
                }
            }
            blockState.setReference(ref);
            if (blockState.initialized.get()) {
                return;
            }
            if (!blockState.initialize(blockState.getChunk().getBlockType(blockState.getPosition()))) {
                LegacyBlockStateRefSystem.LOGGER.at(Level.WARNING).log("Block State failed initialize: %s, %s, %s", blockState, blockState.getPosition(), chunk);
                commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
            }
            else {
                blockState.initialized.set(true);
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
    
    public static class LegacyLateInitBlockStateSystem<T extends BlockState> extends EntityTickingSystem<ChunkStore> implements DisableProcessingAssert
    {
        private static final HytaleLogger LOGGER;
        private final ComponentType<ChunkStore, T> componentType;
        @Nonnull
        private final Query<ChunkStore> query;
        
        public LegacyLateInitBlockStateSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
            this.query = (Query<ChunkStore>)Query.and(componentType, BlockModule.BlockStateInfo.getComponentType());
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return this.query;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return RootDependency.firstSet();
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final T blockStateComponent = archetypeChunk.getComponent(index, this.componentType);
            assert blockStateComponent != null;
            final BlockModule.BlockStateInfo blockStateInfoComponent = archetypeChunk.getComponent(index, BlockModule.BlockStateInfo.getComponentType());
            assert blockStateInfoComponent != null;
            try {
                if (!blockStateComponent.initialized.get()) {
                    blockStateComponent.initialized.set(true);
                    if (blockStateComponent.getReference() == null || !blockStateComponent.getReference().isValid()) {
                        blockStateComponent.setReference(archetypeChunk.getReferenceTo(index));
                    }
                    final World world = store.getExternalData().getWorld();
                    final Store<ChunkStore> chunkStore = world.getChunkStore().getStore();
                    final WorldChunk worldChunkComponent = chunkStore.getComponent(blockStateInfoComponent.getChunkRef(), WorldChunk.getComponentType());
                    assert worldChunkComponent != null;
                    final int x = ChunkUtil.xFromBlockInColumn(blockStateInfoComponent.getIndex());
                    final int y = ChunkUtil.yFromBlockInColumn(blockStateInfoComponent.getIndex());
                    final int z = ChunkUtil.zFromBlockInColumn(blockStateInfoComponent.getIndex());
                    blockStateComponent.setPosition(worldChunkComponent, new Vector3i(x, y, z));
                    final int blockIndex = worldChunkComponent.getBlock(x, y, z);
                    final BlockType blockType = BlockType.getAssetMap().getAsset(blockIndex);
                    if (!blockStateComponent.initialize(blockType)) {
                        LegacyLateInitBlockStateSystem.LOGGER.at(Level.SEVERE).log("Removing invalid block state %s", blockStateComponent);
                        commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
                    }
                }
            }
            catch (final Exception throwable) {
                LegacyLateInitBlockStateSystem.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception while re-init BlockState! Removing!! %s", blockStateComponent);
                commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
            }
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyLateInitBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
    
    public static class LegacyTickingBlockStateSystem<T extends BlockState> extends EntityTickingSystem<ChunkStore> implements DisableProcessingAssert, MetricSystem<ChunkStore>
    {
        private static final HytaleLogger LOGGER;
        private static final MetricsRegistry<LegacyTickingBlockStateSystem<?>> METRICS_REGISTRY;
        private final ComponentType<ChunkStore, T> componentType;
        
        public LegacyTickingBlockStateSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final T blockState = archetypeChunk.getComponent(index, this.componentType);
            try {
                ((TickableBlockState)blockState).tick(dt, index, archetypeChunk, store, commandBuffer);
            }
            catch (final Throwable throwable) {
                LegacyTickingBlockStateSystem.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception while ticking BlockState! Removing!! %s", blockState);
                commandBuffer.removeEntity(archetypeChunk.getReferenceTo(index), RemoveReason.REMOVE);
            }
        }
        
        @Nonnull
        @Override
        public MetricResults toMetricResults(final Store<ChunkStore> store) {
            return LegacyTickingBlockStateSystem.METRICS_REGISTRY.toMetricResults(this);
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyTickingBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
            METRICS_REGISTRY = new MetricsRegistry<LegacyTickingBlockStateSystem<?>>().register("ComponentType", o -> o.componentType.getTypeClass().toString(), (Codec<String>)Codec.STRING);
        }
    }
    
    public static class LegacyLoadPacketBlockStateSystem<T extends BlockState> extends ChunkStore.LoadPacketDataQuerySystem
    {
        private final ComponentType<ChunkStore, T> componentType;
        
        public LegacyLoadPacketBlockStateSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, final Store<ChunkStore> store, final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef player, final List<Packet> results) {
            final SendableBlockState state = (SendableBlockState)BlockState.getBlockState(index, archetypeChunk);
            if (state.canPlayerSee(player)) {
                state.sendTo(results);
            }
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyLoadPacketBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
    }
    
    public static class LegacyUnloadPacketBlockStateSystem<T extends BlockState> extends ChunkStore.UnloadPacketDataQuerySystem
    {
        private final ComponentType<ChunkStore, T> componentType;
        
        public LegacyUnloadPacketBlockStateSystem(final ComponentType<ChunkStore, T> componentType) {
            this.componentType = componentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, final Store<ChunkStore> store, final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef player, final List<Packet> results) {
            final SendableBlockState state = (SendableBlockState)BlockState.getBlockState(index, archetypeChunk);
            if (state.canPlayerSee(player)) {
                state.unloadFrom(results);
            }
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "LegacyUnloadPacketBlockStateSystem{componentType=" + String.valueOf(this.componentType);
        }
    }
    
    public static class ItemContainerStateRefSystem extends RefSystem<ChunkStore>
    {
        private static final Query<ChunkStore> query;
        
        @Override
        public Query<ChunkStore> getQuery() {
            return ItemContainerStateRefSystem.query;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            commandBuffer.getExternalData().getWorld().getChunkStore().getStore().getResource(BlockModule.BlockStateInfoNeedRebuild.getResourceType()).markAsNeedRebuild();
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            commandBuffer.getExternalData().getWorld().getChunkStore().getStore().getResource(BlockModule.BlockStateInfoNeedRebuild.getResourceType()).markAsNeedRebuild();
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "ItemContainerStateRefSystem{}";
        }
        
        static {
            query = BlockStateModule.get().getComponentType(ItemContainerState.class);
        }
    }
}
