package org.rug.simpletests.data.smells;

import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.rug.data.characteristics.smells.*;
import org.rug.data.labels.EdgeLabel;
import org.rug.data.labels.VertexLabel;
import org.rug.data.smells.ArchitecturalSmell;
import org.rug.data.smells.CDSmell;
import org.rug.data.smells.HLSmell;
import org.rug.data.smells.UDSmell;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;
import static org.rug.simpletests.TestData.antlr;

@Tag("unitTests")
public class SmellCharacteristicsSetTest {

    @Test
    void testUDCharacteristics() {
        var strength = new Strength();
        var instaGap = new InstabilityGap();

        var uds = antlr.getArchitecturalSmellsIn("3.3").stream()
                .filter(a -> a.getType() == ArchitecturalSmell.Type.UD)
                .map(a -> (UDSmell)a)
                .collect(Collectors.toList());

        assertTrue(uds.size() > 0);

        uds.forEach(ud -> {
            var result = Double.parseDouble(strength.visit(ud));
            assertNotEquals(0, result);
            assertTrue(result <= 1);
            result = Double.parseDouble(instaGap.visit(ud));
            assertNotEquals(0, result);
        });

    }

    @Test
    void testCDCharacteristics(){
        var avrgWeight = new AverageEdgeWeight();
        var numOfEdges = new NumberOfEdges();
        var numOfInher = new NumberOfInheritanceEdges();

        var cds = antlr.getArchitecturalSmellsIn("3.3").stream()
                .filter(a -> a.getType() == ArchitecturalSmell.Type.CD)
                .map(a -> (CDSmell)a)
                .collect(Collectors.toList());

        assertTrue(cds.size() > 0);

        boolean resultB = false;
        for (CDSmell cd : cds) {
            var result = avrgWeight.visit(cd);
            assertNotEquals(0, Double.parseDouble(result));

            result = numOfEdges.visit(cd);
            assertNotEquals(0, Double.parseDouble(result));

            resultB = resultB || Double.parseDouble(numOfInher.visit(cd)) > 0;
        }
        assertTrue(resultB);
    }

    @Test
    void testOverlapRatio(){
        var overlapCD = new OverlapRatio(ArchitecturalSmell.Type.CD);
        var overlapUD = new OverlapRatio(ArchitecturalSmell.Type.UD);
        var overlapHL = new OverlapRatio(ArchitecturalSmell.Type.HL);
        var overlapAll= new OverlapRatio(null);

        var as = antlr.getArchitecturalSmellsIn("3.4");
        for (ArchitecturalSmell a : as){
            var olR = Double.parseDouble(a.accept(overlapAll));
            var olC = Double.parseDouble(a.accept(overlapCD));
            var olH = Double.parseDouble(a.accept(overlapHL));
            var olU = Double.parseDouble(a.accept(overlapUD));
            assertTrue(olR >= olC);
            assertTrue(olR >= olH);
            assertTrue(olR >= olU);
            //System.out.printf("Smell: %s\tOlR: %.2f\tOlC: %.2f\tOlH: %.2f\tOlU: %.2f\n", a, olR, olC, olH, olU);
        }
    }

    @Test
    void testAvrgInternalPathLength(){
        var avrgPathLength = new AverageInternalPathLength();
        HLSmell hl = createMockHL();
        assertEquals(2, Double.parseDouble(avrgPathLength.visit(hl)));
        var as = antlr.getArchitecturalSmellsIn("3.1");
        hl = (HLSmell)as.stream().filter(s -> s.getId() == 17174).findFirst().orElse(null);
        assertNotNull(hl);
        assertEquals(3.31, Double.parseDouble(avrgPathLength.visit(hl)));
    }

    @Test
    void testAffectedRatio(){
        var affectedClassesRatio = new AffectedClassesRatio();
        HLSmell hl = createMockHL();
        assertEquals(0.4, Double.parseDouble(affectedClassesRatio.visit(hl)));
        var as = antlr.getArchitecturalSmellsIn("3.1");
        hl = (HLSmell)as.stream().filter(s -> s.getId() == 17174).findFirst().orElse(null);
        assertNotNull(hl);
        assertEquals(0.58, Double.parseDouble(affectedClassesRatio.visit(hl)));
    }

    HLSmell createMockHL(){
        var graph = TinkerGraph.open();
        var g = graph.traversal();

        var n = 8;
        var affectedPackageV = g.addV(VertexLabel.PACKAGE.toString()).property("name", "test.packageAffected").next();
        var inPackageV = g.addV(VertexLabel.PACKAGE.toString()).property("name", "test.packageAfferent").next();
        var outPackageV = g.addV(VertexLabel.PACKAGE.toString()).property("name", "test.packageEfferent").next();

        Vertex s1 = null, s2 = null, e1 = null, e2 = null;

        List<Vertex> classesV = new ArrayList<>();
        for (int i = 0; i<= n; i++) {
            var v = g.addV(VertexLabel.CLASS.toString()).property("name", "class" + i).next();
            if (i == 0 || i == 1) {
                g.addE(EdgeLabel.BELONGSTO.toString()).from(v).to(inPackageV).next();
                g.addE(EdgeLabel.ISAFFERENTOF.toString()).from(v).to(affectedPackageV).next();
                s1 = s1 == null && i == 0 ? v : s1;
                s2 = s2 == null && i == 1 ? v : s2;
            }else if (i == n - 1 || i == n - 2){
                g.addE(EdgeLabel.BELONGSTO.toString()).from(v).to(outPackageV).next();
                g.addE(EdgeLabel.ISEFFERENTOF.toString()).from(v).to(affectedPackageV).next();
                e1 = e1 == null && i == n - 1 ? v : e1;
                e2 = e2 == null && i == n - 2 ? v : e2;
            }else {
                g.addE(EdgeLabel.BELONGSTO.toString()).from(v).to(affectedPackageV).next();
            }
            classesV.add(v);
        }

        var perms = generatePerm(classesV);
        var rng = new Random(0);
        Collections.shuffle(perms, rng);
        var edgeProb = 0.5;
        var nPaths = 0;
        var maxPaths = 1;
        // Randomly generate maxPaths paths between source and exit nodes
        for (var path : perms){
            if (nPaths <=maxPaths &&
                    (path.get(0).equals(s1) || path.get(0).equals(s2))){
                var to = rng.nextDouble() > edgeProb ? e1 : e2;
                for (int i = 1; i < path.size(); i++){
                    if (!path.get(i).equals(to)){
                        g.addE(EdgeLabel.DEPENDSON.toString()).from(path.get(i - 1)).to(path.get(i))
                                .property("Weight", rng.nextInt(10))
                                .next();
                    }else{
                        nPaths++;
                        break;
                    }
                }
            }
        }

        var smell = g.addV(VertexLabel.SMELL.toString()).property("smellType", ArchitecturalSmell.Type.HL.toString()).next();
        smell.property("vertexType", HLSmell.Level.PACKAGE.toString());
        g.addE(EdgeLabel.HLAFFECTEDPACK.toString()).from(smell).to(affectedPackageV).next();
        g.addE(EdgeLabel.HLIN.toString()).from(smell).to(inPackageV).next();
        g.addE(EdgeLabel.HLOUT.toString()).from(smell).to(outPackageV).next();

        return new HLSmell(smell);
    }


    <E> List<List<E>> generatePerm(List<E> original) {
        if (original.size() == 0) {
            List<List<E>> result = new ArrayList<List<E>>();
            result.add(new ArrayList<E>());
            return result;
        }
        E firstElement = original.remove(0);
        List<List<E>> returnValue = new ArrayList<List<E>>();
        List<List<E>> permutations = generatePerm(original);
        for (List<E> smallerPermutated : permutations) {
            for (int index=0; index <= smallerPermutated.size(); index++) {
                List<E> temp = new ArrayList<E>(smallerPermutated);
                temp.add(index, firstElement);
                returnValue.add(temp);
            }
        }
        return returnValue;
    }
}