package com.atomist.rug.cli.utils;

import static scala.collection.JavaConversions.asJavaCollection;

import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.util.SystemPropertyUtils;

import com.atomist.rug.cli.Constants;
import com.atomist.rug.cli.Log;
import com.atomist.rug.resolver.ArtifactDescriptor;

import scala.collection.Seq;

public abstract class StringUtils {

    private static final Log log = new Log(StringUtils.class);

    public static void printClosestMatch(String name, ArtifactDescriptor artifact,
            Seq<String> nameOptions) {
        printClosestMatch(name, artifact, asJavaCollection(nameOptions));
    }

    public static void printClosestMatch(String name, ArtifactDescriptor artifact,
            Collection<String> nameOptions) {
        Optional<String> closestMatch = StringUtils.computeClosestMatch(name, nameOptions);
        if (closestMatch.isPresent()) {
            log.newline();
            log.info(Constants.CLOSEST_MATCH_HINT);
            log.info("  %s", StringUtils.stripName(closestMatch.get(), artifact));
        }
    }

    public static Optional<String> computeClosestMatch(String searchTerm,
            Collection<String> searchBase) {
        Map<String, Integer> distances = searchBase.stream().collect(Collectors.toMap(s -> s,
                s -> LevenshteinDistance.computeLevenshteinDistance(searchTerm, s)));
        return distances.entrySet().stream().filter(e -> e.getValue() <= 3)
                .min(Comparator.comparing(Map.Entry::getValue)).map(Map.Entry::getKey);
    }

    public static String puralize(String value, Collection<?> items) {
        return puralize(value, value + "s", items);
    }

    public static String puralize(String singular, String pural, Collection<?> items) {
        if (items.size() > 1) {
            return pural;
        }
        else {
            return singular;
        }
    }

    public static String expandEnvironmentVarsAndHomeDir(String text) {
        if (text == null) {
            return text;
        }
        text = text.replace("~", "${user.home}");
        return expandEnvironmentVars(text);
    }

    public static String expandEnvironmentVars(String text) {
        if (text == null) {
            return text;
        }
        return SystemPropertyUtils.resolvePlaceholders(text);
    }

    public static String stripName(String name, ArtifactDescriptor artifact) {
        return name.replace(artifact.group() + "." + artifact.artifact() + ".", "");
    }

    private static class LevenshteinDistance {
        public static int computeLevenshteinDistance(CharSequence lhs, CharSequence rhs) {
            int[][] distance = new int[lhs.length() + 1][rhs.length() + 1];

            for (int i = 0; i <= lhs.length(); i++)
                distance[i][0] = i;
            for (int j = 1; j <= rhs.length(); j++)
                distance[0][j] = j;

            for (int i = 1; i <= lhs.length(); i++)
                for (int j = 1; j <= rhs.length(); j++)
                    distance[i][j] = minimum(distance[i - 1][j] + 1, distance[i][j - 1] + 1,
                            distance[i - 1][j - 1]
                                    + ((lhs.charAt(i - 1) == rhs.charAt(j - 1)) ? 0 : 1));

            return distance[lhs.length()][rhs.length()];
        }

        private static int minimum(int a, int b, int c) {
            return Math.min(Math.min(a, b), c);
        }
    }

}