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

package com.hypixel.hytale.builtin.path;

import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.Iterator;
import com.hypixel.hytale.assetstore.AssetRegistry;
import java.util.logging.Level;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;
import java.util.Set;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector3d;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.builtin.path.path.IPrefabPath;
import java.util.UUID;
import java.util.Map;
import com.hypixel.hytale.logger.HytaleLogger;

public class PrefabPathCollection
{
    private static final HytaleLogger LOGGER;
    private final int worldgenId;
    private final Map<UUID, IPrefabPath> paths;
    private final Int2ObjectMap<PathSet> pathsByFriendlyName;
    
    public PrefabPathCollection(final int id) {
        this.paths = new Object2ObjectOpenHashMap<UUID, IPrefabPath>();
        this.pathsByFriendlyName = new Int2ObjectOpenHashMap<PathSet>();
        this.worldgenId = id;
    }
    
    @Nullable
    public IPrefabPath getNearestPrefabPath(final int nameIndex, @Nonnull final Vector3d position, final Set<UUID> disallowedPaths, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final PathSet set = this.pathsByFriendlyName.getOrDefault(nameIndex, null);
        if (set == null) {
            return null;
        }
        return set.getNearestPath(position, disallowedPaths, componentAccessor);
    }
    
    public IPrefabPath getPath(final UUID id) {
        return this.paths.getOrDefault(id, null);
    }
    
    public IPrefabPath getOrConstructPath(@Nonnull final UUID id, @Nonnull final String name, @Nonnull final Int2ObjectConcurrentHashMap.IntBiObjFunction<UUID, String, IPrefabPath> pathGenerator) {
        final IPrefabPath path = this.paths.computeIfAbsent(id, k -> {
            PrefabPathCollection.LOGGER.at(Level.FINER).log("Adding path %s.%s", this.worldgenId, k);
            return pathGenerator.apply(this.worldgenId, k, name);
        });
        final int nameIndex = AssetRegistry.getOrCreateTagIndex(name);
        final PathSet set = this.pathsByFriendlyName.computeIfAbsent(nameIndex, s -> new PathSet());
        set.add(path);
        return path;
    }
    
    @Nullable
    public IPrefabPath getNearestPrefabPath(@Nonnull final Vector3d position, @Nullable final Set<UUID> disallowedPaths, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        IPrefabPath nearest = null;
        double minDist2 = Double.MAX_VALUE;
        for (final IPrefabPath path : this.paths.values()) {
            if (disallowedPaths != null && disallowedPaths.contains(path.getId())) {
                continue;
            }
            final double dist2 = position.distanceSquaredTo(path.getNearestWaypointPosition(position, componentAccessor));
            if (dist2 >= minDist2) {
                continue;
            }
            nearest = path;
            minDist2 = dist2;
        }
        return nearest;
    }
    
    public void removePathWaypoint(final UUID id, final int index) {
        this.removePathWaypoint(id, index, false);
    }
    
    public void unloadPathWaypoint(final UUID id, final int index) {
        this.removePathWaypoint(id, index, true);
    }
    
    private void removePathWaypoint(final UUID id, final int index, final boolean unload) {
        final IPrefabPath path = this.getPath(id);
        PrefabPathCollection.LOGGER.at(Level.FINER).log("%s waypoint %s from path %s.%s", unload ? "Unloading" : "Removing", index, this.worldgenId, id);
        if (path == null) {
            PrefabPathCollection.LOGGER.at(Level.SEVERE).log("Path %s.%s not found", this.worldgenId, id);
            return;
        }
        if (unload) {
            path.unloadWaypoint(index);
        }
        else {
            path.removeWaypoint(index, this.worldgenId);
        }
        if (path.length() == 0 || !path.hasLoadedWaypoints()) {
            PrefabPathCollection.LOGGER.at(Level.FINER).log("%s path %s.%s", unload ? "Unloading" : "Removing", this.worldgenId, id);
            this.removePath(id);
        }
    }
    
    public void removePath(final UUID id) {
        final IPrefabPath removed = this.paths.remove(id);
        this.pathsByFriendlyName.get(AssetRegistry.getTagIndex(removed.getName())).remove(removed);
    }
    
    public boolean isEmpty() {
        return this.paths.isEmpty();
    }
    
    public void forEach(final BiConsumer<UUID, IPrefabPath> consumer) {
        this.paths.forEach(consumer);
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    private static class PathSet
    {
        private final List<IPrefabPath> paths;
        
        private PathSet() {
            this.paths = new ObjectArrayList<IPrefabPath>();
        }
        
        public void add(final IPrefabPath path) {
            this.paths.add(path);
        }
        
        public void remove(final IPrefabPath path) {
            this.paths.remove(path);
        }
        
        @Nullable
        public IPrefabPath getNearestPath(@Nonnull final Vector3d position, @Nullable final Set<UUID> disallowedPaths, final ComponentAccessor<EntityStore> componentAccessor) {
            IPrefabPath nearest = null;
            double minDist2 = Double.MAX_VALUE;
            for (int i = 0; i < this.paths.size(); ++i) {
                final IPrefabPath path = this.paths.get(i);
                if (disallowedPaths == null || !disallowedPaths.contains(path.getId())) {
                    final Vector3d nearestWp = path.getNearestWaypointPosition(position, componentAccessor);
                    final double dist2 = position.distanceSquaredTo(nearestWp);
                    if (dist2 < minDist2) {
                        nearest = path;
                        minDist2 = dist2;
                    }
                }
            }
            return nearest;
        }
    }
}
