package fr.monlouyan.bakefile;

import java.io.File;
import java.util.List;

/**
 * Représente une règle du fichier Bakefile.
 * Chaque règle est composée d'un nom, d'une liste de dépendances et d'une liste de commandes.
 * Une règle peut être phony, c'est-à-dire qu'elle n'a pas de fichier cible associé.
 * 
 * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI
 * @version 1.0
 */
public class Rule {

	/*** Nom de la règle */
    private String name;

	/*** Liste des dépendances de la règle */
    private List<String> dependencies;
	
	/*** Liste des commandes */
    private List<String> commands;
    
    /*** Liste des commandes d'affichage (pour préserver la mise en page originale) */
    private List<List<String>> displayCommands;

	/*** true si la règle est phony, false sinon */
    private boolean isPhony;

	/**
	 * Constructeur de la classe Rule.
	 * @param name Nom de la règle
	 * @param dependencies Liste des dépendances
	 * @param commands Liste des commandes
     * @param displayCommands Liste des commandes formatées pour affichage
	 * @param isPhony true si la règle est phony, false sinon
	 */
    public Rule(String name, List<String> dependencies, List<String> commands, 
               List<List<String>> displayCommands, boolean isPhony) {
        this.name = name;
        this.dependencies = dependencies;
        this.commands = commands;
        this.displayCommands = displayCommands;
        this.isPhony = isPhony;
    }

	/**
	 * Récupère le nom de la règle.
	 * @return Le nom de la règle
	 */
    public String getName() { return name; }

	/**
	 * Récupère la liste des dépendances de la règle.
	 * @return La liste des dépendances
	 */
    public List<String> getDependencies() { return dependencies; }

	/**
	 * Récupère la liste des commandes de la règle.
	 * @return La liste des commandes
	 */
    public List<String> getCommands() { return commands; }
    
    /**
	 * Récupère la liste des commandes formatées pour affichage.
	 * @return La liste des commandes formatées
	 */
    public List<List<String>> getDisplayCommands() { return displayCommands; }

	/**
	 * Vérifie si la règle est phony.
	 * @return true si la règle est phony, false sinon.
	 */
    public boolean isPhony() { return isPhony; }

	/**
	 * Vérifie si la règle est vide (sans dépendances ni commandes).
	 * @return true si la règle est vide, false sinon
	 */
    public boolean isEmpty() { return dependencies.isEmpty() && commands.isEmpty(); }

	/**
	 * Vérifie si la règle doit être mise à jour.
	 * @return true si la règle doit être mise à jour, false sinon
	 */
    public boolean needsUpdate() {
        if (BakeCLI.isDebug()){
            System.out.println("Debug : Checking if rule " + name + " needs update");
            System.out.println("Debug : Rule has " + commands.size() + " commands:");
            for (int i = 0; i < commands.size(); i++) {
                System.out.println("Debug : Command " + (i+1) + ": [" + commands.get(i) + "]");
            }
            System.out.println("Debug : Rule has " + dependencies.size() + " dependencies:");
            for (String dep : dependencies) {
                System.out.println("Debug : Dependency: [" + dep + "]");
            }
            File targetFile = new File(cleanFileName(name));
            System.out.println("Debug : Target file '" + name + "' exists: " + targetFile.exists());
        }

		boolean isRequestedTarget = BakeCLI.getTargets().contains(name) || 
                            (BakeCLI.getTargets().isEmpty() && name.equals(BakefileParser.getFirstTarget()));
		
		// Traiter les chemins commençant par ~ de manière spéciale
		if (name.startsWith("~") && !isRequestedTarget) {
			return false;  
		}
    
        // Les règles phony sont toujours mises à jour
        if (isPhony) {
            if (BakeCLI.isDebug()) {
                System.out.println("Debug : Rule " + name + " is phony, always needs update");
            }
            return true;
        }
    
        // Vérifier d'abord toutes les dépendances avant d'exécuter quoi que ce soit
        for (String dependency : dependencies) {
            
            File depFile = new File(cleanFileName(dependency));
            boolean hasRule = BakeEngine.hasRule(dependency);
            if (!depFile.exists() && !dependency.isEmpty() && !hasRule) {
                System.out.println("bake: *** No rule to make target `" + dependency + "', needed by `" + name + "'.  Stop.");
                System.exit(2);
            }
        }

        // Si le fichier cible n'existe pas et qu'il y a des commandes, il doit être mis à jour
        File targetFile = new File(cleanFileName(name));
        if (!targetFile.exists() && !commands.isEmpty()) {
            if (BakeCLI.isDebug()) {
                System.out.println("Debug : Target file " + name + " does not exist and has commands, needs update");
            }
            return true;
        }
    
        // Si la règle n'a pas de commandes, on vérifie seulement si une dépendance doit être mise à jour
        if (commands.isEmpty()) {
            for (String dependency : dependencies) {
                
                Rule depRule = BakeEngine.getRule(dependency);
                if (depRule != null && depRule.needsUpdate()) {
                    if (BakeCLI.isDebug()) {
                        System.out.println("Debug : Dependency rule " + dependency + " needs update");
                    }
                    return true;
                }
            }
            return false;
        }
    
        // Pour les règles avec des commandes, on vérifie aussi les timestamps
        if (BakeCLI.isDebug()){
            System.out.println("Debug : Checking if target file " + name + " exist and is up to date");
        }
    
        long targetTimestamp = targetFile.exists() ? TimestampManager.getTimestamp(targetFile) : 0;
        
        if (BakeCLI.isDebug()) {
            System.out.println("Debug : Target file '" + name + "' last modified at " + TimestampManager.formatTimestamp(targetTimestamp));
        }
    
        long currentTime = System.currentTimeMillis();
        
        for (String dependency : dependencies) {
            
            File depFile = new File(cleanFileName(dependency));
            if (!depFile.exists()) {
                continue;
            }
            
            long depTimestamp = TimestampManager.getTimestamp(depFile);
    
            // Vérifier si le timestamp de la dépendance est dans le futur
            if (depTimestamp > currentTime) {
                // Avertissement similaire à make
                System.out.println("bake: Warning: File '" + dependency + "' has modification time " 
                                  + ((depTimestamp - currentTime) / 1000) + " s in the future");
                if (BakeCLI.isDebug()) {
                    System.out.println("Debug : Dependency " + dependency + " has a timestamp in the future, needs update");
                }
                return true;
            }
    
            if (BakeCLI.isDebug()) {
                System.out.println("Debug : Dependency '" + dependency + "' last modified at " + TimestampManager.formatTimestamp(depTimestamp));
            }
    
            // Si la dépendance est une règle et qu'elle a besoin d'être mise à jour
            Rule depRule = BakeEngine.getRule(dependency);
            if (depRule != null && depRule.needsUpdate()) {
                if (BakeCLI.isDebug()) {
                    System.out.println("Debug : Dependency rule " + dependency + " needs update");
                }
                return true;
            }
    
            // Vérifier les timestamps seulement si le fichier existe
            if (depTimestamp > targetTimestamp) {
                if (BakeCLI.isDebug()) {
                    System.out.println("Debug : Dependency " + dependency + " is newer than target file " + name + ", needs update");
                }
                return true;
            }
        }
        
        return false;
    }

	/**
	* Nettoie un nom de fichier en enlevant les guillemets si nécessaire.
	* @param name Le nom du fichier à nettoyer
	* @return Le nom du fichier sans guillemets
	*/
   private String cleanFileName(String name) {
	   // Enlever les guillemets au début et à la fin si présents
	   if (name.startsWith("\"") && name.endsWith("\"")) {
		   return name.substring(1, name.length() - 1);
	   }
	   return name;
   }
}