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

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

import it.unimi.dsi.fastutil.objects.ObjectCollection;
import java.util.Collection;
import com.hypixel.hytale.protocol.Packet;
import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.math.util.ChunkUtil;
import java.util.logging.Level;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.component.NonTicking;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.codec.KeyedCodec;
import java.util.function.Supplier;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.codecs.map.Int2ObjectMapCodec;
import com.hypixel.hytale.codec.store.CodecKey;
import com.hypixel.hytale.codec.store.StoredCodec;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.ComponentRegistry;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Holder;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Component;

public class BlockComponentChunk implements Component<ChunkStore>
{
    public static final BuilderCodec<BlockComponentChunk> CODEC;
    @Nonnull
    private final Int2ObjectMap<Holder<ChunkStore>> entityHolders;
    @Nonnull
    private final Int2ObjectMap<Ref<ChunkStore>> entityReferences;
    @Nonnull
    private final Int2ObjectMap<Holder<ChunkStore>> entityHoldersUnmodifiable;
    @Nonnull
    private final Int2ObjectMap<Ref<ChunkStore>> entityReferencesUnmodifiable;
    private boolean needsSaving;
    
    public static ComponentType<ChunkStore, BlockComponentChunk> getComponentType() {
        return LegacyModule.get().getBlockComponentChunkComponentType();
    }
    
    public BlockComponentChunk() {
        this.entityHolders = new Int2ObjectOpenHashMap<Holder<ChunkStore>>();
        this.entityReferences = new Int2ObjectOpenHashMap<Ref<ChunkStore>>();
        this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable((Int2ObjectMap<? extends Holder<ChunkStore>>)this.entityHolders);
        this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable((Int2ObjectMap<? extends Ref<ChunkStore>>)this.entityReferences);
    }
    
    public BlockComponentChunk(@Nonnull final Int2ObjectMap<Holder<ChunkStore>> entityHolders, @Nonnull final Int2ObjectMap<Ref<ChunkStore>> entityReferences) {
        this.entityHolders = entityHolders;
        this.entityReferences = entityReferences;
        this.entityHoldersUnmodifiable = Int2ObjectMaps.unmodifiable((Int2ObjectMap<? extends Holder<ChunkStore>>)entityHolders);
        this.entityReferencesUnmodifiable = Int2ObjectMaps.unmodifiable((Int2ObjectMap<? extends Ref<ChunkStore>>)entityReferences);
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> clone() {
        final Int2ObjectOpenHashMap<Holder<ChunkStore>> entityHoldersClone = new Int2ObjectOpenHashMap<Holder<ChunkStore>>(this.entityHolders.size() + this.entityReferences.size());
        for (final Int2ObjectMap.Entry<Holder<ChunkStore>> entry : this.entityHolders.int2ObjectEntrySet()) {
            entityHoldersClone.put(entry.getIntKey(), entry.getValue().clone());
        }
        for (final Int2ObjectMap.Entry<Ref<ChunkStore>> entry2 : this.entityReferences.int2ObjectEntrySet()) {
            final Ref<ChunkStore> reference = entry2.getValue();
            entityHoldersClone.put(entry2.getIntKey(), reference.getStore().copyEntity(reference));
        }
        return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<Ref<ChunkStore>>());
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> cloneSerializable() {
        final ComponentRegistry.Data<ChunkStore> data = ChunkStore.REGISTRY.getData();
        final Int2ObjectOpenHashMap<Holder<ChunkStore>> entityHoldersClone = new Int2ObjectOpenHashMap<Holder<ChunkStore>>(this.entityHolders.size() + this.entityReferences.size());
        for (final Int2ObjectMap.Entry<Holder<ChunkStore>> entry : this.entityHolders.int2ObjectEntrySet()) {
            final Holder<ChunkStore> holder = entry.getValue();
            if (holder.getArchetype().hasSerializableComponents(data)) {
                entityHoldersClone.put(entry.getIntKey(), holder.cloneSerializable(data));
            }
        }
        for (final Int2ObjectMap.Entry<Ref<ChunkStore>> entry2 : this.entityReferences.int2ObjectEntrySet()) {
            final Ref<ChunkStore> reference = entry2.getValue();
            final Store<ChunkStore> store = reference.getStore();
            if (store.getArchetype(reference).hasSerializableComponents(data)) {
                entityHoldersClone.put(entry2.getIntKey(), store.copySerializableEntity(reference));
            }
        }
        return new BlockComponentChunk(entityHoldersClone, new Int2ObjectOpenHashMap<Ref<ChunkStore>>());
    }
    
    @Nonnull
    public Int2ObjectMap<Holder<ChunkStore>> getEntityHolders() {
        return this.entityHoldersUnmodifiable;
    }
    
    @Nullable
    public Holder<ChunkStore> getEntityHolder(final int index) {
        return this.entityHolders.get(index);
    }
    
    public void addEntityHolder(final int index, @Nonnull final Holder<ChunkStore> holder) {
        if (this.entityReferences.containsKey(index)) {
            throw new IllegalArgumentException("Duplicate block components at: " + index);
        }
        if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) {
            throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index);
        }
        this.markNeedsSaving();
    }
    
    public void storeEntityHolder(final int index, @Nonnull final Holder<ChunkStore> holder) {
        if (this.entityHolders.putIfAbsent(index, Objects.requireNonNull(holder)) != null) {
            throw new IllegalArgumentException("Duplicate block components (entity holder) at: " + index);
        }
    }
    
    @Nullable
    public Holder<ChunkStore> removeEntityHolder(final int index) {
        final Holder<ChunkStore> reference = this.entityHolders.remove(index);
        if (reference != null) {
            this.markNeedsSaving();
        }
        return reference;
    }
    
    @Nonnull
    public Int2ObjectMap<Ref<ChunkStore>> getEntityReferences() {
        return this.entityReferencesUnmodifiable;
    }
    
    @Nullable
    public Ref<ChunkStore> getEntityReference(final int index) {
        return this.entityReferences.get(index);
    }
    
    public void addEntityReference(final int index, @Nonnull final Ref<ChunkStore> reference) {
        reference.validate();
        if (this.entityHolders.containsKey(index)) {
            throw new IllegalArgumentException("Duplicate block components at: " + index);
        }
        if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) {
            throw new IllegalArgumentException("Duplicate block components (entity reference) at: " + index);
        }
        this.markNeedsSaving();
    }
    
    public void loadEntityReference(final int index, @Nonnull final Ref<ChunkStore> reference) {
        reference.validate();
        if (this.entityHolders.containsKey(index)) {
            throw new IllegalArgumentException("Duplicate block components at: " + index);
        }
        if (this.entityReferences.putIfAbsent(index, Objects.requireNonNull(reference)) != null) {
            throw new IllegalArgumentException("Duplicate block components (entity reference) at: " + index);
        }
    }
    
    public void removeEntityReference(final int index, final Ref<ChunkStore> reference) {
        if (this.entityReferences.remove(index, reference)) {
            this.markNeedsSaving();
        }
    }
    
    public void unloadEntityReference(final int index, final Ref<ChunkStore> reference) {
        this.entityReferences.remove(index, reference);
    }
    
    @Nullable
    public Int2ObjectMap<Holder<ChunkStore>> takeEntityHolders() {
        if (this.entityHolders.isEmpty()) {
            return null;
        }
        final Int2ObjectOpenHashMap<Holder<ChunkStore>> holders = new Int2ObjectOpenHashMap<Holder<ChunkStore>>(this.entityHolders);
        this.entityHolders.clear();
        return holders;
    }
    
    @Nullable
    public Int2ObjectMap<Ref<ChunkStore>> takeEntityReferences() {
        if (this.entityReferences.isEmpty()) {
            return null;
        }
        final Int2ObjectOpenHashMap<Ref<ChunkStore>> holders = new Int2ObjectOpenHashMap<Ref<ChunkStore>>(this.entityReferences);
        this.entityReferences.clear();
        return holders;
    }
    
    @Nullable
    public <T extends Component<ChunkStore>> T getComponent(final int index, @Nonnull final ComponentType<ChunkStore, T> componentType) {
        final Ref<ChunkStore> reference = this.entityReferences.get(index);
        if (reference != null) {
            return reference.getStore().getComponent(reference, componentType);
        }
        final Holder<ChunkStore> holder = this.entityHolders.get(index);
        if (holder != null) {
            return holder.getComponent(componentType);
        }
        return null;
    }
    
    public boolean hasComponents(final int index) {
        return this.entityReferences.containsKey(index) || this.entityHolders.containsKey(index);
    }
    
    public boolean getNeedsSaving() {
        return this.needsSaving;
    }
    
    public void markNeedsSaving() {
        this.needsSaving = true;
    }
    
    public boolean consumeNeedsSaving() {
        final boolean out = this.needsSaving;
        this.needsSaving = false;
        return out;
    }
    
    static {
        CODEC = BuilderCodec.builder(BlockComponentChunk.class, BlockComponentChunk::new).addField(new KeyedCodec<Int2ObjectMap>("BlockComponents", (Codec<Int2ObjectMap>)new Int2ObjectMapCodec(new StoredCodec<Object>((CodecKey<Object>)ChunkStore.HOLDER_CODEC_KEY), (Supplier<Int2ObjectMap<Object>>)Int2ObjectOpenHashMap::new)), (entityChunk, map) -> {
            entityChunk.entityHolders.clear();
            entityChunk.entityHolders.putAll((Map<?, ?>)map);
        }, entityChunk -> {
            if (entityChunk.entityReferences.isEmpty()) {
                return entityChunk.entityHolders;
            }
            else {
                final Int2ObjectOpenHashMap<Holder<ChunkStore>> map2 = new Int2ObjectOpenHashMap<Holder<ChunkStore>>(entityChunk.entityHolders.size() + entityChunk.entityReferences.size());
                map2.putAll((Map<?, ?>)entityChunk.entityHolders);
                for (final Int2ObjectMap.Entry<Ref<ChunkStore>> entry : entityChunk.entityReferences.int2ObjectEntrySet()) {
                    final Ref<ChunkStore> reference = (Ref<ChunkStore>)entry.getValue();
                    final Store<ChunkStore> store = reference.getStore();
                    if (store.getArchetype(reference).hasSerializableComponents(store.getRegistry().getData())) {
                        map2.put(entry.getIntKey(), store.copySerializableEntity(reference));
                    }
                }
                return map2;
            }
        }).build();
    }
    
    public static class BlockComponentChunkLoadingSystem extends RefChangeSystem<ChunkStore, NonTicking<ChunkStore>>
    {
        private static final HytaleLogger LOGGER;
        private final Archetype<ChunkStore> archetype;
        
        public BlockComponentChunkLoadingSystem() {
            this.archetype = Archetype.of(WorldChunk.getComponentType(), BlockComponentChunk.getComponentType());
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.archetype;
        }
        
        @Nonnull
        @Override
        public ComponentType<ChunkStore, NonTicking<ChunkStore>> componentType() {
            return ChunkStore.REGISTRY.getNonTickingComponentType();
        }
        
        @Override
        public void onComponentAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType());
            final Int2ObjectMap<Ref<ChunkStore>> entityReferences = blockComponentChunk.takeEntityReferences();
            if (entityReferences == null) {
                return;
            }
            final int size = entityReferences.size();
            final int[] indexes = new int[size];
            final Ref<ChunkStore>[] references = new Ref[size];
            int j = 0;
            for (final Int2ObjectMap.Entry<Ref<ChunkStore>> entry : entityReferences.int2ObjectEntrySet()) {
                indexes[j] = entry.getIntKey();
                references[j] = entry.getValue();
                ++j;
            }
            final ComponentRegistry.Data<ChunkStore> data = ChunkStore.REGISTRY.getData();
            for (int i = 0; i < size; ++i) {
                if (store.getArchetype(references[i]).hasSerializableComponents(data)) {
                    final Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
                    commandBuffer.removeEntity(references[i], holder, RemoveReason.UNLOAD);
                    blockComponentChunk.storeEntityHolder(indexes[i], holder);
                }
                else {
                    commandBuffer.removeEntity(references[i], RemoveReason.UNLOAD);
                }
            }
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<ChunkStore> ref, final NonTicking<ChunkStore> oldComponent, @Nonnull final NonTicking<ChunkStore> newComponent, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Override
        public void onComponentRemoved(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final WorldChunk chunk = store.getComponent(ref, WorldChunk.getComponentType());
            final BlockComponentChunk blockComponentChunk = store.getComponent(ref, BlockComponentChunk.getComponentType());
            final Int2ObjectMap<Holder<ChunkStore>> entityHolders = blockComponentChunk.takeEntityHolders();
            if (entityHolders == null) {
                return;
            }
            int holderCount = entityHolders.size();
            final int[] indexes = new int[holderCount];
            final Holder<ChunkStore>[] holders = new Holder[holderCount];
            int j = 0;
            for (final Int2ObjectMap.Entry<Holder<ChunkStore>> entry : entityHolders.int2ObjectEntrySet()) {
                indexes[j] = entry.getIntKey();
                holders[j] = entry.getValue();
                ++j;
            }
            for (int i = holderCount - 1; i >= 0; --i) {
                final Holder<ChunkStore> holder = holders[i];
                if (holder.getArchetype().isEmpty()) {
                    BlockComponentChunkLoadingSystem.LOGGER.at(Level.SEVERE).log("Empty archetype entity holder: %s (#%d)", holder, i);
                    --holderCount;
                    holders[i] = holders[holderCount];
                    holders[holderCount] = holder;
                    chunk.markNeedsSaving();
                }
                else {
                    final int index = indexes[i];
                    final int x = ChunkUtil.xFromBlockInColumn(index);
                    final int y = ChunkUtil.yFromBlockInColumn(index);
                    final int z = ChunkUtil.zFromBlockInColumn(index);
                    holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, ref));
                    final BlockState state = BlockState.getBlockState(holder);
                    if (state != null) {
                        state.setPosition(chunk, new Vector3i(x, y, z));
                    }
                }
            }
            commandBuffer.addEntities(holders, AddReason.LOAD);
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
    
    public static class LoadBlockComponentPacketSystem extends ChunkStore.LoadPacketDataQuerySystem
    {
        private final ComponentType<ChunkStore, BlockComponentChunk> componentType;
        
        public LoadBlockComponentPacketSystem(final ComponentType<ChunkStore, BlockComponentChunk> blockComponentChunkComponentType) {
            this.componentType = blockComponentChunkComponentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef player, @Nonnull final List<Packet> results) {
            final BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType);
            final ObjectCollection<Ref<ChunkStore>> references = component.entityReferences.values();
            final Store<ChunkStore> componentStore = store.getExternalData().getWorld().getChunkStore().getStore();
            componentStore.fetch(references, ChunkStore.LOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results);
        }
    }
    
    public static class UnloadBlockComponentPacketSystem extends ChunkStore.UnloadPacketDataQuerySystem
    {
        private final ComponentType<ChunkStore, BlockComponentChunk> componentType;
        
        public UnloadBlockComponentPacketSystem(final ComponentType<ChunkStore, BlockComponentChunk> blockComponentChunkComponentType) {
            this.componentType = blockComponentChunkComponentType;
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.componentType;
        }
        
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef player, @Nonnull final List<Packet> results) {
            final BlockComponentChunk component = archetypeChunk.getComponent(index, this.componentType);
            final ObjectCollection<Ref<ChunkStore>> references = component.entityReferences.values();
            final Store<ChunkStore> componentStore = store.getExternalData().getWorld().getChunkStore().getStore();
            componentStore.fetch(references, ChunkStore.UNLOAD_PACKETS_DATA_QUERY_SYSTEM_TYPE, player, results);
        }
    }
}
