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

package com.hypixel.hytale.server.core.modules.entity.system;

import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkFlag;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import java.util.concurrent.Executor;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.component.RemoveReason;
import java.util.logging.Level;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.component.CommandBuffer;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.logger.HytaleLogger;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.Message;

public class UpdateLocationSystems
{
    @Nonnull
    private static final Message MESSAGE_GENERAL_PLAYER_IN_INVALID_CHUNK;
    @Nonnull
    private static final HytaleLogger LOGGER;
    
    private static void updateLocation(@Nonnull final Ref<EntityStore> ref, @Nonnull final TransformComponent transformComponent, @Nullable final World world, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        if (world == null) {
            return;
        }
        final Vector3d position = transformComponent.getPosition();
        if (position.getY() < -32.0 && !commandBuffer.getArchetype(ref).contains(Player.getComponentType())) {
            UpdateLocationSystems.LOGGER.at(Level.WARNING).log("Unable to move entity below the world! -32 < " + String.valueOf(position));
            commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
            return;
        }
        final ChunkStore chunkStore = world.getChunkStore();
        final Store<ChunkStore> chunkComponentStore = chunkStore.getStore();
        final int chunkX = MathUtil.floor(position.getX()) >> 5;
        final int chunkZ = MathUtil.floor(position.getZ()) >> 5;
        final Ref<ChunkStore> oldChunkRef = transformComponent.getChunkRef();
        boolean hasOldChunk = false;
        int oldChunkX = 0;
        int oldChunkZ = 0;
        if (oldChunkRef != null && oldChunkRef.isValid()) {
            final WorldChunk oldWorldChunkComponent = chunkComponentStore.getComponent(oldChunkRef, WorldChunk.getComponentType());
            if (oldWorldChunkComponent != null) {
                hasOldChunk = true;
                oldChunkX = oldWorldChunkComponent.getX();
                oldChunkZ = oldWorldChunkComponent.getZ();
            }
        }
        if (!hasOldChunk || oldChunkX != chunkX || oldChunkZ != chunkZ) {
            final long newChunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ);
            final Ref<ChunkStore> newChunkRef = chunkStore.getChunkReference(newChunkIndex);
            if (newChunkRef != null && newChunkRef.isValid()) {
                final WorldChunk newWorldChunkComponent = chunkComponentStore.getComponent(newChunkRef, WorldChunk.getComponentType());
                updateChunk(ref, transformComponent, oldChunkRef, newChunkRef, newWorldChunkComponent, chunkComponentStore, commandBuffer);
            }
            else {
                UpdateLocationSystems.LOGGER.at(Level.WARNING).log("Entity has moved into a chunk that isn't currently loaded! " + chunkX + ", " + chunkZ + ", " + String.valueOf(transformComponent));
                CompletableFutureUtil._catch(chunkStore.getChunkReferenceAsync(newChunkIndex).thenAcceptAsync(asyncChunkRef -> {
                    if (asyncChunkRef == null || !asyncChunkRef.isValid()) {
                        updateChunkAsync(ref, null, null, chunkComponentStore);
                    }
                    else {
                        final WorldChunk asyncWorldChunk = chunkComponentStore.getComponent(asyncChunkRef, WorldChunk.getComponentType());
                        updateChunkAsync(ref, asyncChunkRef, asyncWorldChunk, chunkComponentStore);
                    }
                }, (Executor)world));
            }
        }
    }
    
    private static void updateChunkAsync(@Nonnull final Ref<EntityStore> ref, @Nullable final Ref<ChunkStore> newChunkRef, @Nullable final WorldChunk newWorldChunk, @Nonnull final Store<ChunkStore> chunkComponentStore) {
        if (!ref.isValid()) {
            return;
        }
        final Store<EntityStore> entityStore = ref.getStore();
        final TransformComponent transformComponent = entityStore.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Ref<ChunkStore> oldChunkRef = transformComponent.getChunkRef();
        updateChunk(ref, transformComponent, oldChunkRef, newChunkRef, newWorldChunk, chunkComponentStore, entityStore);
    }
    
    private static void updateChunk(@Nonnull final Ref<EntityStore> ref, @Nonnull final TransformComponent transformComponent, @Nullable final Ref<ChunkStore> oldChunkRef, @Nullable final Ref<ChunkStore> newChunkRef, @Nullable final WorldChunk newWorldChunkComponent, @Nonnull final ComponentAccessor<ChunkStore> chunkComponentStore, @Nonnull final ComponentAccessor<EntityStore> entityComponentAccessor) {
        final boolean isPlayer = entityComponentAccessor.getArchetype(ref).contains(Player.getComponentType());
        if (newWorldChunkComponent == null) {
            handleInvalidChunk(ref, transformComponent, isPlayer, entityComponentAccessor);
            return;
        }
        if (newWorldChunkComponent.not(ChunkFlag.INIT)) {
            return;
        }
        assert newChunkRef != null;
        if (!isPlayer) {
            updateEntityInChunk(ref, oldChunkRef, newChunkRef, newWorldChunkComponent, chunkComponentStore, entityComponentAccessor);
        }
        transformComponent.setChunkLocation(newChunkRef, newWorldChunkComponent);
    }
    
    private static void handleInvalidChunk(@Nonnull final Ref<EntityStore> ref, @Nonnull final TransformComponent transformComponent, final boolean isPlayer, @Nonnull final ComponentAccessor<EntityStore> entityComponentAccessor) {
        if (!isPlayer) {
            UpdateLocationSystems.LOGGER.at(Level.SEVERE).log("Entity is in a chunk that can't be loaded! Removing! %s", transformComponent);
            entityComponentAccessor.removeEntity(ref, EntityStore.REGISTRY.newHolder(), RemoveReason.REMOVE);
        }
        else {
            UpdateLocationSystems.LOGGER.at(Level.SEVERE).log("Player is in a chunk that can't be loaded! Moving (-%d,0,0)! %s", 32, transformComponent);
            final Vector3d position = transformComponent.getPosition();
            final Vector3d targetPosition = position.clone().subtract(32.0, 0.0, 0.0);
            final Vector3f bodyRotation = transformComponent.getRotation();
            final Teleport teleportComponent = Teleport.createForPlayer(targetPosition, bodyRotation);
            entityComponentAccessor.addComponent(ref, Teleport.getComponentType(), teleportComponent);
            final PlayerRef playerRefComponent = entityComponentAccessor.getComponent(ref, PlayerRef.getComponentType());
            if (playerRefComponent != null) {
                playerRefComponent.sendMessage(UpdateLocationSystems.MESSAGE_GENERAL_PLAYER_IN_INVALID_CHUNK);
            }
        }
    }
    
    private static void updateEntityInChunk(@Nonnull final Ref<EntityStore> ref, @Nullable final Ref<ChunkStore> oldChunkRef, @Nonnull final Ref<ChunkStore> newChunkRef, @Nonnull final WorldChunk newWorldChunk, @Nonnull final ComponentAccessor<ChunkStore> chunkComponentStore, @Nonnull final ComponentAccessor<EntityStore> entityComponentAccessor) {
        if (oldChunkRef != null && oldChunkRef.isValid()) {
            final EntityChunk oldEntityChunkComponent = chunkComponentStore.getComponent(oldChunkRef, EntityChunk.getComponentType());
            assert oldEntityChunkComponent != null;
            oldEntityChunkComponent.removeEntityReference(ref);
        }
        final EntityChunk newEntityChunkComponent = chunkComponentStore.getComponent(newChunkRef, EntityChunk.getComponentType());
        assert newEntityChunkComponent != null;
        if (newWorldChunk.not(ChunkFlag.TICKING)) {
            final Holder<EntityStore> holder = EntityStore.REGISTRY.newHolder();
            entityComponentAccessor.removeEntity(ref, holder, RemoveReason.UNLOAD);
            newEntityChunkComponent.addEntityHolder(holder);
        }
        else {
            newEntityChunkComponent.addEntityReference(ref);
        }
    }
    
    static {
        MESSAGE_GENERAL_PLAYER_IN_INVALID_CHUNK = Message.translation("server.general.playerInInvalidChunk");
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public static class SpawnSystem extends RefSystem<EntityStore>
    {
        @Override
        public Query<EntityStore> getQuery() {
            return TransformComponent.getComponentType();
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<EntityStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final TransformComponent transformComponent = commandBuffer.getComponent(ref, TransformComponent.getComponentType());
            assert transformComponent != null;
            final Ref<ChunkStore> chunkRef = transformComponent.getChunkRef();
            if (chunkRef == null || !chunkRef.isValid()) {
                UpdateLocationSystems.updateLocation(ref, transformComponent, store.getExternalData().getWorld(), commandBuffer);
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<EntityStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        }
    }
    
    public static class TickingSystem extends EntityTickingSystem<EntityStore>
    {
        @Override
        public Query<EntityStore> getQuery() {
            return TransformComponent.getComponentType();
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
            final World world = commandBuffer.getExternalData().getWorld();
            final Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
            final TransformComponent transformComponent = archetypeChunk.getComponent(index, TransformComponent.getComponentType());
            assert transformComponent != null;
            UpdateLocationSystems.updateLocation(ref, transformComponent, world, commandBuffer);
        }
    }
}
