package tc.oc.commons.core.formatting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.sk89q.minecraft.util.commands.ChatColor;
import tc.oc.commons.core.LiquidMetal;

public class StringUtils {
    public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 55; // Will never wrap, even with the largest characters

    /**
     * Shorthand for listToEnglishCompound(list, "", "").
     *
     * @see #listToEnglishCompound(java.util.Collection, String, String)
     */
    public static String listToEnglishCompound(Collection<String> list) {
        return listToEnglishCompound(list, "", "");
    }

    /**
     * Converts a list of strings to a nice English list as a string.
     * <p/>
     * For example: In: ["Anxuiz", "MonsieurApple", "Plastix"] Out: "Anxuiz, MonsieurApple and Plastix"
     *
     * @param list   List of strings to concatenate.
     * @param prefix Prefix to add before each element in the resulting string.
     * @param suffix Suffix to add after each element in the resulting string.
     * @return String version of the list of strings.
     */
    public static String listToEnglishCompound(Collection<?> list, String prefix, String suffix) {
        StringBuilder builder = new StringBuilder();
        int i = 0;
        for (Object str : list) {
            if (i != 0) {
                if (i == list.size() - 1) {
                    builder.append(" and ");
                } else {
                    builder.append(", ");
                }
            }
            builder.append(prefix).append(str).append(suffix);
            i++;
        }
        return builder.toString();
    }

    /**
     * Shorthand for listToCompound(list, and, "", "").
     *
     * @see #listToCompound(java.util.Collection, String, String, String)
     */
    public static final String listToCompound(Collection<String> list, String and) {
        return listToCompound(list, and, "", "");
    }

    /**
     * Converts a list of strings to a nice list as a string.
     * <p/>
     * For example: In: ["Anxuiz", "MonsieurApple", "Plastix"] Out: "Anxuiz, MonsieurApple {and} Plastix"
     *
     * @param list   List of strings to concatenate.
     * @param and    String to be used for "and"
     * @param prefix Prefix to add before each element in the resulting string.
     * @param suffix Suffix to add after each element in the resulting string.
     * @return String version of the list of strings.
     */
    public static String listToCompound(Collection<?> list, String and, String prefix, String suffix) {
        StringBuilder builder = new StringBuilder();
        int i = 0;
        for (Object str : list) {
            if (i != 0) {
                if (i == list.size() - 1) {
                    builder.append(" ").append(and).append(" ");
                } else {
                    builder.append(", ");
                }
            }
            builder.append(prefix).append(str).append(suffix);
            i++;
        }
        return builder.toString();
    }

    public static @Nullable <T> T bestFuzzyMatch(String query, Iterable<T> choices, double threshold, Function<? super T, String> stringifier) {
        T result = null;
        for(T choice : choices) {
            if(threshold <= LiquidMetal.score(stringifier.apply(choice), query)) {
                if(result != null) return null; // multiple matches
                result = choice;
            }
        }
        return result;
    }

    public static @Nullable <T> T bestFuzzyMatch(String search, Iterable<T> choices, double threshold) {
        return bestFuzzyMatch(search, choices, threshold, Object::toString);
    }

    public static @Nullable <T> T bestFuzzyMatch(String query, Map<String, T> choices, double threshold) {
        return fuzzyMatch(query, choices, threshold)
            .orElse(null);
    }

    public static <T> Optional<T> fuzzyMatch(String query, Iterable<T> choices, double threshold, Function<? super T, String> stringifier) {
        return Optional.ofNullable(bestFuzzyMatch(query, choices, threshold, stringifier));
    }

    public static <T> Optional<T> fuzzyMatch(String query, Iterable<T> choices, double threshold) {
        return fuzzyMatch(query, choices, threshold, Object::toString);
    }

    public static <T> Optional<T> fuzzyMatch(String query, Map<String, T> choices, double threshold) {
        return fuzzyMatch(query, choices.entrySet(), threshold, Map.Entry::getKey)
            .map(Map.Entry::getValue);
    }

    /**
     * Sanitizes the provided message, removing any non-alphanumeric characters and swapping spaces with the specified
     * string.
     * <p/>
     * Examples: sanitize("Hello! :) How are you?", '-') --> "Hello--How-are-you" sanitize("I am great, thank you!",
     * '*') --> "I*am*great*thank*you"
     *
     * @param string       The message to be sanitized.
     * @param spaceReplace The string to be substituted for spaces.
     * @return The sanitized string.
     */
    public static String sanitize(String string, String spaceReplace) {
        return string.replaceAll("[^\\dA-Za-z ]", "").replaceAll("\\s+", spaceReplace);
    }

    public static String truncate(String text, int length) {
        return text.substring(0, Math.min(text.length(), length));
    }

    public static String removeEnd(String whole, String end) {
        return whole.endsWith(end) ? truncate(whole, whole.length() - end.length())
                                   : whole;
    }

    public static String substring(String text, int begin, int end) {
        return text.substring(Math.min(text.length(), begin), Math.min(text.length(), end));
    }

    public static String dashedChatMessage(String message, String dash, String dashPrefix) {
        return dashedChatMessage(message, dash, dashPrefix, null);
    }

    public static String dashedChatMessage(String message, String dash, String dashPrefix, String messagePrefix) {
        message = " " + message + " ";
        int dashCount = (GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - ChatColor.stripColor(message).length() - 2) / (dash.length() * 2);
        String dashes = dashCount >= 0 ? Strings.repeat(dash, dashCount) : "";

        StringBuffer builder = new StringBuffer();
        if (dashCount > 0) {
            builder.append(dashPrefix).append(dashes).append(ChatColor.RESET);
        }

        if(messagePrefix != null) {
            builder.append(messagePrefix);
        }

        builder.append(message);

        if (dashCount > 0) {
            builder.append(ChatColor.RESET).append(dashPrefix).append(dashes);
        }

        return builder.toString();
    }

    public static List<String> complete(String prefix, Class<? extends Enum> enumClass) {
        return complete(prefix, Stream.of(enumClass.getEnumConstants()).map(c -> c.name().toLowerCase().replace('_', ' ')));
    }

    public static List<String> complete(String prefix, Collection<String> options) {
        return complete(prefix, options.stream());
    }

    public static List<String> complete(String prefix, Stream<String> options) {
        final String prefixLower = prefix.toLowerCase();
        final int pos = prefixLower.lastIndexOf(' ');
        final List<String> matches = new ArrayList<>();
        options.forEach(option -> {
            if(option.toLowerCase().startsWith(prefixLower)) {
                matches.add(pos == -1 ? option : option.substring(pos + 1));
            }
        });
        Collections.sort(matches);
        return matches;
    }

    public static String slugify(String text, char space) {
        return text.trim().toLowerCase().replaceAll("[^a-z0-9" + space + "]+", String.valueOf(space));
    }

    public static Optional<String> nonEmpty(@Nullable String s) {
        return s != null && s.length() > 0 ? Optional.of(s) : Optional.empty();
    }

    public static String pluralize(String singular) {
        // Incredible natural language processing technology
        return singular + "s";
    }

    public static boolean startsWithIgnoreCase(String whole, String prefix) {
        return whole.length() >= prefix.length() &&
               whole.substring(0, prefix.length()).equalsIgnoreCase(prefix);
    }

    public static boolean isBlank(@Nullable String s) {
        if(s == null) return true;
        for(int i = 0; i < s.length(); i++) {
            if(!Character.isWhitespace(s.charAt(i))) return false;
        }
        return true;
    }
}
