package org.rug.tracker;

import org.rug.data.smells.CDSmell;
import org.rug.data.smells.HLSmell;
import org.rug.data.smells.UDSmell;

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

/**
 * This linker uses the simple names of the classes and or packages rather than their full name.
 * This makes sense because it is quite improbable that a class with the same name to be affected
 * by the same type of smell and have the other elements involved in the smell named the same.
 * In case of homonyms, the threshold of the jaccard score of the set of affected elements
 * should be sufficient to eliminate the uncertainty in selecting the appropriate successor.
 *
 * NOTE: In order to strengthen the linking, this implementation uses all the elements taking part
 * in a HL or UD smell, in contrast to the default implementation.
 */
public class SimpleNameJaccardSimilarityLinker extends JaccardSimilarityLinker {

    /**
     * Builds this linker with the given threshold.
     *
     * @param fewElementsThreshold  the threshold value to use for discarding couples with not enough similarity. This
     *                              threshold is applied when the number of affected elements is less than <code>fewElements</code>.
     * @param moreElementsThreshold the threshold to use for discarding when the affected elements are more than few elements.
     * @param fewElements
     */
    public SimpleNameJaccardSimilarityLinker(double fewElementsThreshold, double moreElementsThreshold, int fewElements) {
        super(fewElementsThreshold, moreElementsThreshold, fewElements);
    }

    /**
     * Builds a linker with few elements equal to 3, few elements threshold equal to 0.6, and more elements threshold equal to 0.75.
     * These thresholds were chose to protect smells with low elements to be excluded from calculation since variations
     * of a single element when the size is less than three has a great variation on the score computed.
     */
    public SimpleNameJaccardSimilarityLinker() {
    }

    /**
     * Visit the given smell and returns the element of a CD by class or package name.
     *
     * @param smell the CD smell to visit.
     */
    @Override
    public Set<String> visit(CDSmell smell) {
        return smell.getAffectedElementsNames().stream()
                .map(this::getSimpleName)
                .collect(Collectors.toSet());
    }

    /**
     * Visit the given smell.
     *
     * @param smell the HL smell to visit.
     */
    @Override
    public Set<String> visit(HLSmell smell) {
        Set<String> names = smell.getAffectedElementsNames();
        names.addAll(smell.getInDepNames());
        names.addAll(smell.getOutDepNames());
        names = names.stream()
                .map(this::getSimpleName)
                .collect(Collectors.toSet());
        return names;
    }

    /**
     * Visit the given smell.
     *
     * @param smell the UD smell to visit.
     */
    @Override
    public Set<String> visit(UDSmell smell) {
        Set<String> names = smell.getAffectedElementsNames();
        names.addAll(smell.getBadDepNames());
        names = names.stream().map(this::getSimpleName).collect(Collectors.toSet());
        return names;
    }

    /**
     * Returns the simple name of a class or a package.
     * @param fullName the full name in the format root.package.class.
     * @return the simple name or the same string if no '.' character is present.
     */
    private String getSimpleName(String fullName){
        var lastDot = fullName.lastIndexOf('.');
        return fullName.substring(lastDot < 0 ? 0 : lastDot + 1);
    }
}
