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

package com.hypixel.hytale.server.core.modules.block;

import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.component.data.unknown.UnknownComponents;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.system.HolderSystem;
import java.util.Objects;
import com.hypixel.hytale.component.Resource;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.Component;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.StateData;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockComponentChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.events.ChunkPreLoadProcessEvent;
import com.hypixel.hytale.event.EventPriority;
import com.hypixel.hytale.server.core.universe.world.events.AddWorldEvent;
import com.hypixel.hytale.component.system.ISystem;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.world.meta.state.BlockMapMarkersResource;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.server.core.universe.world.meta.state.BlockMapMarker;
import com.hypixel.hytale.server.core.universe.world.meta.state.RespawnBlock;
import com.hypixel.hytale.server.core.universe.world.meta.state.LaunchPad;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.SystemType;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class BlockModule extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    private static BlockModule instance;
    private SystemType<ChunkStore, MigrationSystem> migrationSystemType;
    private ComponentType<ChunkStore, LaunchPad> launchPadComponentType;
    private ComponentType<ChunkStore, RespawnBlock> respawnBlockComponentType;
    private ComponentType<ChunkStore, BlockMapMarker> blockMapMarkerComponentType;
    private ResourceType<ChunkStore, BlockMapMarkersResource> blockMapMarkersResourceType;
    private ComponentType<ChunkStore, BlockStateInfo> blockStateInfoComponentType;
    private ResourceType<ChunkStore, BlockStateInfoNeedRebuild> blockStateInfoNeedRebuildResourceType;
    
    public static BlockModule get() {
        return BlockModule.instance;
    }
    
    public BlockModule(@Nonnull final JavaPluginInit init) {
        super(init);
        BlockModule.instance = this;
    }
    
    @Override
    protected void setup() {
        this.migrationSystemType = this.getChunkStoreRegistry().registerSystemType((Class<? super MigrationSystem>)MigrationSystem.class);
        this.blockStateInfoComponentType = this.getChunkStoreRegistry().registerComponent(BlockStateInfo.class, () -> {
            throw new UnsupportedOperationException();
        });
        this.getChunkStoreRegistry().registerSystem(new BlockStateInfoRefSystem(this.blockStateInfoComponentType));
        this.launchPadComponentType = this.getChunkStoreRegistry().registerComponent(LaunchPad.class, "LaunchPad", LaunchPad.CODEC);
        this.getChunkStoreRegistry().registerSystem(new MigrateLaunchPad());
        this.respawnBlockComponentType = this.getChunkStoreRegistry().registerComponent(RespawnBlock.class, "RespawnBlock", RespawnBlock.CODEC);
        this.getChunkStoreRegistry().registerSystem(new RespawnBlock.OnRemove());
        this.blockMapMarkerComponentType = this.getChunkStoreRegistry().registerComponent(BlockMapMarker.class, "BlockMapMarker", BlockMapMarker.CODEC);
        this.blockMapMarkersResourceType = this.getChunkStoreRegistry().registerResource(BlockMapMarkersResource.class, "BlockMapMarkers", BlockMapMarkersResource.CODEC);
        this.getChunkStoreRegistry().registerSystem(new BlockMapMarker.OnAddRemove());
        this.getEventRegistry().registerGlobal(AddWorldEvent.class, event -> event.getWorld().getWorldMapManager().getMarkerProviders().put("blockMapMarkers", BlockMapMarker.MarkerProvider.INSTANCE));
        this.blockStateInfoNeedRebuildResourceType = this.getChunkStoreRegistry().registerResource(BlockStateInfoNeedRebuild.class, BlockStateInfoNeedRebuild::new);
        this.getEventRegistry().registerGlobal(EventPriority.EARLY, ChunkPreLoadProcessEvent.class, BlockModule::onChunkPreLoadProcessEnsureBlockEntity);
    }
    
    @Deprecated
    public static Ref<ChunkStore> ensureBlockEntity(final WorldChunk chunk, final int x, final int y, final int z) {
        Ref<ChunkStore> blockRef = chunk.getBlockComponentEntity(x, y, z);
        if (blockRef != null) {
            return blockRef;
        }
        final BlockType blockType = chunk.getBlockType(x, y, z);
        if (blockType == null) {
            return null;
        }
        if (blockType.getBlockEntity() != null) {
            final Holder<ChunkStore> data = blockType.getBlockEntity().clone();
            data.putComponent(BlockStateInfo.getComponentType(), new BlockStateInfo(ChunkUtil.indexBlockInColumn(x, y, z), chunk.getReference()));
            blockRef = chunk.getWorld().getChunkStore().getStore().addEntity(data, AddReason.SPAWN);
            return blockRef;
        }
        final BlockState state = BlockState.ensureState(chunk, x, y, z);
        if (state != null) {
            return state.getReference();
        }
        return null;
    }
    
    private static void onChunkPreLoadProcessEnsureBlockEntity(@Nonnull final ChunkPreLoadProcessEvent event) {
        if (!event.isNewlyGenerated()) {
            return;
        }
        final BlockTypeAssetMap<String, BlockType> blockTypeAssetMap = BlockType.getAssetMap();
        final Holder<ChunkStore> holder = event.getHolder();
        final WorldChunk chunk = event.getChunk();
        final ChunkColumn column = holder.getComponent(ChunkColumn.getComponentType());
        if (column == null) {
            return;
        }
        final Holder<ChunkStore>[] sections = column.getSectionHolders();
        if (sections == null) {
            return;
        }
        final BlockComponentChunk blockComponentModule = holder.getComponent(BlockComponentChunk.getComponentType());
        for (int sectionIndex = 0; sectionIndex < 10; ++sectionIndex) {
            final BlockSection section = sections[sectionIndex].ensureAndGetComponent(BlockSection.getComponentType());
            if (!section.isSolidAir()) {
                final int sectionYBlock = sectionIndex << 5;
                for (int sectionY = 0; sectionY < 32; ++sectionY) {
                    final int y = sectionYBlock | sectionY;
                    for (int z = 0; z < 32; ++z) {
                        for (int x = 0; x < 32; ++x) {
                            final int blockId = section.get(x, y, z);
                            final BlockType blockType = blockTypeAssetMap.getAsset(blockId);
                            if (blockType != null) {
                                if (!blockType.isUnknown()) {
                                    if (section.getFiller(x, y, z) == 0) {
                                        final int index = ChunkUtil.indexBlockInColumn(x, y, z);
                                        if (blockType.getBlockEntity() != null) {
                                            if (blockComponentModule.getEntityHolder(index) != null) {
                                                continue;
                                            }
                                            blockComponentModule.addEntityHolder(index, blockType.getBlockEntity().clone());
                                        }
                                        final StateData state = blockType.getState();
                                        if (state != null && state.getId() != null) {
                                            if (blockComponentModule.getEntityHolder(index) == null) {
                                                final Vector3i position = new Vector3i(x, y, z);
                                                final BlockState blockState = BlockStateModule.get().createBlockState(state.getId(), chunk, position, blockType);
                                                if (blockState != null) {
                                                    blockComponentModule.addEntityHolder(index, blockState.toHolder());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    public SystemType<ChunkStore, MigrationSystem> getMigrationSystemType() {
        return this.migrationSystemType;
    }
    
    public ComponentType<ChunkStore, BlockStateInfo> getBlockStateInfoComponentType() {
        return this.blockStateInfoComponentType;
    }
    
    public ComponentType<ChunkStore, LaunchPad> getLaunchPadComponentType() {
        return this.launchPadComponentType;
    }
    
    public ComponentType<ChunkStore, RespawnBlock> getRespawnBlockComponentType() {
        return this.respawnBlockComponentType;
    }
    
    public ComponentType<ChunkStore, BlockMapMarker> getBlockMapMarkerComponentType() {
        return this.blockMapMarkerComponentType;
    }
    
    public ResourceType<ChunkStore, BlockMapMarkersResource> getBlockMapMarkersResourceType() {
        return this.blockMapMarkersResourceType;
    }
    
    public ResourceType<ChunkStore, BlockStateInfoNeedRebuild> getBlockStateInfoNeedRebuildResourceType() {
        return this.blockStateInfoNeedRebuildResourceType;
    }
    
    @Nullable
    public static Ref<ChunkStore> getBlockEntity(@Nonnull final World world, final int x, final int y, final int z) {
        final ChunkStore chunkStore = world.getChunkStore();
        final Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(ChunkUtil.indexChunkFromBlock(x, z));
        if (chunkRef == null) {
            return null;
        }
        final BlockComponentChunk blockComponentChunk = chunkStore.getStore().getComponent(chunkRef, BlockComponentChunk.getComponentType());
        if (blockComponentChunk == null) {
            return null;
        }
        final int blockIndex = ChunkUtil.indexBlockInColumn(x, y, z);
        final Ref<ChunkStore> blockRef = blockComponentChunk.getEntityReference(blockIndex);
        if (blockRef == null || !blockRef.isValid()) {
            return null;
        }
        return blockRef;
    }
    
    @Nullable
    public <T extends Component<ChunkStore>> T getComponent(final ComponentType<ChunkStore, T> componentType, final World world, final int x, final int y, final int z) {
        final Store<ChunkStore> chunkStore = world.getChunkStore().getStore();
        final Ref<ChunkStore> chunkRef = world.getChunkStore().getChunkReference(ChunkUtil.indexChunkFromBlock(x, z));
        final BlockComponentChunk blockComponentChunk = chunkStore.getComponent(chunkRef, BlockComponentChunk.getComponentType());
        if (blockComponentChunk == null) {
            return null;
        }
        final int blockIndex = ChunkUtil.indexBlockInColumn(x, y, z);
        final Ref<ChunkStore> blockRef = blockComponentChunk.getEntityReference(blockIndex);
        if (blockRef == null || !blockRef.isValid()) {
            return null;
        }
        return chunkStore.getComponent(blockRef, componentType);
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(BlockModule.class).depends(LegacyModule.class).build();
    }
    
    public static class BlockStateInfoNeedRebuild implements Resource<ChunkStore>
    {
        private boolean needRebuild;
        
        public static ResourceType<ChunkStore, BlockStateInfoNeedRebuild> getResourceType() {
            return BlockModule.get().getBlockStateInfoNeedRebuildResourceType();
        }
        
        public BlockStateInfoNeedRebuild() {
            this.needRebuild = false;
        }
        
        public BlockStateInfoNeedRebuild(final boolean needRebuild) {
            this.needRebuild = needRebuild;
        }
        
        public boolean invalidateAndReturnIfNeedRebuild() {
            if (this.needRebuild) {
                this.needRebuild = false;
                return true;
            }
            return false;
        }
        
        public void markAsNeedRebuild() {
            this.needRebuild = true;
        }
        
        @Override
        public Resource<ChunkStore> clone() {
            return new BlockStateInfoNeedRebuild(this.needRebuild);
        }
    }
    
    public static class BlockStateInfo implements Component<ChunkStore>
    {
        private final int index;
        @Nonnull
        private final Ref<ChunkStore> chunkRef;
        
        public static ComponentType<ChunkStore, BlockStateInfo> getComponentType() {
            return BlockModule.get().getBlockStateInfoComponentType();
        }
        
        public BlockStateInfo(final int index, @Nonnull final Ref<ChunkStore> chunkRef) {
            Objects.requireNonNull(chunkRef);
            this.index = index;
            this.chunkRef = chunkRef;
        }
        
        public int getIndex() {
            return this.index;
        }
        
        @Nonnull
        public Ref<ChunkStore> getChunkRef() {
            return this.chunkRef;
        }
        
        public void markNeedsSaving() {
            if (this.chunkRef == null || !this.chunkRef.isValid()) {
                return;
            }
            final BlockComponentChunk blockComponentChunk = this.chunkRef.getStore().getComponent(this.chunkRef, BlockComponentChunk.getComponentType());
            if (blockComponentChunk != null) {
                blockComponentChunk.markNeedsSaving();
            }
        }
        
        @Nonnull
        @Override
        public Component<ChunkStore> clone() {
            return new BlockStateInfo(this.index, this.chunkRef);
        }
    }
    
    public abstract static class MigrationSystem extends HolderSystem<ChunkStore>
    {
    }
    
    public static class BlockStateInfoRefSystem extends RefSystem<ChunkStore>
    {
        private final ComponentType<ChunkStore, BlockStateInfo> componentType;
        
        public BlockStateInfoRefSystem(final ComponentType<ChunkStore, BlockStateInfo> 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 BlockStateInfo blockState = commandBuffer.getComponent(ref, this.componentType);
            final Ref<ChunkStore> chunk = blockState.chunkRef;
            if (chunk != null) {
                final BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType());
                switch (reason) {
                    case SPAWN: {
                        blockComponentChunk.addEntityReference(blockState.getIndex(), ref);
                        break;
                    }
                    case LOAD: {
                        blockComponentChunk.loadEntityReference(blockState.getIndex(), ref);
                        break;
                    }
                }
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final BlockStateInfo blockState = commandBuffer.getComponent(ref, this.componentType);
            final Ref<ChunkStore> chunk = blockState.chunkRef;
            if (chunk != null) {
                final BlockComponentChunk blockComponentChunk = commandBuffer.getComponent(chunk, BlockComponentChunk.getComponentType());
                switch (reason) {
                    case REMOVE: {
                        blockComponentChunk.removeEntityReference(blockState.getIndex(), ref);
                        break;
                    }
                    case UNLOAD: {
                        blockComponentChunk.unloadEntityReference(blockState.getIndex(), ref);
                        break;
                    }
                }
            }
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "BlockStateInfoRefSystem{componentType=" + String.valueOf(this.componentType);
        }
    }
    
    @Deprecated(forRemoval = true)
    public static class MigrateLaunchPad extends 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 LaunchPad launchPad = unknown.removeComponent("launchPad", LaunchPad.CODEC);
            if (launchPad != null) {
                holder.putComponent(LaunchPad.getComponentType(), launchPad);
            }
        }
        
        @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();
        }
    }
}
