// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.common.util;

import java.util.Arrays;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.DoubleFunction;
import java.util.Iterator;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.function.ToIntFunction;
import java.util.Comparator;
import java.util.Objects;
import java.util.Locale;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.Collection;
import java.time.Duration;
import java.util.List;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import java.util.regex.Pattern;

public class StringUtil
{
    public static final Pattern RAW_ARGS_PATTERN;
    @Nonnull
    private static final char[] GRAPH_CHARS;
    
    public static boolean isNumericString(@Nonnull final String str) {
        for (int i = 0; i < str.length(); ++i) {
            final char c = str.charAt(i);
            if (c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }
    
    public static boolean isAlphaNumericHyphenString(@Nonnull final String str) {
        for (int i = 0; i < str.length(); ++i) {
            final char c = str.charAt(i);
            if ((c < '0' || c > 'z' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a')) && c != '-') {
                return false;
            }
        }
        return true;
    }
    
    public static boolean isAlphaNumericHyphenUnderscoreString(@Nonnull final String str) {
        for (int i = 0; i < str.length(); ++i) {
            final char c = str.charAt(i);
            if ((c < '0' || c > 'z' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a')) && c != '-' && c != '_') {
                return false;
            }
        }
        return true;
    }
    
    public static boolean isCapitalized(@Nonnull final String keyStr, final char delim) {
        boolean wasDelimOrFirst = true;
        for (int i = 0; i < keyStr.length(); ++i) {
            final char c = keyStr.charAt(i);
            if (wasDelimOrFirst && c != Character.toUpperCase(c)) {
                return false;
            }
            wasDelimOrFirst = (c == delim);
        }
        return true;
    }
    
    @Nonnull
    public static String capitalize(@Nonnull final String keyStr, final char delim) {
        boolean wasDelimOrFirst = true;
        final StringBuilder sb = new StringBuilder();
        for (int i = 0; i < keyStr.length(); ++i) {
            final char c = keyStr.charAt(i);
            sb.append(wasDelimOrFirst ? Character.toUpperCase(c) : c);
            wasDelimOrFirst = (c == delim);
        }
        return sb.toString();
    }
    
    @Nullable
    public static <V extends Enum<V>> V parseEnum(@Nonnull final V[] enumConstants, final String str) {
        return parseEnum(enumConstants, str, MatchType.EQUALS);
    }
    
    @Nullable
    public static <V extends Enum<V>> V parseEnum(@Nonnull final V[] enumConstants, String str, final MatchType matchType) {
        if (matchType == MatchType.EQUALS) {
            for (final V enumValue : enumConstants) {
                if (enumValue.name().equals(str)) {
                    return enumValue;
                }
            }
        }
        else if (matchType == MatchType.CASE_INSENSITIVE) {
            for (final V enumValue : enumConstants) {
                if (enumValue.name().equalsIgnoreCase(str)) {
                    return enumValue;
                }
            }
        }
        else {
            str = str.toLowerCase();
            V closest = null;
            int diff = -2;
            for (final V enumValue2 : enumConstants) {
                final int index = StringCompareUtil.indexOfDifference(str, enumValue2.name().toLowerCase());
                if ((index > diff && diff != -1) || index == -1 || diff == -2) {
                    closest = enumValue2;
                    diff = index;
                }
            }
            if (diff > -2) {
                return closest;
            }
        }
        return null;
    }
    
    @Nonnull
    @Deprecated(forRemoval = true)
    public static String[] parseArgs(final String rawString, @Nonnull final Map<String, String> argOptions) {
        final String[] rawSplit = StringUtil.RAW_ARGS_PATTERN.split(rawString, 2);
        String argsStr = rawSplit[0];
        final boolean hasRaw = rawSplit.length > 1;
        char quote = '\0';
        int start = 0;
        final List<String> argsList = new ObjectArrayList<String>();
        for (int i = 0; i < argsStr.length(); ++i) {
            final char c = argsStr.charAt(i);
            switch (c) {
                case '\\': {
                    if (i + 1 >= argsStr.length()) {
                        throw new IllegalStateException("Invalid escape at end of string, index " + (i + 1) + " in: " + rawString);
                    }
                    final char c2 = argsStr.charAt(i + 1);
                    switch (c2) {
                        case '\"':
                        case '\'':
                        case '\\': {
                            argsStr = argsStr.substring(0, i) + argsStr.substring(i + 1);
                            ++i;
                            continue;
                        }
                        default: {
                            throw new IllegalStateException("Invalid escape for char " + c2 + " at index " + (i + 1) + " in: " + rawString);
                        }
                    }
                    break;
                }
                case ' ': {
                    if (quote == '\0') {
                        if (start != i) {
                            argsList.add(argsStr.substring(start, i));
                        }
                        start = i + 1;
                        break;
                    }
                    break;
                }
                case '\"': {
                    switch (quote) {
                        case '\0': {
                            quote = '\"';
                            break;
                        }
                        case '\"': {
                            quote = '\0';
                            argsList.add(argsStr.substring(start, i + 1));
                            start = i + 1;
                            break;
                        }
                    }
                    break;
                }
                case '\'': {
                    switch (quote) {
                        case '\0': {
                            quote = '\'';
                            continue;
                        }
                        case '\'': {
                            quote = '\0';
                            argsList.add(argsStr.substring(start, i + 1));
                            start = i + 1;
                            continue;
                        }
                    }
                    break;
                }
            }
        }
        if (quote != '\0') {
            throw new IllegalStateException("Unbalanced quotes!");
        }
        if (start != argsStr.length()) {
            argsList.add(argsStr.substring(start));
        }
        argsList.removeIf(arg -> {
            if (arg.startsWith("--")) {
                final String[] split = arg.substring(2).split("=", 2);
                String value = "";
                if (split.length > 1) {
                    final String value2 = split[1];
                    value = removeQuotes(value2);
                }
                argOptions.put(split[0], value);
                return true;
            }
            else {
                return false;
            }
        });
        argsList.replaceAll(value -> removeQuotes(value.trim()));
        if (hasRaw) {
            final String[] strings = new String[argsList.size() + 1];
            argsList.toArray(strings);
            strings[argsList.size()] = rawSplit[1].trim();
            return strings;
        }
        return argsList.toArray(String[]::new);
    }
    
    @Nonnull
    public static String[] parseArgs(final String rawString) {
        final String[] rawSplit = StringUtil.RAW_ARGS_PATTERN.split(rawString, 2);
        String argsStr = rawSplit[0];
        final boolean hasRaw = rawSplit.length > 1;
        char quote = '\0';
        int start = 0;
        final List<String> argsList = new ObjectArrayList<String>();
        for (int i = 0; i < argsStr.length(); ++i) {
            final char c = argsStr.charAt(i);
            switch (c) {
                case '\\': {
                    if (i + 1 >= argsStr.length()) {
                        throw new IllegalStateException("Invalid escape at end of string, index " + (i + 1) + " in: " + rawString);
                    }
                    final char c2 = argsStr.charAt(i + 1);
                    switch (c2) {
                        case '\"':
                        case '\'':
                        case '\\': {
                            argsStr = argsStr.substring(0, i) + argsStr.substring(i + 1);
                            ++i;
                            continue;
                        }
                        default: {
                            throw new IllegalStateException("Invalid escape for char " + c2 + " at index " + (i + 1) + " in: " + rawString);
                        }
                    }
                    break;
                }
                case ' ': {
                    if (quote == '\0') {
                        if (start != i) {
                            argsList.add(argsStr.substring(start, i));
                        }
                        start = i + 1;
                        break;
                    }
                    break;
                }
                case '\"': {
                    switch (quote) {
                        case '\0': {
                            quote = '\"';
                            break;
                        }
                        case '\"': {
                            quote = '\0';
                            argsList.add(argsStr.substring(start, i + 1));
                            start = i + 1;
                            break;
                        }
                    }
                    break;
                }
                case '\'': {
                    switch (quote) {
                        case '\0': {
                            quote = '\'';
                            continue;
                        }
                        case '\'': {
                            quote = '\0';
                            argsList.add(argsStr.substring(start, i + 1));
                            start = i + 1;
                            continue;
                        }
                    }
                    break;
                }
            }
        }
        if (quote != '\0') {
            throw new IllegalStateException("Unbalanced quotes!");
        }
        if (start != argsStr.length()) {
            argsList.add(argsStr.substring(start));
        }
        argsList.replaceAll(value -> removeQuotes(value.trim()));
        if (hasRaw) {
            final String[] strings = new String[argsList.size() + 1];
            argsList.toArray(strings);
            strings[argsList.size()] = rawSplit[1].trim();
            return strings;
        }
        return argsList.toArray(String[]::new);
    }
    
    @Nonnull
    public static String removeQuotes(@Nonnull String value) {
        switch (value.charAt(0)) {
            case '\"':
            case '\'': {
                value = value.substring(1, value.length() - 1);
                break;
            }
        }
        return value;
    }
    
    @Nonnull
    public static String stripQuotes(@Nonnull final String s) {
        if (s.length() >= 2) {
            final char first = s.charAt(0);
            final char last = s.charAt(s.length() - 1);
            if ((first == '\"' && last == '\"') || (first == '\'' && last == '\'')) {
                return s.substring(1, s.length() - 1);
            }
        }
        return s;
    }
    
    public static boolean isGlobMatching(@Nonnull final String pattern, @Nonnull final String text) {
        return pattern.equals(text) || isGlobMatching(pattern, 0, text, 0);
    }
    
    public static boolean isGlobMatching(@Nonnull final String pattern, int patternPos, @Nonnull final String text, int textPos) {
        while (patternPos < pattern.length()) {
            final char charAt = pattern.charAt(patternPos);
            if (charAt == '*') {
                ++patternPos;
                while (patternPos < pattern.length() && pattern.charAt(patternPos) == '*') {
                    ++patternPos;
                }
                if (patternPos == pattern.length()) {
                    return true;
                }
                final char matchChar = pattern.charAt(patternPos);
                while (textPos < text.length()) {
                    if (matchChar == text.charAt(textPos) && isGlobMatching(pattern, patternPos + 1, text, textPos + 1)) {
                        return true;
                    }
                    ++textPos;
                }
                return false;
            }
            else {
                if (textPos == text.length()) {
                    return false;
                }
                if (charAt != '?' && charAt != text.charAt(textPos)) {
                    return false;
                }
                ++patternPos;
                ++textPos;
            }
        }
        return textPos == text.length();
    }
    
    public static boolean isGlobPattern(@Nonnull final String text) {
        for (int i = 0; i < text.length(); ++i) {
            final char c = text.charAt(i);
            if (c == '?' || c == '*') {
                return true;
            }
        }
        return false;
    }
    
    @Nonnull
    public static String humanizeTime(@Nonnull final Duration duration, final boolean useSeconds) {
        final long length = duration.toMillis();
        final long days = length / 86400000L;
        final long hours = (length - days * 86400000L) / 3600000L;
        final long minutes = (length - (days * 86400000L + hours * 3600000L)) / 60000L;
        String base = days + "d " + hours + "h " + minutes;
        if (useSeconds) {
            final long seconds = (length - (days * 86400000L + hours * 3600000L + minutes * 60000L)) / 1000L;
            base = base + " " + seconds;
        }
        return base;
    }
    
    @Nonnull
    public static String humanizeTime(@Nonnull final Duration length) {
        return humanizeTime(length, false);
    }
    
    @Nonnull
    public static <T> List<T> sortByFuzzyDistance(@Nonnull final String str, @Nonnull final Collection<T> collection, final int length) {
        final List<T> list = sortByFuzzyDistance(str, collection);
        return (list.size() > length) ? list.subList(0, length) : list;
    }
    
    @Nonnull
    public static <T> List<T> sortByFuzzyDistance(@Nonnull final String str, @Nonnull final Collection<T> collection) {
        final Object2IntMap<T> map = new Object2IntOpenHashMap<T>(collection.size());
        for (final T value : collection) {
            map.put(value, StringCompareUtil.getFuzzyDistance(value.toString(), str, Locale.ENGLISH));
        }
        final ObjectArrayList<Object> list2;
        final List<T> list = (List<T>)(list2 = new ObjectArrayList<Object>(collection));
        final Object2IntMap<T> obj = map;
        Objects.requireNonNull((Object2IntOpenHashMap)obj);
        list2.sort(Comparator.comparingInt(obj::getInt).reversed());
        return list;
    }
    
    @Nonnull
    public static String toPaddedBinaryString(int val) {
        final byte[] buf = { 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48 };
        final int leadingZeros = Integer.numberOfLeadingZeros(val);
        final int mag = 32 - leadingZeros;
        int pos = Math.max(mag, 1);
        do {
            buf[leadingZeros + --pos] = (byte)(48 + (val & 0x1));
            val >>>= 1;
        } while (pos > 0);
        return new String(buf, 0);
    }
    
    @Nonnull
    public static String trimEnd(@Nonnull final String str, @Nonnull final String end) {
        if (!str.endsWith(end)) {
            return str;
        }
        return str.substring(0, str.length() - end.length());
    }
    
    public static void generateGraph(@Nonnull final StringBuilder sb, final int width, final int height, final long minX, final long maxX, final double minY, final double maxY, @Nonnull final DoubleFunction<String> labelFormatFunc, final int historyLength, @Nonnull final IntToLongFunction timestampFunc, @Nonnull final IntToDoubleFunction valueFunc) {
        final double lengthY = maxY - minY;
        final long lengthX = maxX - minX;
        final double rowAggLength = lengthY / height;
        final double colAggLength = lengthX / (double)width;
        final double[] values = new double[width];
        Arrays.fill(values, -1.0);
        int historyIndex = 0;
        for (int i = 0; i < width; ++i) {
            double total = 0.0;
            int count = 0;
            for (long nextAggTimestamp = maxX - (lengthX - (long)(colAggLength * i)); historyIndex < historyLength && timestampFunc.applyAsLong(historyIndex) < nextAggTimestamp; ++historyIndex) {
                total += valueFunc.applyAsDouble(historyIndex);
                ++count;
            }
            if (count != 0) {
                values[i] = total / count;
            }
            else if (i > 0) {
                values[i] = values[i - 1];
            }
        }
        double last = -1.0;
        for (int j = values.length - 1; j >= 0; --j) {
            if (values[j] != -1.0) {
                last = values[j];
            }
            else if (last != -1.0) {
                values[j] = last;
            }
        }
        int yLabelWidth = 0;
        final String[] labels = new String[height];
        for (int row = 0; row < height; ++row) {
            final double rowMaxValue = minY + lengthY - rowAggLength * row;
            final String label = labelFormatFunc.apply(rowMaxValue);
            labels[row] = label;
            final int length = label.length();
            if (length > yLabelWidth) {
                yLabelWidth = length;
            }
        }
        final String bar = " ".repeat(yLabelWidth) + " " + "#".repeat(width + 2);
        sb.append(bar).append('\n');
        for (int row2 = 0; row2 < height; ++row2) {
            sb.append(" ".repeat(Math.max(0, yLabelWidth - labels[row2].length()))).append(labels[row2]).append(" #");
            final double rowMinValue = minY + lengthY - rowAggLength * (row2 + 1);
            for (int col = 0; col < width; ++col) {
                final double colRowValue = values[col] - rowMinValue;
                if (colRowValue <= 0.0 || colRowValue > rowAggLength) {
                    sb.append(' ');
                }
                else {
                    final double valuePercent = colRowValue / rowAggLength;
                    final int charIndex = (int)Math.round(valuePercent * (StringUtil.GRAPH_CHARS.length - 1));
                    sb.append(StringUtil.GRAPH_CHARS[Math.max(0, charIndex)]);
                }
            }
            sb.append("#\n");
        }
        sb.append(bar).append('\n');
        sb.append('\n');
    }
    
    static {
        RAW_ARGS_PATTERN = Pattern.compile(" -- ");
        GRAPH_CHARS = new char[] { '_', '\u2584', '\u2500', '\u2580', '¯' };
    }
    
    public enum MatchType
    {
        INDEX_DIFFERENCE, 
        EQUALS, 
        CASE_INSENSITIVE;
    }
}
