package org.openlca.util;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import org.openlca.core.database.IDatabase;
import org.openlca.core.database.ProcessDao;
import org.openlca.core.model.ProcessLink;
import org.openlca.core.model.ProductSystem;
import org.slf4j.LoggerFactory;

public final class ProductSystems {

	private ProductSystems() {
	}

	/**
	 * Returns true when the given product system has links to library
	 * processes.
	 */
	public static boolean hasLibraryLinks(ProductSystem sys, IDatabase db) {
		if (sys == null || db == null)
			return false;
		if (sys.referenceProcess != null
				&& sys.referenceProcess.isFromLibrary())
			return true;
		var processes = new ProcessDao(db).descriptorMap();
		for (var processID : sys.processes) {
			var process = processes.get(processID);
			if (process != null && process.isFromLibrary())
				return true;
		}
		return false;
	}

	/**
	 * Removes unreachable processes and process links from the given system. It
	 * traverses the links of the system starting from the reference process and
	 * removes all links and processes that cannot be reached by this traversal.
	 */
	public static void tidy(ProductSystem system) {
		var log = LoggerFactory.getLogger(ProductSystems.class);
		log.info("cleanup product system: {}", system);

		if (system.referenceProcess == null) {
			log.error("system has no reference process");
			return;
		}
		long refProcess = system.referenceProcess.id;

		var linkMap = new HashMap<Long, List<ProcessLink>>();
		for (var link : system.processLinks) {
			linkMap.computeIfAbsent(link.processId, pid -> new ArrayList<>())
					.add(link);
		}

		var validProcesses = new HashSet<Long>();
		var validLinks = new ArrayList<ProcessLink>();
		var queue = new ArrayDeque<Long>();
		queue.add(refProcess);
		while (!queue.isEmpty()) {
			var next = queue.poll();
			if (validProcesses.contains(next))
				continue;
			validProcesses.add(next);

			var inLinks = linkMap.get(next);
			if (inLinks == null)
				continue;
			validLinks.addAll(inLinks);
			for (var link : inLinks) {
				var provider = link.providerId;
				if (!validProcesses.contains(provider)
						&& !queue.contains(provider)) {
					queue.add(provider);
				}
			}
		}

		int processRemovals = system.processes.size() - validProcesses.size();
		system.processes.clear();
		system.processes.addAll(validProcesses);
		int linkRemovals = system.processLinks.size() - validLinks.size();
		system.processLinks.clear();
		system.processLinks.addAll(validLinks);
		log.info("removed {} unreachable processes and {} links",
				processRemovals, linkRemovals);
	}

}

@@ -2694,10 +2694,903 @@
 s);%0A%09%7D%0A%0A
+%09public static boolean isConnected(ProductSystem system) %7B%0A%09%09if (system.referenceProcess == null)%0A%09%09%09return false;%0A%09%09var inEdges = new HashMap%3CLong, List%3CProcessLink%3E%3E();%0A%09%09for (var link : system.processLinks) %7B%0A%09%09%09inEdges.computeIfAbsent(link.processId, pid -%3E new ArrayList%3C%3E())%0A%09%09%09%09%09.add(link);%0A%09%09%7D%0A%0A%09%09var visited = new HashSet%3CLong%3E();%0A%09%09var queue = new ArrayDeque%3CLong%3E();%0A%09%09queue.add(system.referenceProcess.id);%0A%09%09while (!queue.isEmpty()) %7B%0A%09%09%09var next = queue.poll();%0A%09%09%09if (visited.contains(next))%0A%09%09%09%09continue;%0A%09%09%09visited.add(next);%0A%09%09%09var inLinks = inEdges.get(next);%0A%09%09%09if (inLinks == null)%0A%09%09%09%09continue;%0A%09%09%09for (var link : inLinks) %7B%0A%09%09%09%09var provider = link.providerId;%0A%09%09%09%09if (!visited.contains(provider) && !queue.contains(provider)) %7B%0A%09%09%09%09%09queue.add(provider);%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D%0A%0A%09%09return visited.size() == system.processes.size()%0A%09%09%09%09&& visited.containsAll(system.processes);%0A%09%7D%0A
 %7D%0A

add utility method to check if a product system graph is connected