package org.implab.gradle.common.core.lang;

import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.gradle.api.provider.Provider;

@NonNullByDefault
public class Strings {

    private static final boolean[] ALLOWED_FILE_NAME_CHAR = new boolean[128];

    private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();

    private static final Pattern firstLetter = Pattern.compile("^\\w");

    private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");

    static {
        for (char c = '0'; c <= '9'; c++)
            ALLOWED_FILE_NAME_CHAR[c] = true;
        for (char c = 'A'; c <= 'Z'; c++)
            ALLOWED_FILE_NAME_CHAR[c] = true;
        for (char c = 'a'; c <= 'z'; c++)
            ALLOWED_FILE_NAME_CHAR[c] = true;
        ALLOWED_FILE_NAME_CHAR['.'] = true;
        ALLOWED_FILE_NAME_CHAR['_'] = true;
        ALLOWED_FILE_NAME_CHAR['-'] = true;
    }

    public static String capitalize(String string) {
        return string == null ? null
                : string.length() == 0 ? string
                        : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
    }

    public static String toCamelCase(String name) {
        if (name == null || name.isEmpty())
            return name;
        StringBuilder out = new StringBuilder(name.length());
        boolean up = false;
        boolean first = true;
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            switch (c) {
                case '-', '_', ' ', '.' -> up = true;
                default -> {
                    out.append(
                            first ? Character.toLowerCase(c)
                                    : up ? Character.toUpperCase(c): c);
                    up = false;
                    first = false;
                }
            }
        }
        return out.toString();
    }

    public static void argumentNotNullOrEmpty(String value, String argumentName) {
        if (value == null || value.length() == 0)
            throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
    }

    public static void argumentNotNullOrBlank(String value, String argumentName) {
        if (value == null || value.trim().length() == 0)
            throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
    }

    public static String requireNonBlank(String value) {
        argumentNotNullOrBlank(value, "value");
        return value;
    }

    public static String requireNonEmpty(String value) {
        argumentNotNullOrEmpty(value, "value");
        return value;
    }


    public static String sanitizeName(String value) {
        return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
    }

    public static String sanitizeFileName(String value) {
        int length = value.length();
        for (int i = 0; i < length; i++) {
            char c = value.charAt(i);
            if (c >= ALLOWED_FILE_NAME_CHAR.length || !ALLOWED_FILE_NAME_CHAR[c])
                return sanitizeFileName(value, i);
        }
        return value;
    }

    public static String asString(Object value) {
        if (value == null)
            return null;
        if (value instanceof Provider<?> provider)
            return asString(provider.get());
        else
            return value.toString();
    }

    private static String sanitizeFileName(String value, int invalidIndex) {
        int length = value.length();
        StringBuilder out = new StringBuilder(length + 16);
        out.append(value, 0, invalidIndex);

        for (int i = invalidIndex; i < length; i++) {
            char c = value.charAt(i);
            if (c < ALLOWED_FILE_NAME_CHAR.length && ALLOWED_FILE_NAME_CHAR[c]) {
                out.append(c);
            } else if (Character.isHighSurrogate(c) && i + 1 < length && Character.isLowSurrogate(value.charAt(i + 1))) {
                appendUrlEncodedUtf8(out, Character.toCodePoint(c, value.charAt(++i)));
            } else if (Character.isSurrogate(c)) {
                appendUrlEncodedUtf8(out, 0xFFFD);
            } else {
                appendUrlEncodedUtf8(out, c);
            }
        }

        return out.toString();
    }

    private static void appendUrlEncodedUtf8(StringBuilder out, int codePoint) {
        if (codePoint <= 0x7F) {
            appendUrlEncodedByte(out, codePoint);
        } else if (codePoint <= 0x7FF) {
            appendUrlEncodedByte(out, 0xC0 | (codePoint >>> 6));
            appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
        } else if (codePoint <= 0xFFFF) {
            appendUrlEncodedByte(out, 0xE0 | (codePoint >>> 12));
            appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
            appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
        } else {
            appendUrlEncodedByte(out, 0xF0 | (codePoint >>> 18));
            appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 12) & 0x3F));
            appendUrlEncodedByte(out, 0x80 | ((codePoint >>> 6) & 0x3F));
            appendUrlEncodedByte(out, 0x80 | (codePoint & 0x3F));
        }
    }

    private static void appendUrlEncodedByte(StringBuilder out, int value) {
        out.append('%');
        out.append(HEX_DIGITS[(value >>> 4) & 0x0F]);
        out.append(HEX_DIGITS[value & 0x0F]);
    }
}
