package de.dhbwstuttgart.target.generate;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CycleFinder {
    private CycleFinder() {}

    static Set<GenerateGenerics.TPH> allNodes(Set<? extends GenerateGenerics.Pair> input) {
        return input.stream()
                .filter(GenerateGenerics.PairLT.class::isInstance)
                .map(GenerateGenerics.PairLT.class::cast)
                .flatMap(pair -> Stream.of(pair.left, pair.right)).collect(Collectors.toSet());
    }

    static Set<GenerateGenerics.TPH> outgoingEdgesOf(GenerateGenerics.TPH tph, Set<? extends GenerateGenerics.Pair> input) {
        return input.stream()
                .filter(GenerateGenerics.PairLT.class::isInstance)
                .map(GenerateGenerics.PairLT.class::cast)
                .filter(pair -> pair.left.equals(tph))
                .map(pair -> pair.right).collect(Collectors.toSet());
    }

    static boolean containsEdge(GenerateGenerics.TPH a, GenerateGenerics.TPH b, Set<? extends GenerateGenerics.Pair> input) {
        return input.stream()
                .filter(GenerateGenerics.PairLT.class::isInstance)
                .map(GenerateGenerics.PairLT.class::cast)
                .anyMatch(pair -> pair.left.equals(a) && pair.right.equals(b));
    }

    // Tiernan simple cycles algorithm
    // Adapted from https://github.com/jgrapht/jgrapht/blob/master/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TiernanSimpleCycles.java
    static Set<List<GenerateGenerics.TPH>> findCycles(Set<? extends GenerateGenerics.Pair> input) {
        Map<GenerateGenerics.TPH, Integer> indices = new HashMap<>();
        List<GenerateGenerics.TPH> path = new ArrayList<>();
        Set<GenerateGenerics.TPH> pathSet = new HashSet<>();
        Map<GenerateGenerics.TPH, Set<GenerateGenerics.TPH>> blocked = new HashMap<>();
        Set<List<GenerateGenerics.TPH>> cycles = new HashSet<>();

        int index = 0;
        for (var tph : allNodes(input)) {
            blocked.put(tph, new HashSet<>());
            indices.put(tph, index++);
        }

        var vertexIterator = allNodes(input).iterator();
        if (!vertexIterator.hasNext()) return cycles;

        GenerateGenerics.TPH startOfPath = null;
        GenerateGenerics.TPH endOfPath = vertexIterator.next();
        GenerateGenerics.TPH temp = null;
        int endIndex = 0;
        boolean extensionFound = false;
        path.add(endOfPath);
        pathSet.add(endOfPath);

        while (true) {
            do {
                extensionFound = false;
                for (GenerateGenerics.TPH n : outgoingEdgesOf(endOfPath, input)) {
                    int cmp = indices.get(n).compareTo(indices.get(path.get(0)));
                    if ((cmp > 0) && !pathSet.contains(n) && !blocked.get(endOfPath).contains(n)) {
                        path.add(n);
                        pathSet.add(n);
                        endOfPath = n;
                        extensionFound = true;
                        break;
                    }
                }
            } while (extensionFound);

            startOfPath = path.get(0);
            if (containsEdge(endOfPath, startOfPath, input)) {
                List<GenerateGenerics.TPH> cycle = new ArrayList<>(path);
                cycles.add(cycle);
            }
            if (path.size() > 1) {
                blocked.get(endOfPath).clear();
                endIndex = path.size() - 1;
                path.remove(endIndex);
                pathSet.remove(endOfPath);
                --endIndex;
                temp = endOfPath;
                endOfPath = path.get(endIndex);
                blocked.get(endOfPath).add(temp);
                continue;
            }
            if (vertexIterator.hasNext()) {
                path.clear();
                pathSet.clear();
                endOfPath = vertexIterator.next();
                path.add(endOfPath);
                pathSet.add(endOfPath);
                for (GenerateGenerics.TPH tph : blocked.keySet()) {
                    blocked.get(tph).clear();
                }
                continue;
            }
            break;
        }
        return cycles;
    }
}
