package net.ssehub.kernel_haven.feature_effects;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import net.ssehub.kernel_haven.SetUpException;
import net.ssehub.kernel_haven.analysis.AnalysisComponent;
import net.ssehub.kernel_haven.build_model.BuildModel;
import net.ssehub.kernel_haven.code_model.CodeElement;
import net.ssehub.kernel_haven.code_model.SourceFile;
import net.ssehub.kernel_haven.config.Configuration;
import net.ssehub.kernel_haven.feature_effects.PcFinder.VariableWithPcs;
import net.ssehub.kernel_haven.util.Logger;
import net.ssehub.kernel_haven.util.logic.Conjunction;
import net.ssehub.kernel_haven.util.logic.Formula;
import net.ssehub.kernel_haven.util.logic.Variable;

/**
 * A component that creates a mapping variable -> set of all PCs the variable is used in.
 * 
 * @author Adam
 */
public class PcFinder extends AnalysisComponent<VariableWithPcs> {
    
    /**
     * A variable together with all presence conditions it is used in.
     * 
     * @author Adam
     */
    public static class VariableWithPcs {
        private String variable;
        
        private Set<Formula> pcs;

        /**
         * Creates a new {@link VariableWithPcs}.
         * 
         * @param variable The variable name.
         * @param pcs All the PCs that the variable is used in. Must not be <code>null</code>.
         */
        public VariableWithPcs(String variable, Set<Formula> pcs) {
            this.variable = variable;
            this.pcs = pcs;
        }
        
        /**
         * Returns the variable name.
         * 
         * @return The name of the variable.
         */
        public String getVariable() {
            return variable;
        }
        
        /**
         * Returns a set of all presence conditions that this variable is used in.
         * 
         * @return A set of all PCs, never <code>null</code>.
         */
        public Set<Formula> getPcs() {
            return pcs;
        }
        
        @Override
        public String toString() {
            return "PCs[" + variable + "] = " + pcs.toString();
        }
        
    }
    
    private AnalysisComponent<SourceFile> sourceFiles;
    
    private AnalysisComponent<BuildModel> bmComponent;
    
    private PresenceConditionAnalysisHelper helper;

    /**
     * Creates a {@link PcFinder} for the given code model.
     * 
     * @param config The global configuration.
     * @param sourceFiles The code model provider component.
     * 
     * @throws SetUpException If setting up this component fails.
     */
    public PcFinder(Configuration config, AnalysisComponent<SourceFile> sourceFiles) throws SetUpException {
        super(config);
        this.sourceFiles = sourceFiles;
        this.helper = new PresenceConditionAnalysisHelper(config);
    }
    
    /**
     * Creates a {@link PcFinder} for the given code and build model. The build model presence conditions will be
     * added to the code model conditions.
     * 
     * @param config The global configuration.
     * @param sourceFiles The code model provider component.
     * @param bm The build model provider component.
     * 
     * @throws SetUpException If setting up this component fails.
     */
    public PcFinder(Configuration config, AnalysisComponent<SourceFile> sourceFiles, AnalysisComponent<BuildModel> bm)
            throws SetUpException {
        this(config, sourceFiles);
        this.bmComponent = bm;
    }

    @Override
    protected void execute() {
        BuildModel bm = null;
        if (bmComponent != null) {
            bm = bmComponent.getNextResult();
            if (bm != null) {
                Logger.get().logDebug("Calculating presence conditions including information from build model");
            } else {
                Logger.get().logWarning("Should use build information for calculation of presence conditions, "
                        + "but build model provider returned null", "Ignoring build model");
            }
        } else {
            Logger.get().logDebug("Calculating presence conditions without considering build model");
        }
            
        

        Map<String, Set<Formula>> result = new HashMap<>();
        
        SourceFile file;
        while ((file = sourceFiles.getNextResult()) != null) {
            Formula filePc = null;
            if (null != bm) {
                filePc = bm.getPc(file.getPath());
                Logger.get().logDebug("Presence condition for " + file.getPath() + ": " + filePc);
            }
            
            for (CodeElement b : file) {
                findPcsInElement(b, result, filePc, false);
            }
        }
        
        for (Map.Entry<String, Set<Formula>> entry : result.entrySet()) {
            addResult(new VariableWithPcs(entry.getKey(), entry.getValue()));
        }
    }
    
    /**
     * Finds all PCs in an element and recursively in all child elements. Adds the PC to the set for
     * all variables that are found in the PC.
     * 
     * @param element The element to find PCs in.
     * @param result The result to add the PCs to.
     * @param filePc Optional: The presence condition of the file which is currently processed. Will be ignored if it is
     * <tt>null</tt>.
     * @param parentIsRelevant Used for optimization (<tt>true</tt> parent condition is relevant and, thus, also all
     * nested conditions are relevant, <tt>false</tt> this method will check if the condition should be considered).
     */
    private void findPcsInElement(CodeElement element, Map<String, Set<Formula>> result, Formula filePc,
        boolean parentIsRelevant) {
        
        Set<Variable> vars = new HashSet<>();
        Formula pc = element.getPresenceCondition();
        
        if (parentIsRelevant || helper.isRelevant(pc)) {
            // Skip retrieval of variables for nested conditions (last for loop)
            parentIsRelevant = true;
            if (null != filePc) {
                pc = new Conjunction(filePc, pc);
            }
            
            helper.findVars(pc, vars);
            
            for (Variable var : vars)  {
                result.putIfAbsent(var.getName(), new HashSet<>());
                result.get(var.getName()).add(pc);
            }
        }
        
        for (CodeElement child : element.iterateNestedElements()) {
            findPcsInElement(child, result, filePc, parentIsRelevant);
        }
    }

}

@@ -595,16 +595,120 @@
 ogger;%0D%0A
+import net.ssehub.kernel_haven.util.io.TableElement;%0D%0Aimport net.ssehub.kernel_haven.util.io.TableRow;%0D%0A
 import n
@@ -751,24 +751,24 @@
 njunction;%0D%0A
-
 import net.s
@@ -1171,32 +1171,47 @@
  Adam%0D%0A     */%0D%0A
+    @TableRow%0D%0A
     public stati
@@ -1233,24 +1233,34 @@
 eWithPcs %7B%0D%0A
+        %0D%0A
         priv
@@ -1835,32 +1835,85 @@
 .%0D%0A         */%0D%0A
+        @TableElement(name = %22Variable%22, index = 0)%0D%0A
         public S
@@ -2151,32 +2151,32 @@
 e%3Enull%3C/code%3E.%0D%0A
-
          */%0D%0A   
@@ -2164,32 +2164,97 @@
 .%0D%0A         */%0D%0A
+        @TableElement(name = %22Presence conditions%22, index  = 1)%0D%0A
         public S

Annotate VariableWithPcs with @TableRow