package fr.monlouyan.bakefile;

import java.util.*;

/**
 * Résolveur de dépendances entre les règles du fichier Bakefile.
 * Cette classe est responsable de la résolution des dépendances entre les règles
 * pour déterminer l'ordre dans lequel les règles doivent être
 * 
 * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI
 * @version 1.0
 */
public class DependencyResolver {

	/*** true si le mode debug est activé, false sinon */
    private boolean debug;

	/*** Map des règles indexées par leur nom */
    private Map<String, Rule> ruleMap;

	/*** true si une dépendance circulaire est détectée, false sinon */
    private boolean isCircular;
	
	/**
	 * Constructeur de la classe DependencyResolver.
	 * @param debug true si le mode debug est activé, false sinon
	 */
    public DependencyResolver(boolean debug) {
        this.debug = debug;
        this.isCircular = false;
    }

	/**
	 * Résout les dépendances entre les règles.
	 * @param allRules Liste de toutes les règles
	 * @param requestedRules Liste des règles demandées
	 * @return Liste 
	 */
    public List<Rule> resolve(List<Rule> allRules, List<String> requestedRules) {
        List<Rule> rulesToBuild = new ArrayList<>();
        ruleMap = new HashMap<>();
        
        // Construire la map des règles
        for (Rule rule : allRules) {
            ruleMap.put(rule.getName(), rule);
        }
    
        // Premier passage : détection et suppression des cycles
        Set<String> visited = new HashSet<>();
        Set<String> stack = new HashSet<>();
        
        List<String> targetRules = requestedRules.isEmpty() ? 
            Collections.singletonList(BakefileParser.getFirstTarget()) : 
            requestedRules;

        for (String ruleName : targetRules) {
            if (ruleMap.containsKey(ruleName)) {
                detectCycle(ruleName, visited, stack, ruleName);
            } else {
				if (debug) {
					System.out.println("Warning: Rule '" + ruleName + "' not found.");
				}
				System.out.println("bake: *** No rule to make target '" + ruleName + "'. Stop.");
				System.exit(2);
            }
        }
    
        // Deuxième passage : collecte des dépendances dans l'ordre
        Set<String> processed = new HashSet<>();
        List<String> buildOrder = new ArrayList<>();
        
        for (String ruleName : targetRules) {
            topologicalSort(ruleName, processed, buildOrder);
        }
        
        // Construire la liste finale des règles dans l'ordre
        for (String ruleName : buildOrder) {

            
            Rule rule = ruleMap.get(ruleName);
            if (rule != null) {
                rulesToBuild.add(rule);
            }
        }
    
        return rulesToBuild;
    }

	/**
	 * Méthode de tri topologique pour résoudre les dépendances.
	 * @param ruleName Nom de la règle
	 * @param processed Set des règles déjà traitées
	 * @param buildOrder Liste des règles dans l'ordre
	 */
    private void topologicalSort(String ruleName, Set<String> processed, List<String> buildOrder) {
        if (!ruleMap.containsKey(ruleName) || processed.contains(ruleName)) {
            return;
        }
        
        processed.add(ruleName);
        Rule rule = ruleMap.get(ruleName);
        
        // D'abord traiter les dépendances
        for (String dep : rule.getDependencies()) {
            topologicalSort(dep, processed, buildOrder);
        }
        
        // Puis ajouter la règle courante
        buildOrder.add(ruleName);
    }

    /**
	 * Méthode de détection des cycles dans les dépendances.
	 * @param ruleName Nom de la règle
	 * @param visited Set des règles déjà visitées
	 * @param stack Set des règles en cours de traitement
	 * @param parent Nom de la règle parente
	 */
    private void detectCycle(String ruleName, Set<String> visited, Set<String> stack, String parent) {
        if (stack.contains(ruleName)) {
            if (parent != null) { 
                System.out.println("bake: Circular " + parent + " <- " + ruleName + " dependency dropped.");
                this.isCircular = true;
                Rule parentRule = ruleMap.get(parent);
                if (parentRule != null) {
                    parentRule.getDependencies().remove(ruleName);
                }
            }
            return;
        }
    
        if (!visited.contains(ruleName)) {
            visited.add(ruleName);
            stack.add(ruleName);
    
            Rule rule = ruleMap.get(ruleName);
            if (rule != null) {
                List<String> dependenciesCopy = new ArrayList<>(rule.getDependencies());
                for (String dependency : dependenciesCopy) {
                    
                    if (ruleMap.containsKey(dependency)) {
                        detectCycle(dependency, visited, stack, ruleName);
                    }
                }
            }
            stack.remove(ruleName);
        }
    }

	/**
	 * Vérifie si une dépendance circulaire a été détectée.
	 * @return true si une dépendance circulaire a été détectée, false sinon
	 */
    public boolean isCircular() {
        return isCircular;
    }
}