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

package com.hypixel.hytale.component.dependency;

import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Iterator;
import com.hypixel.hytale.component.ComponentRegistry;
import java.util.HashSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.Set;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.system.ISystem;

public class DependencyGraph<ECS_TYPE>
{
    @Nonnull
    private final ISystem<ECS_TYPE>[] systems;
    @Nonnull
    private final Map<ISystem<ECS_TYPE>, List<Edge<ECS_TYPE>>> beforeSystemEdges;
    @Nonnull
    private final Map<ISystem<ECS_TYPE>, List<Edge<ECS_TYPE>>> afterSystemEdges;
    @Nonnull
    private final Map<ISystem<ECS_TYPE>, Set<Edge<ECS_TYPE>>> afterSystemUnfulfilledEdges;
    private Edge<ECS_TYPE>[] edges;
    
    public DependencyGraph(@Nonnull final ISystem<ECS_TYPE>[] systems) {
        this.beforeSystemEdges = new Object2ObjectOpenHashMap<ISystem<ECS_TYPE>, List<Edge<ECS_TYPE>>>();
        this.afterSystemEdges = new Object2ObjectOpenHashMap<ISystem<ECS_TYPE>, List<Edge<ECS_TYPE>>>();
        this.afterSystemUnfulfilledEdges = new Object2ObjectOpenHashMap<ISystem<ECS_TYPE>, Set<Edge<ECS_TYPE>>>();
        this.edges = Edge.emptyArray();
        this.systems = systems;
        for (int i = 0; i < systems.length; ++i) {
            final ISystem<ECS_TYPE> system = systems[i];
            this.beforeSystemEdges.put(system, new ObjectArrayList<Edge<ECS_TYPE>>());
            this.afterSystemEdges.put(system, new ObjectArrayList<Edge<ECS_TYPE>>());
            this.afterSystemUnfulfilledEdges.put(system, new HashSet<Edge<ECS_TYPE>>());
        }
    }
    
    @Nonnull
    public ISystem<ECS_TYPE>[] getSystems() {
        return this.systems;
    }
    
    public void resolveEdges(@Nonnull final ComponentRegistry<ECS_TYPE> registry) {
        for (final ISystem<ECS_TYPE> system : this.systems) {
            for (final Dependency<ECS_TYPE> dependency : system.getDependencies()) {
                dependency.resolveGraphEdge(registry, system, this);
            }
            if (system.getGroup() != null) {
                for (final Dependency<ECS_TYPE> dependency : system.getGroup().getDependencies()) {
                    dependency.resolveGraphEdge(registry, system, this);
                }
            }
        }
        for (final ISystem<ECS_TYPE> system : this.systems) {
            if (this.afterSystemEdges.get(system).isEmpty()) {
                int priority = 0;
                final List<Edge<ECS_TYPE>> edges = this.beforeSystemEdges.get(system);
                for (final Edge<ECS_TYPE> edge : edges) {
                    priority += edge.priority / edges.size();
                }
                this.addEdgeFromRoot(system, priority);
            }
        }
    }
    
    public void addEdgeFromRoot(@Nonnull final ISystem<ECS_TYPE> afterSystem, final int priority) {
        this.addEdge(new Edge<ECS_TYPE>(null, afterSystem, priority));
    }
    
    public void addEdge(@Nonnull final ISystem<ECS_TYPE> beforeSystem, @Nonnull final ISystem<ECS_TYPE> afterSystem, final int priority) {
        this.addEdge(new Edge<ECS_TYPE>(beforeSystem, afterSystem, priority));
    }
    
    public void addEdge(@Nonnull final Edge<ECS_TYPE> edge) {
        final int index = Arrays.binarySearch(this.edges, edge);
        int insertionPoint;
        if (index >= 0) {
            for (insertionPoint = index; insertionPoint < this.edges.length; ++insertionPoint) {
                if (this.edges[insertionPoint].priority != edge.priority) {
                    break;
                }
            }
        }
        else {
            insertionPoint = -(index + 1);
        }
        final int oldLength = this.edges.length;
        final int newLength = oldLength + 1;
        if (oldLength < newLength) {
            this.edges = Arrays.copyOf(this.edges, newLength);
        }
        System.arraycopy(this.edges, insertionPoint, this.edges, insertionPoint + 1, oldLength - insertionPoint);
        this.edges[insertionPoint] = edge;
        if (edge.beforeSystem != null) {
            this.beforeSystemEdges.get(edge.beforeSystem).add(edge);
        }
        this.afterSystemEdges.get(edge.afterSystem).add(edge);
        if (!edge.fulfilled) {
            this.afterSystemUnfulfilledEdges.get(edge.afterSystem).add(edge);
        }
    }
    
    public void sort(@Nonnull final ISystem<ECS_TYPE>[] sortedSystems) {
        int index = 0;
    Label_0002:
        while (index < this.systems.length) {
            for (final Edge<ECS_TYPE> edge : this.edges) {
                if (!edge.resolved) {
                    if (edge.fulfilled) {
                        final ISystem<ECS_TYPE> system = edge.afterSystem;
                        if (this.afterSystemUnfulfilledEdges.get(system).isEmpty()) {
                            if (!this.hasEdgeOfLaterPriority(system, edge.priority)) {
                                this.resolveEdgesFor(sortedSystems[index++] = system);
                                this.fulfillEdgesFor(system);
                                continue Label_0002;
                            }
                        }
                    }
                }
            }
            for (final Edge<ECS_TYPE> edge : this.edges) {
                if (!edge.resolved) {
                    if (edge.fulfilled) {
                        final ISystem<ECS_TYPE> system = edge.afterSystem;
                        if (this.afterSystemUnfulfilledEdges.get(system).isEmpty()) {
                            this.resolveEdgesFor(sortedSystems[index++] = system);
                            this.fulfillEdgesFor(system);
                            continue Label_0002;
                        }
                    }
                }
            }
            throw new IllegalArgumentException("Found a cyclic dependency!" + String.valueOf(this));
        }
    }
    
    private boolean hasEdgeOfLaterPriority(@Nonnull final ISystem<ECS_TYPE> system, final int priority) {
        for (final Edge<ECS_TYPE> edge : this.afterSystemEdges.get(system)) {
            if (edge.resolved) {
                continue;
            }
            if (edge.priority > priority) {
                return true;
            }
        }
        return false;
    }
    
    private void resolveEdgesFor(@Nonnull final ISystem<ECS_TYPE> system) {
        for (final Edge<ECS_TYPE> edge : this.afterSystemEdges.get(system)) {
            edge.resolved = true;
        }
    }
    
    private void fulfillEdgesFor(@Nonnull final ISystem<ECS_TYPE> system) {
        for (final Edge<ECS_TYPE> edge : this.beforeSystemEdges.get(system)) {
            edge.fulfilled = true;
            this.afterSystemUnfulfilledEdges.get(edge.afterSystem).remove(edge);
        }
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "DependencyGraph{systems=" + Arrays.toString(this.systems) + ", edges=" + Arrays.toString(this.edges);
    }
    
    private static class Edge<ECS_TYPE> implements Comparable<Edge<ECS_TYPE>>
    {
        @Nonnull
        private static final Edge<?>[] EMPTY_ARRAY;
        @Nullable
        private final ISystem<ECS_TYPE> beforeSystem;
        private final ISystem<ECS_TYPE> afterSystem;
        private final int priority;
        private boolean fulfilled;
        private boolean resolved;
        
        public static <ECS_TYPE> Edge<ECS_TYPE>[] emptyArray() {
            return (Edge<ECS_TYPE>[])Edge.EMPTY_ARRAY;
        }
        
        public Edge(@Nullable final ISystem<ECS_TYPE> beforeSystem, @Nonnull final ISystem<ECS_TYPE> afterSystem, final int priority) {
            this.beforeSystem = beforeSystem;
            this.afterSystem = afterSystem;
            this.priority = priority;
            this.fulfilled = (beforeSystem == null);
        }
        
        @Override
        public int compareTo(@Nonnull final Edge<ECS_TYPE> o) {
            return Integer.compare(this.priority, o.priority);
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "Edge{beforeSystem=" + String.valueOf(this.beforeSystem) + ", afterSystem=" + String.valueOf(this.afterSystem) + ", priority=" + this.priority + ", fulfilled=" + this.fulfilled + ", resolved=" + this.resolved;
        }
        
        static {
            EMPTY_ARRAY = new Edge[0];
        }
    }
}
