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

package com.hypixel.hytale.server.core.plugin.pending;

import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Iterator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collections;
import java.util.List;
import com.hypixel.hytale.server.core.plugin.PluginBase;
import javax.annotation.Nullable;
import java.nio.file.Path;
import com.hypixel.hytale.common.plugin.PluginManifest;
import javax.annotation.Nonnull;
import com.hypixel.hytale.common.plugin.PluginIdentifier;

public abstract class PendingLoadPlugin
{
    @Nonnull
    private final PluginIdentifier identifier;
    @Nonnull
    private final PluginManifest manifest;
    @Nullable
    private final Path path;
    
    PendingLoadPlugin(@Nullable final Path path, @Nonnull final PluginManifest manifest) {
        this.path = path;
        this.identifier = new PluginIdentifier(manifest);
        this.manifest = manifest;
    }
    
    @Nonnull
    public PluginIdentifier getIdentifier() {
        return this.identifier;
    }
    
    @Nonnull
    public PluginManifest getManifest() {
        return this.manifest;
    }
    
    @Nullable
    public Path getPath() {
        return this.path;
    }
    
    public abstract PendingLoadPlugin createSubPendingLoadPlugin(final PluginManifest p0);
    
    @Nullable
    public abstract PluginBase load();
    
    @Nonnull
    public List<PendingLoadPlugin> createSubPendingLoadPlugins() {
        final List<PluginManifest> subPlugins = this.manifest.getSubPlugins();
        if (subPlugins.isEmpty()) {
            return Collections.emptyList();
        }
        final ObjectArrayList<PendingLoadPlugin> plugins = new ObjectArrayList<PendingLoadPlugin>(subPlugins.size());
        for (final PluginManifest subManifest : subPlugins) {
            subManifest.inherit(this.manifest);
            plugins.add(this.createSubPendingLoadPlugin(subManifest));
        }
        return plugins;
    }
    
    public boolean dependsOn(final PluginIdentifier identifier) {
        return this.manifest.getDependencies().containsKey(identifier) || this.manifest.getOptionalDependencies().containsKey(identifier);
    }
    
    public abstract boolean isInServerClassPath();
    
    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        final PendingLoadPlugin that = (PendingLoadPlugin)o;
        return this.identifier.equals(that.identifier) && this.manifest.equals(that.manifest) && Objects.equals(this.path, that.path);
    }
    
    @Override
    public int hashCode() {
        int result = this.identifier.hashCode();
        result = 31 * result + this.manifest.hashCode();
        result = 31 * result + ((this.path != null) ? this.path.hashCode() : 0);
        return result;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "PendingLoadPlugin{identifier=" + String.valueOf(this.identifier) + ", manifest=" + String.valueOf(this.manifest) + ", path=" + String.valueOf(this.path);
    }
    
    @Nonnull
    public static List<PendingLoadPlugin> calculateLoadOrder(@Nonnull final Map<PluginIdentifier, PendingLoadPlugin> pending) {
        final HashMap<PluginIdentifier, EntryNode> nodes = new HashMap<PluginIdentifier, EntryNode>(pending.size());
        for (final Map.Entry<PluginIdentifier, PendingLoadPlugin> entry : pending.entrySet()) {
            nodes.put(entry.getKey(), new EntryNode(entry.getValue()));
        }
        final HashSet<PluginIdentifier> classpathPlugins = new HashSet<PluginIdentifier>();
        for (final Map.Entry<PluginIdentifier, PendingLoadPlugin> entry2 : pending.entrySet()) {
            if (entry2.getValue().isInServerClassPath()) {
                classpathPlugins.add(entry2.getKey());
            }
        }
        final HashMap<PluginIdentifier, Set<PluginIdentifier>> missingDependencies = new HashMap<PluginIdentifier, Set<PluginIdentifier>>();
        for (final EntryNode node : nodes.values()) {
            final PluginManifest manifest = node.plugin.manifest;
            for (final PluginIdentifier depId : manifest.getDependencies().keySet()) {
                if (nodes.containsKey(depId)) {
                    node.edge.add(depId);
                }
                else {
                    missingDependencies.computeIfAbsent(node.plugin.identifier, k -> new HashSet()).add(depId);
                }
            }
            for (final PluginIdentifier identifier : manifest.getOptionalDependencies().keySet()) {
                final EntryNode dep = nodes.get(identifier);
                if (dep != null) {
                    node.edge.add(identifier);
                }
            }
            if (!node.plugin.isInServerClassPath()) {
                node.edge.addAll(classpathPlugins);
            }
        }
        final HashMap<PluginIdentifier, Set<PluginIdentifier>> missingLoadBefore = new HashMap<PluginIdentifier, Set<PluginIdentifier>>();
        for (final Map.Entry<PluginIdentifier, PendingLoadPlugin> entry3 : pending.entrySet()) {
            final PluginManifest manifest2 = entry3.getValue().manifest;
            for (final PluginIdentifier targetId : manifest2.getLoadBefore().keySet()) {
                final EntryNode targetNode = nodes.get(targetId);
                if (targetNode != null) {
                    targetNode.edge.add(entry3.getKey());
                }
                else {
                    missingLoadBefore.computeIfAbsent((PluginIdentifier)entry3.getKey(), k -> new HashSet()).add(targetId);
                }
            }
        }
        if (!missingDependencies.isEmpty() || !missingLoadBefore.isEmpty()) {
            final StringBuilder sb = new StringBuilder();
            if (!missingDependencies.isEmpty()) {
                sb.append("Missing required dependencies:\n");
                for (final Map.Entry<PluginIdentifier, Set<PluginIdentifier>> entry4 : missingDependencies.entrySet()) {
                    sb.append("  ").append(entry4.getKey()).append(" requires: ").append(entry4.getValue()).append("\n");
                }
            }
            if (!missingLoadBefore.isEmpty()) {
                sb.append("Missing loadBefore targets:\n");
                for (final Map.Entry<PluginIdentifier, Set<PluginIdentifier>> entry4 : missingLoadBefore.entrySet()) {
                    sb.append("  ").append(entry4.getKey()).append(" loadBefore: ").append(entry4.getValue()).append("\n");
                }
            }
            throw new IllegalArgumentException(sb.toString());
        }
        final ObjectArrayList<PendingLoadPlugin> loadOrder = new ObjectArrayList<PendingLoadPlugin>(nodes.size());
        while (!nodes.isEmpty()) {
            boolean didWork = false;
            final Iterator<Map.Entry<PluginIdentifier, EntryNode>> iterator = nodes.entrySet().iterator();
            while (iterator.hasNext()) {
                final Map.Entry<PluginIdentifier, EntryNode> entry5 = iterator.next();
                final EntryNode node2 = entry5.getValue();
                if (!node2.edge.isEmpty()) {
                    continue;
                }
                didWork = true;
                iterator.remove();
                loadOrder.add(node2.plugin);
                final PluginIdentifier identifier2 = entry5.getKey();
                for (final EntryNode otherNode : nodes.values()) {
                    otherNode.edge.remove(identifier2);
                }
            }
            if (!didWork) {
                final StringBuilder sb2 = new StringBuilder("Found cyclic dependency between plugins:\n");
                for (final Map.Entry<PluginIdentifier, EntryNode> entry6 : nodes.entrySet()) {
                    sb2.append("  ").append(entry6.getKey()).append(" waiting on: ").append(entry6.getValue().edge).append("\n");
                }
                throw new IllegalArgumentException(sb2.toString());
            }
        }
        return loadOrder;
    }
    
    private static final class EntryNode
    {
        private final Set<PluginIdentifier> edge;
        private final PendingLoadPlugin plugin;
        
        private EntryNode(final PendingLoadPlugin plugin) {
            this.edge = new HashSet<PluginIdentifier>();
            this.plugin = plugin;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "EntryNode{plugin=" + String.valueOf(this.plugin) + ", dependencies=" + String.valueOf(this.edge);
        }
    }
}
