package doglet.text;

import nova.Machine;
import nova.Store;
import nova.eval;
import nova.special;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;

/**
 * Warranty & Liability
 * To the extent permitted by applicable law and unless explicitly
 * otherwise agreed upon, XLOG Technologies AG makes no warranties
 * regarding the provided information. XLOG Technologies AG assumes
 * no liability that any problems might be solved with the information
 * provided by XLOG Technologies AG.
 *
 * Rights & License
 * All industrial property rights regarding the information - copyright
 * and patent rights in particular - are the sole property of XLOG
 * Technologies AG. If the company was not the originator of some
 * excerpts, XLOG Technologies AG has at least obtained the right to
 * reproduce, change and translate the information.
 *
 * Reproduction is restricted to the whole unaltered document. Reproduction
 * of the information is only allowed for non-commercial uses. Selling,
 * giving away or letting of the execution of the library is prohibited.
 * The library can be distributed as part of your applications and libraries
 * for execution provided this comment remains unchanged.
 *
 * Restrictions
 * Only to be distributed with programs that add significant and primary
 * functionality to the library. Not to be distributed with additional
 * software intended to replace any components of the library.
 *
 * Trademarks
 * Jekejeke is a registered trademark of XLOG Technologies AG.
 */
public final class strlib {

    /*******************************************************************/
    /* Number Formatting                                               */
    /*******************************************************************/

    /**
     * sys_number_atom(F, S, N, A): internal only
     * The built-in succeeds in A with the number F formatted according
     * to the format specifier S and the number of digits N.
     */
    private static boolean test_sys_number_atom(Object[] args) {
        Object num = Machine.exec_build(args[0]);
        special.check_number(num);
        Object spec = Machine.exec_build(args[1]);
        special.check_atom(spec);
        Object alpha = Machine.exec_build(args[2]);
        special.check_integer(alpha);
        if (Machine.is_bigint(alpha))
            throw Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{"int"}));
        int digs = ((Integer) alpha).intValue();
        if (digs < 0)
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"not_less_than_zero", alpha}));
        String text;
        double value = num instanceof Double ? ((Double) num).doubleValue() :
                special.narrow_float((Number) num);
        if ("fF".contains((String)spec)) {
            text = floatAtom(value, digs, "0.0");
            text = shapeFloat(text, (String)spec);
        } else if ("eE".contains((String)spec)) {
            text = floatAtom(value, digs, "0.0E00");
            text = shapeFloat(text, (String)spec);
        } else if ("gG".contains((String)spec)) {
            int mag = (value != 0 ? (int)Math.floor(Math.log10(
                    Math.abs(value))) : 0);
            if (mag >= digs || mag < -4) {
                text = floatAtom(value, digs - 1, "0.0E00");
            } else {
                text = floatAtom(value, digs - 1 - mag, "0.0");
            }
            text = shapeFloat(text, (String)spec);
        } else {
            throw Machine.make_error(new Store.Compound("existence_error",
                    new Object[]{"format_character", spec}));
        }
        return Machine.exec_unify(args[3], text);
    }

    private static String floatAtom(double value, int digs, String type) {
        DecimalFormat fmt = (DecimalFormat)NumberFormat.getInstance(Locale.UK);
        fmt.applyPattern(type);
        fmt.setMaximumFractionDigits(digs);
        fmt.setMinimumFractionDigits(digs);
        fmt.setRoundingMode(RoundingMode.HALF_EVEN);
        fmt.setGroupingUsed(false);
        return fmt.format(value);
    }

    /**
     * Shape the number string.
     *
     * @param res  The number string.
     * @param spec The format specifier.
     * @return The shaped number string.
     */
    public static String shapeFloat(String res, String spec) {
        int peek = res.indexOf('E');
        if (peek != -1) {
            res = shapeFloatMantissa(res.substring(0, peek), spec) +
                    ("FEG".contains(spec) ? "E": "e")
                    + shapeFloatExponent(res.substring(peek + 1));
        } else {
            res = shapeFloatMantissa(res, spec);
        }
        return res;
    }

    private static String shapeFloatMantissa(String res, String spec) {
        if ("gG".contains(spec)) {
            if (res.indexOf('.') != -1) {
                int pos = res.length();
                while (res.charAt(pos - 1) == '0')
                    pos--;
                if (res.charAt(pos - 1) == '.')
                    pos--;
                if (pos != res.length())
                    res = res.substring(0, pos);
            }
        }
        return res;
    }

    private static String shapeFloatExponent(String res) {
        if (res.startsWith("-"))
            return res;
        return "+"+res;
    }

    /*******************************************************************/
    /* Integer Formatting                                              */
    /*******************************************************************/

    /**
     * sys_integer_atom(F, S, N, A): internal only
     * The built-in succeeds in A with the integer F formatted according
     * to the format specifier S and the radix N.
     */
    private static boolean test_sys_integer_atom(Object[] args) {
        Object num = Machine.exec_build(args[0]);
        special.check_integer(num);
        Object spec = Machine.exec_build(args[1]);
        special.check_atom(spec);
        Object alpha = Machine.exec_build(args[2]);
        special.check_integer(alpha);
        if (Machine.is_bigint(alpha))
            throw Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{"int"}));
        int radix = ((Integer) alpha).intValue();
        if (radix < 2 || radix > 36)
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"radix", alpha}));
        String text;
        if ("r".equals(spec)) {
            text = special.atom_integer_encode(num, radix);
        } else if ("R".equals(spec)) {
            text = special.atom_integer_encode(num, radix);
            text = text.toUpperCase();
        } else {
            throw Machine.make_error(new Store.Compound("existence_error",
                    new Object[]{"format_character", spec}));
        }
        return Machine.exec_unify(args[3], text);
    }

    /*******************************************************************/
    /* Str Lib Init                                                    */
    /*******************************************************************/

    public static void main() {
        Store.set("sys_number_atom", 4, special.make_check(strlib::test_sys_number_atom));
        Store.set("sys_integer_atom", 4, special.make_check(strlib::test_sys_integer_atom));
    }

}
