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

package com.hypixel.hytale.server.npc.corecomponents.world;

import java.util.function.Supplier;
import it.unimi.dsi.fastutil.objects.ObjectList;
import com.hypixel.hytale.server.core.universe.world.World;
import java.util.List;
import com.hypixel.hytale.builtin.path.WorldPathData;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.server.npc.entities.PathManager;
import com.hypixel.hytale.builtin.path.path.IPrefabPath;
import java.util.Set;
import com.hypixel.hytale.server.core.universe.world.path.IPathWaypoint;
import com.hypixel.hytale.server.core.universe.world.path.IPath;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.builtin.path.PathPlugin;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider;
import com.hypixel.hytale.server.npc.sensorinfo.ExtraInfoProvider;
import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorPath;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.modules.entity.component.WorldGenId;
import javax.annotation.Nullable;
import com.hypixel.hytale.builtin.path.entities.PatrolPathMarkerEntity;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.server.npc.sensorinfo.PositionProvider;
import com.hypixel.hytale.server.npc.sensorinfo.PathProvider;
import java.util.UUID;
import java.util.HashSet;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.corecomponents.SensorBase;

public class SensorPath extends SensorBase
{
    protected final double range;
    protected final PathType pathType;
    protected final Vector3d closestWaypoint;
    protected final HashSet<UUID> disallowedPaths;
    protected final PathProvider pathProvider;
    protected final PositionProvider positionProvider;
    protected final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> prefabPathSpatialResource;
    @Nullable
    protected final ComponentType<EntityStore, PatrolPathMarkerEntity> patrolPathMarkerEntityComponentType;
    protected final ComponentType<EntityStore, WorldGenId> worldGenIdComponentType;
    @Nullable
    protected String path;
    protected int pathIndex;
    protected int pathChangeRevision;
    protected double distanceSquared;
    @Nonnull
    protected LoadStatus loadStatus;
    
    public SensorPath(@Nonnull final BuilderSensorPath builder, @Nonnull final BuilderSupport support) {
        super(builder);
        this.closestWaypoint = new Vector3d(Vector3d.MIN);
        this.disallowedPaths = new HashSet<UUID>();
        this.pathProvider = new PathProvider();
        this.positionProvider = new PositionProvider(null, new ExtraInfoProvider[] { this.pathProvider });
        this.distanceSquared = Double.MAX_VALUE;
        this.loadStatus = LoadStatus.WAITING;
        this.path = builder.getPath(support);
        if (this.path != null && !this.path.isEmpty()) {
            this.pathIndex = AssetRegistry.getOrCreateTagIndex(this.path);
        }
        this.range = builder.getRange(support);
        this.pathType = builder.getPathType(support);
        this.prefabPathSpatialResource = PathPlugin.get().getPrefabPathSpatialResource();
        this.patrolPathMarkerEntityComponentType = PatrolPathMarkerEntity.getComponentType();
        this.worldGenIdComponentType = WorldGenId.getComponentType();
    }
    
    @Override
    public boolean matches(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final double dt, @Nonnull final Store<EntityStore> store) {
        if (!super.matches(ref, role, dt, store) || this.loadStatus == LoadStatus.FAILED) {
            this.pathProvider.clear();
            this.positionProvider.clear();
            return false;
        }
        final TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType());
        assert npcComponent != null;
        final Vector3d position = transformComponent.getPosition();
        final PathManager pathManager = npcComponent.getPathManager();
        final int newRevision = NPCPlugin.get().getPathChangeRevision();
        final boolean newPathRequested = role.getWorldSupport().consumeNewPathRequested();
        if (pathManager.isFollowingPath() && newRevision == this.pathChangeRevision && !newPathRequested) {
            final IPath<?> path = pathManager.getPath(ref, store);
            if (path == null) {
                this.pathProvider.clear();
                this.positionProvider.clear();
                return false;
            }
            this.findClosestWaypoint(path, position, this.closestWaypoint, store);
            if (!this.pathMatches(path) || !this.isInRange(this.distanceSquared)) {
                this.pathProvider.clear();
                this.positionProvider.clear();
                return false;
            }
            this.pathProvider.setPath((IPath<? extends IPathWaypoint>)path);
            this.positionProvider.setTarget(this.closestWaypoint);
            return true;
        }
        else {
            this.pathChangeRevision = newRevision;
            if (newPathRequested && pathManager.isFollowingPath()) {
                final UUID pathId = pathManager.getCurrentPathHint();
                if (pathId != null) {
                    this.disallowedPaths.add(pathId);
                }
            }
            final IPath<?> path = this.findPath(ref, position, store, this.disallowedPaths, newPathRequested);
            if (path == null) {
                this.pathProvider.clear();
                this.positionProvider.clear();
                return false;
            }
            this.closestWaypoint.assign(Vector3d.MIN);
            this.findClosestWaypoint(path, position, this.closestWaypoint, store);
            if (!this.isInRange(this.distanceSquared)) {
                this.pathProvider.clear();
                this.positionProvider.clear();
                return false;
            }
            if (this.pathType == PathType.WorldPath) {
                pathManager.setTransientPath(path);
            }
            else {
                pathManager.setPrefabPath(path.getId(), (IPrefabPath)path);
                store.putComponent(ref, this.worldGenIdComponentType, new WorldGenId(((IPrefabPath)path).getWorldGenId()));
            }
            this.disallowedPaths.clear();
            this.pathProvider.setPath((IPath<? extends IPathWaypoint>)path);
            this.positionProvider.setTarget(this.closestWaypoint);
            return true;
        }
    }
    
    @Override
    public InfoProvider getSensorInfo() {
        return this.positionProvider;
    }
    
    protected boolean pathMatches(@Nonnull final IPath<?> path) {
        return this.path == null || this.path.isEmpty() || this.path.equals(path.getName());
    }
    
    protected boolean isInRange(final double squaredDistance) {
        return this.range <= 0.0 || this.range * this.range > squaredDistance;
    }
    
    @Nullable
    protected IPath<?> findPath(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d position, @Nonnull final Store<EntityStore> store, @Nonnull final Set<UUID> disallowedPaths, final boolean newPathRequested) {
        final World world = store.getExternalData().getWorld();
        final NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType());
        assert npcComponent != null;
        final PathManager pathManager = npcComponent.getPathManager();
        final WorldGenId worldGenIdComponent = store.getComponent(ref, this.worldGenIdComponentType);
        final int worldGenId = (worldGenIdComponent != null) ? worldGenIdComponent.getWorldGenId() : 0;
        IPath<? extends IPathWaypoint> path = null;
        switch (this.pathType.ordinal()) {
            case 0: {
                path = world.getWorldPathConfig().getPath(this.path);
                if (path == null) {
                    NPCPlugin.get().getLogger().at(Level.WARNING).log("Path sensor: Path %s does not exist", this.path);
                    this.loadStatus = LoadStatus.FAILED;
                    return null;
                }
                break;
            }
            case 1: {
                final WorldPathData worldPathData = store.getResource(WorldPathData.getResourceType());
                if (this.path == null) {
                    path = worldPathData.getNearestPrefabPath(worldGenId, position, disallowedPaths, store);
                }
                else {
                    final UUID entityPath = pathManager.getCurrentPathHint();
                    if (entityPath != null && !newPathRequested) {
                        path = worldPathData.getPrefabPath(worldGenId, entityPath, false);
                    }
                    else {
                        path = worldPathData.getNearestPrefabPath(worldGenId, this.pathIndex, position, disallowedPaths, store);
                    }
                }
                if (path == null || !((IPrefabPath)path).isFullyLoaded()) {
                    this.loadStatus = LoadStatus.WAITING;
                    return null;
                }
                this.path = path.getName();
                break;
            }
            case 2: {
                final SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = store.getResource(this.prefabPathSpatialResource);
                final ObjectList<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
                spatialResource.getSpatialStructure().ordered(position, this.range, results);
                if (results.isEmpty()) {
                    this.loadStatus = LoadStatus.WAITING;
                    return null;
                }
                double nearest2 = Double.MAX_VALUE;
                PatrolPathMarkerEntity nearestWaypoint = null;
                for (int i = 0; i < results.size(); ++i) {
                    final Ref<EntityStore> eRef = results.get(i);
                    final PatrolPathMarkerEntity ePatrolPathMarkerEntityComponent = store.getComponent(eRef, this.patrolPathMarkerEntityComponentType);
                    assert ePatrolPathMarkerEntityComponent != null;
                    if (!disallowedPaths.contains(ePatrolPathMarkerEntityComponent.getParentPath().getId())) {
                        final TransformComponent eTransformComponent = store.getComponent(eRef, TransformComponent.getComponentType());
                        assert eTransformComponent != null;
                        final double dist2 = position.distanceSquaredTo(eTransformComponent.getPosition());
                        if (dist2 < nearest2) {
                            nearest2 = dist2;
                            nearestWaypoint = ePatrolPathMarkerEntityComponent;
                        }
                    }
                }
                if (nearestWaypoint == null) {
                    this.loadStatus = LoadStatus.WAITING;
                    return null;
                }
                path = nearestWaypoint.getParentPath();
                if (path == null || !((IPrefabPath)path).isFullyLoaded()) {
                    this.loadStatus = LoadStatus.WAITING;
                    return null;
                }
                break;
            }
            case 3: {
                path = (IPath<? extends IPathWaypoint>)pathManager.getPath(ref, store);
                this.path = null;
                break;
            }
            default: {
                throw new IllegalStateException();
            }
        }
        this.loadStatus = LoadStatus.SUCCESS;
        return path;
    }
    
    protected void findClosestWaypoint(@Nonnull final IPath<?> path, @Nonnull final Vector3d position, @Nonnull final Vector3d cachedTarget, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final double prevDistanceSquared = this.distanceSquared;
        if (!cachedTarget.equals(Vector3d.MIN)) {
            final double newDistance = position.distanceSquaredTo(cachedTarget);
            if (newDistance <= this.distanceSquared) {
                this.distanceSquared = newDistance;
                return;
            }
        }
        this.distanceSquared = Double.MAX_VALUE;
        for (int i = 0; i < path.length(); ++i) {
            final IPathWaypoint pathWaypoint = (IPathWaypoint)path.get(i);
            if (pathWaypoint != null) {
                final Vector3d waypoint = pathWaypoint.getWaypointPosition(componentAccessor);
                final double distance = position.distanceSquaredTo(waypoint);
                if (distance < this.distanceSquared) {
                    this.distanceSquared = distance;
                    cachedTarget.assign(waypoint);
                }
            }
        }
        if (this.distanceSquared == Double.MAX_VALUE) {
            this.distanceSquared = prevDistanceSquared;
        }
    }
    
    protected enum LoadStatus
    {
        WAITING, 
        FAILED, 
        SUCCESS;
    }
    
    public enum PathType implements Supplier<String>
    {
        WorldPath("named world path"), 
        CurrentPrefabPath("a path from the prefab the NPC spawned in"), 
        AnyPrefabPath("a path from any prefab"), 
        TransientPath("a transient path (testing purposes only)");
        
        private final String description;
        
        private PathType(final String description) {
            this.description = description;
        }
        
        @Override
        public String get() {
            return this.description;
        }
    }
}
