package speiger.src.coreengine.utils.collections.registries;

import java.util.List;
import java.util.Map;
import java.util.Set;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectLinkedOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.utils.maps.Object2ObjectMaps;

public class DependencyResolver {
	public static <T> void resolveDependencies(Object2ObjectMap<T, T> map, Set<T> values) {
		Map<T, Entry<T>> results = new Object2ObjectLinkedOpenHashMap<T, Entry<T>>();
		for(Object2ObjectMap.Entry<T, T> entry : Object2ObjectMaps.fastIterable(map)) {
			results.put(entry.getKey(), new Entry<T>(entry.getKey(), entry.getValue()));
		}
		for(Entry<T> entry : results.values()) {
			Entry<T> newer = results.get(entry.value);
			if(newer != null) newer.addChild(entry);
		}
		for(Entry<T> entry : results.values()) {
			if(entry.isLoop()) entry.clearLoop(values);
		}
		if(!results.entrySet().removeIf(T -> T.getValue().parent != null)) return;
		map.clear();
		for(Entry<T> root : results.values()) {
			root.addToMap(map);
		}
	}
	
	static class Entry<T> {
		T key;
		T value;
		Entry<T> parent = null;
		List<Entry<T>> children = new ObjectArrayList<>();
		
		public Entry(T key, T value) {
			this.key = key;
			this.value = value;
		}
		
		public void addChild(Entry<T> child) {
			if(child != null) {
				children.add(child);
				child.parent = this;
			}
		}
		
		public void addToMap(Map<T, T> map) {
			addToMap(value, map);
		}
		
		protected void addToMap(T newValue, Map<T, T> map) {
			map.put(key, newValue);
			for(int i = 0,m = children.size();i < m;i++) {
				addToMap(newValue, map);
			}
		}
		
		protected boolean isLoop() {
			if(parent == null) return false;
			Entry<T> current = parent;
			while(current.parent != null) {
				current = current.parent;
				if(current == parent) return true;
			}
			return false;
		}
		
		protected void clearLoop(Set<T> keys) {
			if(keys.contains(value)) {
				parent = null;
				return;
			}
			parent.clearLoop(keys);
		}
	}
}
