package me.teridax.jcash.decode;

import me.teridax.jcash.lang.Locales;

import javax.swing.text.NumberFormatter;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * Utility class for converting various single line strings into a specific data type according
 * to a format mostly dictated by a locale.
 */
public class StringDecoder {

    public static NumberFormat getNumberFormat() {
        return NumberFormat.getInstance(Locales.getDefaultLocale());
    }

    public static NumberFormatter getNumberFormatter(double maxValue) {
        var formatter = new NumberFormatter();
        formatter.setValueClass(Double.class);
        formatter.setMinimum(0d);
        formatter.setMaximum(maxValue);
        formatter.setAllowsInvalid(true);
        formatter.setCommitsOnValidEdit(true);

        return formatter;
    }

    public static Object getIntegerNumberFormatter() {
        var formatter = new NumberFormatter();
        formatter.setValueClass(Integer.class);
        formatter.setMinimum(0d);
        formatter.setAllowsInvalid(true);
        formatter.setCommitsOnValidEdit(true);

        return formatter;
    }

    /**
     * Attempts to convert the given string into a double value representing a percentage.
     * The output value will be in the range [0, 100]. Strings formatted without a percentage
     * symbol will be assumed to be normalized and thus multiplied by 100 to retrieve the result in percent.
     *
     * @param number the string to convert
     * @return the double value
     * @throws IllegalArgumentException when the format is invalid
     * @throws NullPointerException     when the argument is null
     */
    public static double decodePercent(String number) throws IllegalArgumentException, NullPointerException {
        Objects.requireNonNull(number);

        // trim and cut out weird leading single quotes for numbers
        var prepared = number.trim().replaceAll("^\\s*['‘`](?=\\d)", "");

        var pattern = Pattern.compile("^([^%]+)?(%)?$", Pattern.CASE_INSENSITIVE);
        var matcher = pattern.matcher(prepared);
        if (matcher.find()) {
            // if no percentage symbol is given the number will be multiplied by 100%
            var scale = 1e2;

            // check if capture group 2 captured a percentage symbol
            // if to we don't want to apply any scaling to the value
            if (null != matcher.group(2))
                scale = 1;

            try {
                return getNumberFormat().parse(matcher.group(1)).doubleValue() * scale;
            } catch (ParseException ex) {
                throw new IllegalArgumentException("Not a valid number: " + number, ex);
            }
        } else {
            throw new IllegalArgumentException("not a valid percentage");
        }
    }

    /**
     * Attempts to convert the given string into a currency value.
     *
     * @param currency the string to convert
     * @return the double value
     * @throws IllegalArgumentException when the format is invalid
     * @throws NullPointerException     when the argument is null
     */
    public static double decodeCurrency(String currency) throws IllegalArgumentException, NullPointerException {
        Objects.requireNonNull(currency);

        // trim and cut out weird leading single quotes for numbers
        var prepared = currency.trim().replaceAll("^\\s*['‘`](?=\\d)", "");

        try {
            return getNumberFormat().parse(prepared).doubleValue();
        } catch (ParseException ex) {
            throw new IllegalArgumentException("Not a valid currency in german locale: " + currency, ex);
        }
    }

    /**
     * Attempts to convert the given string into universally unique number.
     * This function does not check for duplicates. The number must be a positive integer.
     *
     * @param number the string to convert
     * @return the integer serial number
     * @throws IllegalArgumentException when the format is invalid
     * @throws NullPointerException     when the argument is null
     */
    public static int decodeUniqueIdentificationNumber(String number) throws IllegalArgumentException, NullPointerException {
        Objects.requireNonNull(number);

        // trim and cut out weird leading single quotes for numbers
        var preparedUID = number.trim().replaceAll("^\\s*['‘`](?=\\d)", "");

        // check if the string is a valid unsigned number
        try {
            var serialNumber = getNumberFormat().parse(preparedUID);

            if (serialNumber.intValue() < 0)
                throw new IllegalArgumentException("Not a valid unique identification number: " + number);

            return serialNumber.intValue();
        } catch (ParseException ex) {
            throw new IllegalArgumentException("Not a valid serial number: " + number, ex);
        }
    }

    /**
     * Attempts to convert the given string into a name.
     * This method performs validation and trimming.
     *
     * @param name the string to convert
     * @return the qualified name
     * @throws IllegalArgumentException when the format is invalid
     * @throws NullPointerException     when the argument is null
     */
    public static String decodeName(String name) throws IllegalArgumentException, NullPointerException {
        Objects.requireNonNull(name);

        var trimmed = name.trim();

        var pattern = Pattern.compile("[^\\d]+", Pattern.CASE_INSENSITIVE);
        var matcher = pattern.matcher(trimmed);
        if (matcher.find()) {
            return matcher.group();
        } else {
            throw new IllegalArgumentException("not a void name");
        }
    }

    /**
     * Attempts to convert the given string into a street and an optional house address.
     *
     * @param street the string to convert
     * @return the address name
     * @throws IllegalArgumentException when the format is invalid
     * @throws NullPointerException     when the argument is null
     */
    public static String decodeStreet(String street) throws IllegalArgumentException, NullPointerException {
        Objects.requireNonNull(street);

        var pattern = Pattern.compile("\\S+(\\s+\\d+(\\s*/\\s*\\d+)?)?", Pattern.CASE_INSENSITIVE);
        var matcher = pattern.matcher(street);
        if (matcher.find()) {
            return matcher.group();
        } else {
            throw new IllegalArgumentException("not a void address");
        }
    }
}
