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

package com.hypixel.hytale.server.core.util;

import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.collision.CollisionMath;
import com.hypixel.hytale.math.vector.Vector2d;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.component.spatial.SpatialResource;
import java.util.List;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import java.util.Iterator;
import com.hypixel.hytale.math.block.BlockUtil;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.LinkedList;
import com.hypixel.hytale.math.vector.Vector3d;
import java.util.function.IntPredicate;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.math.iterator.BlockIterator;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.function.predicate.BiIntPredicate;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.universe.world.World;

public final class TargetUtil
{
    private static final float ENTITY_TARGET_RADIUS = 8.0f;
    
    @Nullable
    public static Vector3i getTargetBlock(@Nonnull final World world, @Nonnull final BiIntPredicate blockIdPredicate, final double originX, final double originY, final double originZ, final double directionX, final double directionY, final double directionZ, final double maxDistance) {
        final TargetBuffer buffer = new TargetBuffer(world);
        buffer.updateChunk((int)originX, (int)originZ);
        final boolean success = BlockIterator.iterate(originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> {
            if (y < 0 || y >= 320) {
                return false;
            }
            else {
                iBuffer.updateChunk(x, z);
                if (iBuffer.currentBlockChunk == null || iBuffer.currentChunkColumn == null) {
                    return false;
                }
                else {
                    iBuffer.x = x;
                    iBuffer.y = y;
                    iBuffer.z = z;
                    final BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y);
                    final int blockId = blockSection.get(x, y, z);
                    final int fluidId = WorldUtil.getFluidIdAtPosition(iBuffer.chunkStoreAccessor, iBuffer.currentChunkColumn, x, y, z);
                    return !blockIdPredicate.test(blockId, fluidId);
                }
            }
        }, buffer);
        return success ? null : new Vector3i(buffer.x, buffer.y, buffer.z);
    }
    
    @Nullable
    public static Vector3d getTargetLocation(@Nonnull final World world, @Nonnull final IntPredicate blockIdPredicate, final double originX, final double originY, final double originZ, final double directionX, final double directionY, final double directionZ, final double maxDistance) {
        final TargetBufferLocation buffer = new TargetBufferLocation(world);
        buffer.updateChunk((int)originX, (int)originZ);
        final boolean success = BlockIterator.iterate(originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> {
            if (y < 0 || y >= 320) {
                return false;
            }
            else {
                iBuffer.updateChunk(x, z);
                if (iBuffer.currentBlockChunk == null) {
                    return false;
                }
                else {
                    iBuffer.x = x + px;
                    iBuffer.y = y + py;
                    iBuffer.z = z + pz;
                    final BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y);
                    final int blockId = blockSection.get(x, y, z);
                    return !blockIdPredicate.test(blockId);
                }
            }
        }, buffer);
        return success ? null : new Vector3d(buffer.x, buffer.y, buffer.z);
    }
    
    @Nullable
    public static Vector3i getTargetBlockAvoidLocations(@Nonnull final World world, @Nonnull final IntPredicate blockIdPredicate, final double originX, final double originY, final double originZ, final double directionX, final double directionY, final double directionZ, final double maxDistance, @Nonnull final LinkedList<LongOpenHashSet> blocksToIgnore) {
        final TargetBuffer buffer = new TargetBuffer(world);
        buffer.updateChunk((int)originX, (int)originZ);
        final boolean success = BlockIterator.iterate(originX, originY, originZ, directionX, directionY, directionZ, maxDistance, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> {
            if (y < 0 || y >= 320) {
                return false;
            }
            else {
                iBuffer.updateChunk(x, z);
                if (iBuffer.currentBlockChunk == null) {
                    return false;
                }
                else {
                    iBuffer.x = x;
                    iBuffer.y = y;
                    iBuffer.z = z;
                    final BlockSection blockSection = iBuffer.currentBlockChunk.getSectionAtBlockY(y);
                    final int blockId = blockSection.get(x, y, z);
                    if (blockId != 0) {
                        final long packedBlockLocation = BlockUtil.pack(x, y, z);
                        for (final LongOpenHashSet locations : blocksToIgnore) {
                            if (locations.contains(packedBlockLocation)) {
                                return true;
                            }
                        }
                    }
                    return !blockIdPredicate.test(blockId);
                }
            }
        }, buffer);
        return success ? null : new Vector3i(buffer.x, buffer.y, buffer.z);
    }
    
    @Nullable
    public static Vector3i getTargetBlock(@Nonnull final Ref<EntityStore> ref, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return getTargetBlock(ref, blockId -> blockId != 0, maxDistance, componentAccessor);
    }
    
    @Nullable
    public static Vector3i getTargetBlock(@Nonnull final Ref<EntityStore> ref, @Nonnull final IntPredicate blockIdPredicate, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final World world = componentAccessor.getExternalData().getWorld();
        final Transform transform = getLook(ref, componentAccessor);
        final Vector3d pos = transform.getPosition();
        final Vector3d dir = transform.getDirection();
        return getTargetBlock(world, (id, _fluidId) -> blockIdPredicate.test(id), pos.x, pos.y, pos.z, dir.x, dir.y, dir.z, maxDistance);
    }
    
    @Nullable
    public static Vector3d getTargetLocation(@Nonnull final Ref<EntityStore> ref, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return getTargetLocation(ref, blockId -> blockId != 0, maxDistance, componentAccessor);
    }
    
    @Nullable
    public static Vector3d getTargetLocation(@Nonnull final Ref<EntityStore> ref, @Nonnull final IntPredicate blockIdPredicate, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final Transform transform = getLook(ref, componentAccessor);
        return getTargetLocation(transform, blockIdPredicate, maxDistance, componentAccessor);
    }
    
    @Nullable
    public static Vector3d getTargetLocation(@Nonnull final Transform transform, @Nonnull final IntPredicate blockIdPredicate, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final World world = componentAccessor.getExternalData().getWorld();
        final Vector3d pos = transform.getPosition();
        final Vector3d dir = transform.getDirection();
        return getTargetLocation(world, blockIdPredicate, pos.x, pos.y, pos.z, dir.x, dir.y, dir.z, maxDistance);
    }
    
    @Nonnull
    public static List<Ref<EntityStore>> getAllEntitiesInSphere(@Nonnull final Vector3d position, final double radius, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
        final SpatialResource<Ref<EntityStore>, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType());
        entitySpatialResource.getSpatialStructure().collect(position, (float)radius, results);
        final SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType());
        playerSpatialResource.getSpatialStructure().collect(position, (float)radius, results);
        return results;
    }
    
    @Nonnull
    public static List<Ref<EntityStore>> getAllEntitiesInCylinder(@Nonnull final Vector3d position, final double radius, final double height, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
        final SpatialResource<Ref<EntityStore>, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType());
        entitySpatialResource.getSpatialStructure().collectCylinder(position, radius, height, results);
        final SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType());
        playerSpatialResource.getSpatialStructure().collectCylinder(position, radius, height, results);
        return results;
    }
    
    @Nonnull
    public static List<Ref<EntityStore>> getAllEntitiesInBox(@Nonnull final Vector3d min, @Nonnull final Vector3d max, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
        final SpatialResource<Ref<EntityStore>, EntityStore> entitySpatialResource = componentAccessor.getResource(EntityModule.get().getEntitySpatialResourceType());
        entitySpatialResource.getSpatialStructure().collectBox(min, max, results);
        final SpatialResource<Ref<EntityStore>, EntityStore> playerSpatialResource = componentAccessor.getResource(EntityModule.get().getPlayerSpatialResourceType());
        playerSpatialResource.getSpatialStructure().collectBox(min, max, results);
        return results;
    }
    
    @Nullable
    public static Ref<EntityStore> getTargetEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return getTargetEntity(ref, 8.0f, componentAccessor);
    }
    
    @Nullable
    public static Ref<EntityStore> getTargetEntity(@Nonnull final Ref<EntityStore> ref, final float radius, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Vector3d transformPosition = transformComponent.getPosition();
        final Transform lookVec = getLook(ref, componentAccessor);
        final Vector3d position = lookVec.getPosition();
        final Vector3d direction = lookVec.getDirection();
        final List<Ref<EntityStore>> targetEntities = getAllEntitiesInSphere(position, radius, componentAccessor);
        Ref<EntityStore> targetRef = null;
        targetEntities.removeIf(targetRef -> {
            if (targetRef == null || !targetRef.isValid() || targetRef.equals(ref)) {
                return true;
            }
            else {
                return !isHitByRay(targetRef, position, direction, componentAccessor);
            }
        });
        if (targetEntities.isEmpty()) {
            return null;
        }
        Ref<EntityStore> closest = null;
        double minDist2 = Double.MAX_VALUE;
        final Iterator<Ref<EntityStore>> iterator = targetEntities.iterator();
        while (iterator.hasNext()) {
            targetRef = iterator.next();
            if (targetRef != null) {
                if (!targetRef.isValid()) {
                    continue;
                }
                final TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TransformComponent.getComponentType());
                assert targetTransformComponent != null;
                final double distance = transformPosition.distanceSquaredTo(targetTransformComponent.getPosition());
                if (distance >= minDist2) {
                    continue;
                }
                minDist2 = distance;
                closest = targetRef;
            }
        }
        return closest;
    }
    
    @Nonnull
    public static Transform getLook(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        float eyeHeight = 0.0f;
        final ModelComponent modelComponent = componentAccessor.getComponent(ref, ModelComponent.getComponentType());
        if (modelComponent != null) {
            eyeHeight = modelComponent.getModel().getEyeHeight(ref, componentAccessor);
        }
        final HeadRotation headRotationComponent = componentAccessor.getComponent(ref, HeadRotation.getComponentType());
        assert headRotationComponent != null;
        final Vector3d position = transformComponent.getPosition();
        final Vector3f headRotation = headRotationComponent.getRotation();
        return new Transform(position.getX(), position.getY() + eyeHeight, position.getZ(), headRotation.getPitch(), headRotation.getYaw(), headRotation.getRoll());
    }
    
    private static boolean isHitByRay(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d rayStart, @Nonnull final Vector3d rayDir, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BoundingBox.getComponentType());
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        if (boundingBoxComponent == null) {
            return false;
        }
        final Box boundingBox = boundingBoxComponent.getBoundingBox();
        final Vector3d position = transformComponent.getPosition();
        final Vector2d minMax = new Vector2d();
        return CollisionMath.intersectRayAABB(rayStart, rayDir, position.getX(), position.getY(), position.getZ(), boundingBox, minMax);
    }
    
    private static class TargetBuffer
    {
        @Nonnull
        private final World world;
        @Nonnull
        private final ComponentAccessor<ChunkStore> chunkStoreAccessor;
        private int x;
        private int y;
        private int z;
        private int currentChunkX;
        private int currentChunkZ;
        @Nullable
        private Ref<ChunkStore> currentChunkRef;
        @Nullable
        private ChunkColumn currentChunkColumn;
        @Nullable
        private BlockChunk currentBlockChunk;
        
        public TargetBuffer(@Nonnull final World world) {
            this.world = world;
            this.chunkStoreAccessor = world.getChunkStore().getStore();
        }
        
        public void updateChunk(final int blockX, final int blockZ) {
            final int chunkX = ChunkUtil.chunkCoordinate(blockX);
            final int chunkZ = ChunkUtil.chunkCoordinate(blockZ);
            if (this.currentChunkRef != null && chunkX == this.currentChunkX && chunkZ == this.currentChunkZ) {
                return;
            }
            this.currentChunkX = chunkX;
            this.currentChunkZ = chunkZ;
            final long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ);
            this.currentChunkRef = this.world.getChunkStore().getChunkReference(chunkIndex);
            if (this.currentChunkRef == null || !this.currentChunkRef.isValid()) {
                this.currentChunkColumn = null;
                this.currentBlockChunk = null;
                return;
            }
            this.currentChunkColumn = this.chunkStoreAccessor.getComponent(this.currentChunkRef, ChunkColumn.getComponentType());
            this.currentBlockChunk = this.chunkStoreAccessor.getComponent(this.currentChunkRef, BlockChunk.getComponentType());
        }
    }
    
    private static class TargetBufferLocation
    {
        @Nonnull
        public final World world;
        @Nonnull
        public final ComponentAccessor<ChunkStore> chunkStoreAccessor;
        private double x;
        private double y;
        private double z;
        private int currentChunkX;
        private int currentChunkZ;
        @Nullable
        public Ref<ChunkStore> currentChunkRef;
        @Nullable
        public BlockChunk currentBlockChunk;
        
        public TargetBufferLocation(@Nonnull final World world) {
            this.world = world;
            this.chunkStoreAccessor = world.getChunkStore().getStore();
        }
        
        public void updateChunk(final int blockX, final int blockZ) {
            final int chunkX = ChunkUtil.chunkCoordinate(blockX);
            final int chunkZ = ChunkUtil.chunkCoordinate(blockZ);
            if (this.currentChunkRef != null && chunkX == this.currentChunkX && chunkZ == this.currentChunkZ) {
                return;
            }
            this.currentChunkX = chunkX;
            this.currentChunkZ = chunkZ;
            final long chunkIndex = ChunkUtil.indexChunk(chunkX, chunkZ);
            this.currentChunkRef = this.world.getChunkStore().getChunkReference(chunkIndex);
            if (this.currentChunkRef == null || !this.currentChunkRef.isValid()) {
                this.currentBlockChunk = null;
                return;
            }
            this.currentBlockChunk = this.chunkStoreAccessor.getComponent(this.currentChunkRef, BlockChunk.getComponentType());
        }
    }
}
