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

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

import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.entity.nameplate.Nameplate;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.universe.world.World;
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 com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
import com.hypixel.hytale.codec.store.CodecKey;
import com.hypixel.hytale.codec.store.StoredCodec;
import java.util.Collection;
import java.util.Arrays;
import javax.annotation.Nullable;
import java.util.Objects;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.ComponentRegistry;
import java.util.Iterator;
import java.util.Collections;
import java.util.HashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import java.util.Set;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import java.util.List;
import javax.annotation.Nonnull;
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 EntityChunk implements Component<ChunkStore>
{
    @Nonnull
    public static final BuilderCodec<EntityChunk> CODEC;
    @Nonnull
    private final List<Holder<EntityStore>> entityHolders;
    @Nonnull
    private final Set<Ref<EntityStore>> entityReferences;
    @Nonnull
    private final List<Holder<EntityStore>> entityHoldersUnmodifiable;
    @Nonnull
    private final Set<Ref<EntityStore>> entityReferencesUnmodifiable;
    private boolean needsSaving;
    
    @Nonnull
    public static ComponentType<ChunkStore, EntityChunk> getComponentType() {
        return LegacyModule.get().getEntityChunkComponentType();
    }
    
    public EntityChunk() {
        this.entityHolders = new ObjectArrayList<Holder<EntityStore>>();
        this.entityReferences = new HashSet<Ref<EntityStore>>();
        this.entityHoldersUnmodifiable = Collections.unmodifiableList((List<? extends Holder<EntityStore>>)this.entityHolders);
        this.entityReferencesUnmodifiable = Collections.unmodifiableSet((Set<? extends Ref<EntityStore>>)this.entityReferences);
    }
    
    public EntityChunk(@Nonnull final List<Holder<EntityStore>> entityHolders, @Nonnull final Set<Ref<EntityStore>> entityReferences) {
        this.entityHolders = entityHolders;
        this.entityReferences = entityReferences;
        this.entityHoldersUnmodifiable = Collections.unmodifiableList((List<? extends Holder<EntityStore>>)entityHolders);
        this.entityReferencesUnmodifiable = Collections.unmodifiableSet((Set<? extends Ref<EntityStore>>)entityReferences);
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> clone() {
        final ObjectArrayList<Holder<EntityStore>> entityHoldersClone = new ObjectArrayList<Holder<EntityStore>>(this.entityHolders.size() + this.entityReferences.size());
        for (final Holder<EntityStore> entityHolder : this.entityHolders) {
            entityHoldersClone.add(entityHolder.clone());
        }
        for (final Ref<EntityStore> reference : this.entityReferences) {
            entityHoldersClone.add(reference.getStore().copyEntity(reference));
        }
        return new EntityChunk(entityHoldersClone, new HashSet<Ref<EntityStore>>());
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> cloneSerializable() {
        final ComponentRegistry.Data<EntityStore> data = EntityStore.REGISTRY.getData();
        final ObjectArrayList<Holder<EntityStore>> entityHoldersClone = new ObjectArrayList<Holder<EntityStore>>(this.entityHolders.size() + this.entityReferences.size());
        for (final Holder<EntityStore> entityHolder : this.entityHolders) {
            if (entityHolder.getArchetype().hasSerializableComponents(data)) {
                entityHoldersClone.add(entityHolder.cloneSerializable(data));
            }
        }
        for (final Ref<EntityStore> reference : this.entityReferences) {
            final Store<EntityStore> store = reference.getStore();
            if (store.getArchetype(reference).hasSerializableComponents(data)) {
                entityHoldersClone.add(store.copySerializableEntity(reference));
            }
        }
        return new EntityChunk(entityHoldersClone, new HashSet<Ref<EntityStore>>());
    }
    
    @Nonnull
    public List<Holder<EntityStore>> getEntityHolders() {
        return this.entityHoldersUnmodifiable;
    }
    
    public void addEntityHolder(@Nonnull final Holder<EntityStore> holder) {
        this.entityHolders.add(Objects.requireNonNull(holder));
        this.markNeedsSaving();
    }
    
    public void storeEntityHolder(@Nonnull final Holder<EntityStore> holder) {
        this.entityHolders.add(Objects.requireNonNull(holder));
    }
    
    @Nonnull
    public Set<Ref<EntityStore>> getEntityReferences() {
        return this.entityReferencesUnmodifiable;
    }
    
    public void addEntityReference(@Nonnull final Ref<EntityStore> reference) {
        this.entityReferences.add(Objects.requireNonNull(reference));
        this.markNeedsSaving();
    }
    
    public void loadEntityReference(@Nonnull final Ref<EntityStore> reference) {
        this.entityReferences.add(Objects.requireNonNull(reference));
    }
    
    public void removeEntityReference(@Nonnull final Ref<EntityStore> reference) {
        this.entityReferences.remove(Objects.requireNonNull(reference));
        this.markNeedsSaving();
    }
    
    public void unloadEntityReference(@Nonnull final Ref<EntityStore> reference) {
        this.entityReferences.remove(Objects.requireNonNull(reference));
    }
    
    @Nullable
    public Holder<EntityStore>[] takeEntityHolders() {
        if (this.entityHolders.isEmpty()) {
            return null;
        }
        final Holder<EntityStore>[] holders = this.entityHolders.toArray(Holder[]::new);
        this.entityHolders.clear();
        return holders;
    }
    
    @Nullable
    public Ref<EntityStore>[] takeEntityReferences() {
        if (this.entityReferences.isEmpty()) {
            return null;
        }
        final Ref<EntityStore>[] holders = this.entityReferences.toArray(Ref[]::new);
        this.entityReferences.clear();
        return holders;
    }
    
    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(EntityChunk.class, EntityChunk::new).addField(new KeyedCodec<Holder[]>("Entities", new ArrayCodec<Holder>((Codec<Holder>)new StoredCodec<Holder>((CodecKey<Holder>)EntityStore.HOLDER_CODEC_KEY), Holder[]::new)), (entityChunk, array) -> {
            entityChunk.entityHolders.clear();
            Collections.addAll(entityChunk.entityHolders, (Holder[])array);
        }, entityChunk -> {
            if (entityChunk.entityReferences.isEmpty()) {
                return (Holder[])entityChunk.entityHolders.toArray(new Holder[entityChunk.entityHolders.size()]);
            }
            else {
                final Holder[] array2 = new Holder[entityChunk.entityHolders.size() + entityChunk.entityReferences.size()];
                final Holder[] array3 = entityChunk.entityHolders.toArray(array2);
                int index = entityChunk.entityHolders.size();
                for (final Ref<EntityStore> reference : entityChunk.entityReferences) {
                    final Store<EntityStore> store = reference.getStore();
                    if (store.getArchetype(reference).hasSerializableComponents(store.getRegistry().getData())) {
                        index++;
                        final Object o2;
                        final int n2;
                        o2[n2] = store.copyEntity(reference);
                    }
                }
                return (index == array3.length) ? array3 : Arrays.copyOfRange(array3, 0, index);
            }
        }).build();
    }
    
    public static class EntityChunkLoadingSystem extends RefChangeSystem<ChunkStore, NonTicking<ChunkStore>>
    {
        @Nonnull
        private static final HytaleLogger LOGGER;
        @Nonnull
        private final Archetype<ChunkStore> archetype;
        
        public EntityChunkLoadingSystem() {
            this.archetype = Archetype.of(WorldChunk.getComponentType(), EntityChunk.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 World world = store.getExternalData().getWorld();
            final EntityChunk entityChunkComponent = store.getComponent(ref, EntityChunk.getComponentType());
            assert entityChunkComponent != null;
            final Ref<EntityStore>[] references = entityChunkComponent.takeEntityReferences();
            if (references == null) {
                return;
            }
            final Store<EntityStore> entityStore = world.getEntityStore().getStore();
            final Holder<EntityStore>[] holders = entityStore.removeEntities(references, RemoveReason.UNLOAD);
            final ComponentRegistry.Data<EntityStore> data = EntityStore.REGISTRY.getData();
            for (int i = 0; i < holders.length; ++i) {
                final Holder<EntityStore> holder = holders[i];
                if (holder.hasSerializableComponents(data)) {
                    entityChunkComponent.storeEntityHolder(holder);
                }
            }
        }
        
        @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 World world = store.getExternalData().getWorld();
            final WorldChunk worldChunkComponent = store.getComponent(ref, WorldChunk.getComponentType());
            assert worldChunkComponent != null;
            final EntityChunk entityChunkComponent = store.getComponent(ref, EntityChunk.getComponentType());
            assert entityChunkComponent != null;
            final Store<EntityStore> entityStore = world.getEntityStore().getStore();
            final Holder<EntityStore>[] holders = entityChunkComponent.takeEntityHolders();
            if (holders == null) {
                return;
            }
            int holderCount = holders.length;
            for (int i = holderCount - 1; i >= 0; --i) {
                final Holder<EntityStore> holder = holders[i];
                final Archetype<EntityStore> archetype = holder.getArchetype();
                assert archetype != null;
                if (archetype.isEmpty()) {
                    EntityChunkLoadingSystem.LOGGER.at(Level.SEVERE).log("Empty archetype entity holder: %s (#%d)", holder, i);
                    --holderCount;
                    holders[i] = holders[holderCount];
                    holders[holderCount] = holder;
                    worldChunkComponent.markNeedsSaving();
                }
                else if (archetype.count() == 1 && archetype.contains(Nameplate.getComponentType())) {
                    EntityChunkLoadingSystem.LOGGER.at(Level.SEVERE).log("Nameplate only entity holder: %s (#%d)", holder, i);
                    --holderCount;
                    holders[i] = holders[holderCount];
                    holders[holderCount] = holder;
                    worldChunkComponent.markNeedsSaving();
                }
                else {
                    final TransformComponent transformComponent = holder.getComponent(TransformComponent.getComponentType());
                    assert transformComponent != null;
                    transformComponent.setChunkLocation(ref, worldChunkComponent);
                }
            }
            final Ref<EntityStore>[] refs = entityStore.addEntities(holders, 0, holderCount, AddReason.LOAD);
            for (int j = 0; j < refs.length; ++j) {
                final Ref<EntityStore> entityRef = refs[j];
                if (!entityRef.isValid()) {
                    break;
                }
                entityChunkComponent.loadEntityReference(entityRef);
            }
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
}
