package org.rug.data.characteristics.smells;

import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.rug.data.labels.EdgeLabel;
import org.rug.data.labels.VertexLabel;
import org.rug.data.project.ArcanDependencyGraphParser;
import org.rug.data.smells.ArchitecturalSmell;
import org.rug.data.smells.CDSmell;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * This characteristic computes whether a smell at the architectural level is also present at design level.
 * For example, is a cycle only among packages, or it is also among the classes belonging to those packages?
 */
public class AffectedDesign extends AbstractSmellCharacteristic{

    public AffectedDesign() {
        super("affectedDesignLevel");
    }

    @Override
    public String visit(CDSmell smell) {
        if (smell.getLevel().isDesignLevel()){
            return Level.DESIGN.toString();
        } else {
            GraphTraversalSource g = smell.getTraversalSource();
            // Get list of smells affecting the system and filter out smells that are not relevant.
            // Then check if all the affected classes of the relevant ones are contained
            // in all the packages of the given smell.
            // If so, the smell is present at both levels.
            List<ArchitecturalSmell> smellList = ArcanDependencyGraphParser.getArchitecturalSmellsIn(smell.getAffectedGraph());
            Set<ArchitecturalSmell> classLevelSmells = smellList.stream()
                    .filter(s -> s.getType() == ArchitecturalSmell.Type.CD && s.getLevel().isDesignLevel())
                    .filter(s ->
                        g.V(s.getAffectedElements())
                                .out(EdgeLabel.BELONGSTO.toString())
                                .hasLabel(P.within(VertexLabel.getComponentStrings()))
                                .toSet().equals(smell.getAffectedElements()))
                    .collect(Collectors.toSet());

            return classLevelSmells.size() > 0 ? Level.DESIGN_AND_ARCH.toString() : Level.ARCHITECTURAL.toString();
        }
    }

    /**
     * Whether the smell is present only at architectural level (between packages)
     * or also at design level.
     */
    public enum Level{
        ARCHITECTURAL("architecturalOnly"),
        DESIGN("designOnly"),
        DESIGN_AND_ARCH("designAndArch");

        private String level;

        Level(String level){
            this.level = level;
        }

        @Override
        public String toString() {
            return level;
        }
    }
}
