package src.fr.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import src.fr.api.PartType;
import src.fr.api.CompatibilityManager;

public class CompatibilityManagerImpl implements CompatibilityManager {

    private HashMap<PartType, Set<PartType>> incompatibilities;
    private HashMap<PartType, Set<PartType>> requirements;

    public CompatibilityManagerImpl() {
        this.incompatibilities = new HashMap<>();
        this.requirements = new HashMap<>();
    }

    @Override
    public void addIncompatibilities(PartType reference, Set<PartType> target){
        //Précondition
        Objects.requireNonNull(reference);
        Objects.requireNonNull(target);


        Set<PartType> incompa = this.getIncompatibilities(reference);
        Set<PartType> require = this.getRequirements(reference);

        if(incompa.isEmpty()){
            incompatibilities.put(reference, target);

        }else {
            for(PartType x: target){
                if (incompa.contains(x)){
                    System.out.println("We have it already");
                }else {
                    if(require.contains(x)){
                        System.out.println("Cannot remove");
                    }else{
                        //Ajout ref à x
                        incompa.add(x);
                        System.out.println("Add "+reference.getName() +" incompatible with "+x.getName());
                        this.incompatibilities.put(reference, incompa);
                        // Symétrie
                        //Ajoute x à ref
                        //this.getIncompatibilities(x).add(reference);
                        //System.out.println("Add "+x.getName() +" incompatible with "+reference.getName());
                    }
                }
            }
        }
    }

    @Override
    public void removeIncompatibility(PartType reference, PartType target){
        //Précondition
        Objects.requireNonNull(reference);
        Objects.requireNonNull(target);

        Set<PartType> incompa = this.getIncompatibilities(reference);
        if(incompa.isEmpty()){
            System.out.println("No part incompatible");
        }else{
            if(incompa.contains(target)){
                incompa.remove(target);
                this.incompatibilities.replace(reference, incompa);
                // SyMéTrIe
                //Remove target -> ref
                //this.getIncompatibilities(target).remove(reference);
            }else {
                System.out.println("This part doesn't exist in incompatibilities list");
            }
        }
    }

    @Override
    public void addRequirements(PartType reference, Set<PartType> target){
        //Précondition
        Objects.requireNonNull(reference);
        Objects.requireNonNull(target);

        Set<PartType> incompa = this.getIncompatibilities(reference);
        Set<PartType> require = this.getRequirements(reference);
        if(require.isEmpty()){
            requirements.put(reference, target);
        }else {
            for(PartType x: target){
                if (require.contains(x)){
                    System.out.println("We have it already");
                }else {
                    if(incompa.contains(x)){
                        System.out.println("Cannot remove");
                    }else{
                        //Ajout ref à x
                        require.add(x);
                        System.out.println("Add "+reference.getName() +" compatible with "+x.getName());
                        
                        this.requirements.put(reference, require);
                        //Symétrie
                        //Ajoute x à ref
                        //this.getRequirements(x).add(reference);
                        //System.out.println("Add "+x.getName() +" compatible with "+reference.getName());
                    }
                }
            }
        }

    }
    
    @Override
    public void removeRequirement(PartType reference, PartType target){
        //Précondition
        Objects.requireNonNull(reference);
        Objects.requireNonNull(target);

        Set<PartType> require = this.getRequirements(reference);
        if(require.isEmpty()){
            System.out.println("No part compatible");
        }else{
            if(require.contains(target)){
                require.remove(target);
                this.requirements.replace(reference, require);
                // SyMéTrIe
                //Remove target -> ref
                //this.getRequirements(target).remove(reference);
            }else {
                System.out.println("This part doesn't exist in compatibilities list");
            }
        }
    }
    
    // A -> B -> C -> A, D -> B
    //erquirement only 
    private void Femeture_Transivite(PartType reference, Set<PartType> result, Set<PartType> visited, PartType start){
        //Condition de quitter la boucle
        if(visited.contains(reference)){
            return;
        }else {
            visited.add(reference);
        }

        Set<PartType> list = null;
        //Get all requirement of A (in example is B)
        list = this.requirements.get(reference);

        if(list != null){ // list = {B}
            for(PartType pt: list){
                if(!result.contains(pt)&& pt !=start){
                    result.add(pt);
                }
                Femeture_Transivite(pt,result,visited,reference);
            }

        }
    }

    @Override
    public Set<PartType> getIncompatibilities(PartType reference) {
        Set<PartType> result = new HashSet<>();
        
        // Remove the case of result become null
        Set<PartType> directIncompa = this.incompatibilities.get(reference);
        if(directIncompa!=null){
            result.addAll(directIncompa);
        }

        // Collect the requirements list
        Set<PartType> requirements = new HashSet<>();
        Femeture_Transivite(reference, requirements,new HashSet<>(),reference);

        for(Map.Entry<PartType, Set<PartType>> entry : this.incompatibilities.entrySet()){
            // check start
            if(entry.getValue().contains(reference)){
                result.add(entry.getKey());
            }
                
            // check incompatible of A's requirements
            for(PartType pt: requirements){
                // In Catalog of pt
                if(entry.getValue().contains(pt)){
                    result.add(entry.getKey());
                }
                // Direct of pt
                Set<PartType> direct_pt = this.incompatibilities.get(pt);
                if(direct_pt!=null){
                    result.addAll(direct_pt);
                }
            }
            
        }
        
        return result;
    }


    @Override
    public Set<PartType> getRequirements(PartType reference) {
        Set<PartType> result = new HashSet<>();
        Femeture_Transivite(reference, result,new HashSet<>(),reference);
        return result;
    }
}