package nova;

import doglet.fastlib;

import java.math.*;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * 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 eval {
    public static final BigInteger NEG_MIN_INTEGER = BigInteger.valueOf(-(long) Integer.MIN_VALUE);
    private static final Double DOUBLE_PI = Double.valueOf(Math.PI);
    private static final Double DOUBLE_E = Double.valueOf(Math.E);
    private static final Double DOUBLE_EPSILON = Double.valueOf(Math.ulp(1.0));
    public static final Double DOUBLE_PINF = Double.valueOf(Double.POSITIVE_INFINITY);
    public static final Double DOUBLE_NINF = Double.valueOf(Double.NEGATIVE_INFINITY);
    public static final Double DOUBLE_NAN = Double.valueOf(Double.NaN);

    public static final int NUM_INTEGER = 0;
    public static final int NUM_BIG_INTEGER = 1;
    public static final int NUM_DOUBLE = 2;
    public static final int NUM_SPECIAL = 3;

    /******************************************************************/
    /* is/2, (-)/2, abs/2 and sign/2                                  */
    /******************************************************************/

    /**
     * X is Y: [ISO 8.6.1]
     * The predicate succeeds in X with the evaluation of Y.
     */
    private static boolean test_eval(Object[] args) {
        Number res = Machine.exec_eval(args[1]);
        return Machine.exec_unify(args[0], res);
    }

    /**
     * -(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the negation of A.
     * This implements NaN propagation IEEE 754-2019 §6.2.3.
     */
    private static Number arit_neg(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x != Integer.MIN_VALUE) {
                return Integer.valueOf(-x);
            } else {
                return NEG_MIN_INTEGER;
            }
        } else if (alpha instanceof BigInteger) {
            return special.norm_bigint(((BigInteger) alpha).negate());
        } else  {
            return Double.valueOf(-alpha.doubleValue());
        }
    }

    /**
     * abs(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the absolute value of A.
     */
    private static Number arit_abs(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x != Integer.MIN_VALUE) {
                return Integer.valueOf(Math.abs(x));
            } else {
                return NEG_MIN_INTEGER;
            }
        } else if (alpha instanceof BigInteger) {
            return special.norm_bigint(((BigInteger) alpha).abs());
        } else  {
            return Double.valueOf(Math.abs(special.narrow_float(alpha)));
        }
    }

    /**
     * sign(A, B): [ISO 9.1.4]
     * The predicate succeeds in B with the sign of A.
     */
    private static Number arit_sign(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return Integer.valueOf(integer_signum(alpha));
        } else {
            return Double.valueOf(Math.signum(special.narrow_float(alpha)));
        }
    }

    /*****************************************************************/
    /* (+)/3, (-)/3, (*)/3, (/)/3, (//)/3 and (rem)/3.               */
    /*****************************************************************/

    /**
     * +(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with the sum of A and B.
     */
    private static Number arit_add(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return add(alpha, beta);
    }

    public static Number add(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) m.intValue() + n.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(m).add(
                        special.widen_bigint(n)));
            default:
                return special.norm_float(special.narrow_float(m) +
                        special.narrow_float(n));
        }
    }

    /**
     * -(A, B, C):
     * The predicate succeeds in C with A subtracted by B.
     */
    private static Number arit_sub(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return subtract(alpha, beta);
    }

    public static Number subtract(Number m, Number n) {
        switch (Math.max(numType(m), numType(n))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) m.intValue() - n.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(m).subtract(
                        special.widen_bigint(n)));
            default:
                return special.norm_float(special.narrow_float(m) -
                        special.narrow_float(n));
        }
    }

    /**
     * *(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with the product of A and B.
     */
    private static Number arit_mul(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
                return special.norm_smallint((long) alpha.intValue() * beta.intValue());
            case NUM_BIG_INTEGER:
                return special.norm_bigint(special.widen_bigint(alpha).multiply(
                        special.widen_bigint(beta)));
            default:
                return special.norm_float(special.narrow_float(alpha) *
                        special.narrow_float(beta));
        }
    }

    /**
     * /(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A float divided by B.
     */
    private static Number arit_quot(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        double d = special.narrow_float(beta);
        if (d == 0.0)
            throw Machine.make_error(new Store.Compound("evaluation_error",
                    new Object[]{"zero_divisor"}));
        return special.norm_float(special.narrow_float(alpha) / d);
    }

    /**
     * //(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A truncate divided by B.
     */
    private static Number arit_intquot(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            int v = alpha.intValue();
            if (v == Integer.MIN_VALUE && u == -1) {
                return NEG_MIN_INTEGER;
            } else {
                return Integer.valueOf(v / u);
            }
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            return special.norm_bigint(special.widen_bigint(alpha).divide(p));
        }
    }

    /**
     * rem(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A remainder B.
     */
    private static Number arit_rem(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            return Integer.valueOf(alpha.intValue() % u);
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            return special.norm_bigint(special.widen_bigint(alpha).remainder(p));
        }
    }

    /***************************************************************/
    /* float/1, (^)/3, div/3 and mod/3.                            */
    /***************************************************************/

    /**
     * float(A, B): [ISO 9.17]
     * The predicate succeeds in B with the approximated A.
     */
    private static Number arit_float(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return Double.valueOf(special.narrow_float(alpha));
    }

    /**
     * ^(A, B, C): [TC2 9.3.10]
     * The predicate succeeds in C with A int power by B.
     */
    private static Number arit_intpow(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numType(alpha), numType(beta))) {
            case NUM_INTEGER:
                int y = beta.intValue();
                if (y < 0)
                    throw Machine.make_error(new Store.Compound("domain_error",
                            new Object[]{"not_less_than_zero", beta}));
                int x = alpha.intValue();
                if (x != Integer.MIN_VALUE && bitlength(Math.abs(x)) * y < 63) {
                    return special.norm_smallint(intPow(x, y));
                } else {
                    return special.norm_bigint(BigInteger.valueOf(x).pow(y));
                }
            case NUM_BIG_INTEGER:
                if (beta instanceof BigInteger)
                    throw Machine.make_error(new Store.Compound("representation_error",
                            new Object[]{"int"}));
                y = beta.intValue();
                if (y < 0)
                    throw Machine.make_error(new Store.Compound("domain_error",
                            new Object[]{"not_less_than_zero", beta}));
                return special.norm_bigint(((BigInteger) alpha).pow(y));
            default:
                return special.norm_float(Math.pow(
                        special.narrow_float(alpha), special.narrow_float(beta)));
        }
    }

    private static long intPow(long m, int n) {
        long r = 1;
        while (n != 0) {
            if ((n & 1) != 0)
                r *= m;
            n >>= 1;
            if (n != 0)
                m *= m;
        }
        return r;
    }

    private static int bitlength(long m) {
        return 64 - Long.numberOfLeadingZeros(m);
    }

    /**
     * div(A, B, C): [TC2 9.1.3]
     * The predicate succeeds in C with A floor divided by B.
     */
    private static Number arit_div(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            int v = alpha.intValue();
            if (v == Integer.MIN_VALUE && u == -1) {
                return NEG_MIN_INTEGER;
            } else {
                return Integer.valueOf(Math.floorDiv(v, u));
            }
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            BigInteger val = bigDiv(special.widen_bigint(alpha), p);
            return special.norm_bigint(val);
        }
    }

    private static BigInteger bigDiv(BigInteger v, BigInteger u) {
        BigInteger h;
        if ((v.signum() < 0) != (u.signum() < 0)) {
            BigInteger[] res = v.divideAndRemainder(u);
            h = res[0];
            if (res[1].signum() != 0)
                h = h.subtract(BigInteger.ONE);
        } else {
            h = v.divide(u);
        }
        return h;
    }

    /**
     * mod(A, B, C): [ISO 9.1.7]
     * The predicate succeeds in C with A modulus B.
     */
    private static Number arit_mod(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int u = beta.intValue();
            if (u == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            return Integer.valueOf(Math.floorMod(alpha.intValue(), u));
        } else {
            BigInteger p = special.widen_bigint(beta);
            if (p.signum() == 0)
                throw Machine.make_error(new Store.Compound("evaluation_error",
                        new Object[]{"zero_divisor"}));
            BigInteger val = bigMod(special.widen_bigint(alpha), p);
            return special.norm_bigint(val);
        }
    }

    private static BigInteger bigMod(BigInteger v, BigInteger u) {
        BigInteger res = v.remainder(u);
        if ((v.signum() < 0) != (u.signum() < 0)) {
            if (res.signum() != 0)
                res = res.add(u);
        }
        return res;
    }

    /****************************************************************/
    /* min/3, max/3, inf/1 and nan/1                                */
    /****************************************************************/

    public static int integer_signum(Number num) {
        if (num instanceof Integer) {
            return Integer.signum(num.intValue());
        } else {
            return ((BigInteger) num).signum();
        }
    }

    /**
     * min(A, B, C): [TC2 9.3.9]
     * The predicate succeeds in C with the minimum of A and B.
     * This implements minimumNumber IEEE 754-2019 §9.6.
     */
    private static Number arit_min(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numTypeWithInf(alpha), numTypeWithInf(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                if (integer_compare(alpha, beta) > 0)
                    return beta;
                return alpha;
            case NUM_DOUBLE:
                double a = special.narrow_float(alpha);
                double b = special.narrow_float(beta);
                if (a > b) {
                    alpha = beta;
                    a = b;
                }
                return (alpha instanceof Double ? alpha : Double.valueOf(a));
            default:
                if (eval.DOUBLE_NAN.equals(alpha))
                    return beta;
                if (eval.DOUBLE_NAN.equals(beta))
                    return alpha;
                if (eval.DOUBLE_PINF.equals(alpha))
                    return beta;
                if (eval.DOUBLE_PINF.equals(beta))
                    return alpha;
                if (eval.DOUBLE_NINF.equals(alpha))
                    return alpha;
                return beta;
        }
    }

    /**
     * max(A, B, C): [TC2 9.3.8]
     * The predicate succeeds in C with the maximum of A and B.
     * This implements maximumNumber IEEE 754-2019 §9.6.
     */
    private static Number arit_max(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        switch (Math.max(numTypeWithInf(alpha), numTypeWithInf(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                if (integer_compare(alpha, beta) < 0)
                    return beta;
                return alpha;
            case NUM_DOUBLE:
                double a = special.narrow_float(alpha);
                double b = special.narrow_float(beta);
                if (a < b) {
                    alpha = beta;
                    a = b;
                }
                return (alpha instanceof Double ? alpha : Double.valueOf(a));
            default:
                if (eval.DOUBLE_NAN.equals(alpha))
                    return beta;
                if (eval.DOUBLE_NAN.equals(beta))
                    return alpha;
                if (eval.DOUBLE_NINF.equals(alpha))
                    return beta;
                if (eval.DOUBLE_NINF.equals(beta))
                    return alpha;
                if (eval.DOUBLE_PINF.equals(alpha))
                    return alpha;
                return beta;
        }
    }

    /**
     * inf(A):
     * The predicate succeeds in A with positive infinity.
     */
    private static Number arit_inf(Object[] args) {
        return DOUBLE_PINF;
    }

    /**
     * nan(A):
     * The predicate succeeds in A with not a number.
     */
    private static Number arit_nan(Object[] args) {
        return DOUBLE_NAN;
    }

    /******************************************************************/
    /* truncate/2, floor/2, ceiling/2 and round/2                     */
    /******************************************************************/

    private static final long MIN_SAFE_LONG = -9007199254740991L;
    private static final long MAX_SAFE_LONG = 9007199254740991L;

    /**
     * truncate(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the truncate of A.
     */
    private static Number arit_truncate(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return alpha;
        } else {
            double d = special.narrow_float(alpha);
            if (MIN_SAFE_LONG <= d && d <= MAX_SAFE_LONG) {
                return special.norm_smallint((long) d);
            } else {
                return toInteger(d);
            }
        }
    }

    /**
     * floor(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the floor of A.
     */
    private static Number arit_floor(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return alpha;
        } else {
            return toInteger(Math.floor(special.narrow_float(alpha)));
        }
    }

    /**
     * ceiling(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the ceiling of A.
     */
    private static Number arit_ceiling(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return alpha;
        } else {
            return toInteger(Math.ceil(special.narrow_float(alpha)));
        }
    }

    /**
     * round(A, B): [ISO 9.1.7]
     * The predicate succeeds in B with the rounding of A.
     */
    private static Number arit_round(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        if (Machine.is_integer(alpha)) {
            return alpha;
        } else {
            double d = special.narrow_float(alpha);
            if (MIN_SAFE_LONG <= d && d <= MAX_SAFE_LONG) {
                return special.norm_smallint(Math.round(d));
            } else {
                return toInteger(d);
            }
        }
    }

    private static Number toInteger(double d) {
        if (Long.MIN_VALUE <= d && d <= Long.MAX_VALUE) {
            return special.norm_smallint((long) d);
        } else {
            int y = Math.getExponent(d) - 52;
            long x = (long)Math.scalb(d, -y);
            return BigInteger.valueOf(x).shiftLeft(y);
        }
    }

    /*********************************************************************/
    /* =:=/2, =\=/2, </2, >=/2, >/2 and =</2                             */
    /*********************************************************************/

    /**
     * X =:= Y: [ISO 8.7.1]
     * The predicate succeeds when X number equals Y, otherwise fails.
     */
    private static boolean test_numberequal(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return number_equal(alpha, beta);
    }

    /**
     * X =\= Y: [ISO 8.7.1]
     * The predicate succeeds when X does not number equal Y, otherwise fails.
     */
    private static boolean test_numbernotequal(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return !number_equal(alpha, beta);
    }

    /**
     * Check whether two Prolog numbers are equal.
     * This implement compareQuietEqual IEEE 754-2019 §5.6.1.
     * 
     * @param alpha The first Prolog number.
     * @param beta The second Prolog number.
     * @return True if they are equal, false otherwise.
     */
    private static boolean number_equal(Number alpha, Number beta) {
        switch (Math.max(numTypeWithInf(alpha), numTypeWithInf(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                return alpha.equals(beta);
            case NUM_DOUBLE:
                return special.narrow_float(alpha)
                        == special.narrow_float(beta);
            default:
                if (eval.DOUBLE_NAN.equals(alpha))
                    return false;
                if (eval.DOUBLE_NAN.equals(beta))
                    return false;
                return alpha.equals(beta);
        }
    }

    /**
     * X < Y: [ISO 8.7.1]
     * The predicate succeeds when X is number less than Y, otherwise fails.
     */
    private static boolean test_numberless(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return number_less(alpha, beta);
    }

    /**
     * X >= Y: [ISO 8.7.1]
     * The predicate succeeds when X is number greater or equal to Y, otherwise fails.
     */
    private static boolean test_numbergreaterequal(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return number_lessequal(beta, alpha);
    }

    /**
     * X > Y: [ISO 8.7.1]
     * The predicate succeeds when X is number greater than Y, otherwise fails.
     */
    private static boolean test_numbergreater(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return number_less(beta, alpha);
    }

    /**
     * X =< Y: [ISO 8.7.1]
     * The predicate succeeds when X is number less or equal to Y, otherwise fails.
     */
    private static boolean test_numberlessequal(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return number_lessequal(alpha, beta);
    }

    /**
     * Determine whether a Prolog numbers is less than a Prolog number.
     * This implement compareQuietLess IEEE 754-2019 §5.6.1.
     *
     * @param alpha The first Prolog number.
     * @param beta The second Prolog number.
     * @return True if the first is less than the second, otherwise false.
     */
    private static boolean number_less(Number alpha, Number beta) {
        switch (Math.max(numTypeWithInf(alpha), numTypeWithInf(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                return integer_compare(alpha, beta) < 0;
            case NUM_DOUBLE:
                return Double.compare(special.narrow_float(alpha),
                        special.narrow_float(beta)) < 0;
            default:
                if (eval.DOUBLE_NAN.equals(alpha))
                    return false;
                if (eval.DOUBLE_NAN.equals(beta))
                    return false;
                if (alpha.equals(beta))
                    return false;
                if (eval.DOUBLE_PINF.equals(beta))
                    return true;
                return eval.DOUBLE_NINF.equals(alpha);
        }
    }

    /**
     * Determine whether a Prolog numbers is less or equal than a Prolog number.
     * This implement compareQuietLessEqual IEEE 754-2019 §5.6.1.
     *
     * @param alpha The first Prolog number.
     * @param beta The second Prolog number.
     * @return True if the first is less or equal than the second, otherwise false.
     */
    private static boolean number_lessequal(Number alpha, Number beta) {
        switch (Math.max(numTypeWithInf(alpha), numTypeWithInf(beta))) {
            case NUM_INTEGER:
            case NUM_BIG_INTEGER:
                return integer_compare(alpha, beta) <= 0;
            case NUM_DOUBLE:
                return Double.compare(special.narrow_float(alpha),
                        special.narrow_float(beta)) <= 0;
            default:
                if (eval.DOUBLE_NAN.equals(alpha))
                    return false;
                if (eval.DOUBLE_NAN.equals(beta))
                    return false;
                if (alpha.equals(beta))
                    return true;
                if (eval.DOUBLE_PINF.equals(beta))
                    return true;
                return eval.DOUBLE_NINF.equals(alpha);
        }
    }

    private static int numType(Number m) {
        if (m instanceof Integer) {
            return NUM_INTEGER;
        } else if (m instanceof BigInteger) {
            return NUM_BIG_INTEGER;
        } else {
            return NUM_DOUBLE;
        }
    }

    private static int numTypeWithInf(Number m) {
        if (m instanceof Integer) {
            return NUM_INTEGER;
        } else if (m instanceof BigInteger) {
            return NUM_BIG_INTEGER;
        } else if (!Machine.is_special(m)) {
            return NUM_DOUBLE;
        } else {
            return NUM_SPECIAL;
        }
    }

    public static int integer_compare(Number alfa, Number beta) {
        if (alfa instanceof Integer) {
            if (beta instanceof Integer) {
                return ((Integer) alfa).compareTo((Integer) beta);
            } else {
                return -((BigInteger) beta).signum();
            }
        } else {
            if (beta instanceof Integer) {
                return ((BigInteger) alfa).signum();
            } else {
                return ((BigInteger) alfa).compareTo((BigInteger) beta);
            }
        }
    }

    /******************************************************************/
    /* sin/3, cos/2, tan/2, asin/2, acos/2, atan/2 and pi/1.          */
    /******************************************************************/

    /**
     * sin(A, B): [ISO 9.3.2]
     * The predicate succeeds in B with the sine of A.
     */
    private static Number arit_sin(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.sin(
                special.narrow_float(alpha)));
    }

    /**
     * cos(A, B): [ISO 9.3.3]
     * The predicate succeeds in B with the cosine of A.
     */
    private static Number arit_cos(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.cos(
                special.narrow_float(alpha)));
    }

    /**
     * tan(A, B): [TC2 9.3.14]
     * The predicate succeeds in B with the tangent of A.
     */
    private static Number arit_tan(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.tan(
                special.narrow_float(alpha)));
    }

    /**
     * asin(A, B): [TC2 9.3.11]
     * The predicate succeeds in B with the arcus sine of A.
     */
    private static Number arit_asin(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.asin(
                special.narrow_float(alpha)));
    }

    /**
     * acos(A, B): [TC2 9.3.12]
     * The predicate succeeds in B with the arcus cosine of A.
     */
    private static Number arit_acos(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.acos(
                special.narrow_float(alpha)));
    }

    /**
     * atan(A, B): [ISO 9.3.4]
     * The predicate succeeds in B with the arcus tangent of A.
     */
    private static Number arit_atan(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.atan(
                special.narrow_float(alpha)));
    }

    /**
     * pi(A): [TC2 9.3.15]
     * The predicate succeeds in A with π.
     */
    private static Number arit_pi(Object[] args) {
        return DOUBLE_PI;
    }

    /******************************************************************/
    /* (**)/3, exp/2, log/2, sqrt/2, e/1, epsilon/1 and atan2/3       */
    /******************************************************************/

    /**
     * **(A, B, C): [ISO 9.3.1]
     * The predicate succeeds in C with A float power by B.
     */
    private static Number arit_pow(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        return special.norm_float(Math.pow(
                special.narrow_float(alpha),
                special.narrow_float(beta)));
    }

    /**
     * exp(A, B): [ISO 9.3.5]
     * The predicate succeeds in B with e power by A.
     */
    private static Number arit_exp(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.exp(
                special.narrow_float(alpha)));
    }

    /**
     * log(A, B): [ISO 9.3.6]
     * The predicate succeeds in B with the natural logarithm of A.
     */
    private static Number arit_log(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.log(
                special.narrow_float(alpha)));
    }

    /**
     * sqrt(A, B): [ISO 9.3.7]
     * The predicate succeeds in B with the square root of A.
     */
    private static Number arit_sqrt(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        return special.norm_float(Math.sqrt(
                special.narrow_float(alpha)));
    }

    /**
     * e(A): [N208 9.7.2]
     * The predicate succeeds in A with the Euler number.
     */
    private static Number arit_e(Object[] args) {
        return DOUBLE_E;
    }

    /**
     * epsilon(A): [N208 9.7.3]
     * The predicate succeeds in A with the machine epsilon.
     */
    private static Number arit_epsilon(Object[] args) {
        return DOUBLE_EPSILON;
    }

    /**
     * atan2(A, B, C): [TC2 9.3.13]
     * The predicate succeeds in C with the arc tangent of A and B.
     */
    private static Number arit_atan2(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        Number beta = Machine.exec_eval(args[1]);
        double y = special.narrow_float(alpha);
        double x = special.narrow_float(beta);
        if (y == 0.0 && x == 0.0) {
            throw Machine.make_error(new Store.Compound("evaluation_error",
                    new Object[]{"undefined"}));
        } else {
            return special.norm_float(Math.atan2(y, x));
        }
    }

    /*********************************************************************/
    /* (\)/2, (/\)/3, (\/)/3, (xor)/3, (>>)/3 and (<</3)                 */
    /*********************************************************************/

    /**
     * \(A, B): [ISO 9.4.5]
     * The predicate succeeds in B with the bitwise not of A.
     */
    private static Number arit_not(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        if (alpha instanceof Integer) {
            return Integer.valueOf(~alpha.intValue());
        } else {
            return special.norm_bigint(((BigInteger) alpha).not());
        }
    }

    /**
     * /\(A, B, C): [ISO 9.4.3]
     * The predicate succeeds in C with the bitwise and of A and B.
     */
    private static Number arit_and(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() & beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).and(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * \/(A, B, C): [ISO 9.4.4]
     * The predicate succeeds in C with the bitwise or of A and B.
     */
    private static Number arit_or(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() | beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).or(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * xor(A, B, C): [TC2 9.4.6]
     * The predicate succeeds in C with the bitwise xor of A and B.
     */
    private static Number arit_xor(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            return Integer.valueOf(alpha.intValue() ^ beta.intValue());
        } else {
            BigInteger val = special.widen_bigint(alpha).xor(
                    special.widen_bigint(beta));
            return special.norm_bigint(val);
        }
    }

    /**
     * >>(A, B, C): [ISO 9.4.1]
     * The predicate succeeds in C with A shift right by B.
     */
    private static Number arit_shiftright(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int y = beta.intValue();
            if (y == 0) {
                return alpha;
            } else if (y > 0 && y <= 31) {
                return Integer.valueOf(alpha.intValue() >> y);
            } else if (y < 0 && -31 <= y) {
                long val = (long) alpha.intValue() << (-y);
                return special.norm_smallint(val);
            } else {
                return special.norm_bigint(BigInteger.valueOf(alpha.intValue()).shiftRight(y));
            }
        } else {
            if (beta instanceof BigInteger)
                throw Machine.make_error(new Store.Compound("representation_error",
                        new Object[]{"int"}));
            int y = beta.intValue();
            return special.norm_bigint(((BigInteger) alpha).shiftRight(y));
        }
    }

    /**
     * <<(A, B, C): [ISO 9.4.2]
     * The predicate succeeds in C with A shift left by B.
     */
    private static Number arit_shiftleft(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number beta = Machine.exec_eval(args[1]);
        special.check_integer(beta);
        if (alpha instanceof Integer && beta instanceof Integer) {
            int y = beta.intValue();
            if (y == 0) {
                return alpha;
            } else if (y > 0 && y <= 31) {
                long val = (long) alpha.intValue() << y;
                return special.norm_smallint(val);
            } else if (y < 0 && -31 <= y) {
                return Integer.valueOf(alpha.intValue() >> (-y));
            } else {
                return special.norm_bigint(BigInteger.valueOf(alpha.intValue()).shiftLeft(y));
            }
        } else {
            if (beta instanceof BigInteger)
                throw Machine.make_error(new Store.Compound("representation_error",
                        new Object[]{"int"}));
            int y = beta.intValue();
            return special.norm_bigint(((BigInteger) alpha).shiftLeft(y));
        }
    }

    /***************************************************************/
    /* ==/2 and \==/2                                              */
    /***************************************************************/

    /**
     * S == T: [ISO 8.4.1]
     * The built-in succeeds when S and T are syntactically equivalent
     * Prolog terms, otherwise the built-in fails.
     */
    private static boolean test_equal(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object beta = Machine.exec_build(args[1]);
        return equal_term(alpha, beta);
    }

    /**
     * S \== T: [ISO 8.4.1]
     * The built-in succeeds when S and T are not syntactically equivalent
     * Prolog terms, otherwise the built-in fails.
     */
    private static boolean test_notequal(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object beta = Machine.exec_build(args[1]);
        return !equal_term(alpha, beta);
    }

    /**
     * Determine whether two Prolog terms are syntactically equivalent.
     * Can handle cyclic terms and deep recursion.
     *
     * @param first  The first Prolog term.
     * @param second The second Prolog term.
     * @return True if they are syntactically equivalent, otherwise false.
     */
    public static boolean equal_term(Object first, Object second) {
        List stack = null;
        List log = null;
        try {
            for (; ; ) {
                first = Store.deref(first);
                second = Store.deref(second);
                if (!Store.is_structure(first)) {
                    if (!Objects.equals(first, second))
                        break;
                } else if (!Store.is_structure(second)) {
                    break;
                } else if (((Store.Structure) first).args.length !=
                        ((Store.Structure) second).args.length) {
                    break;
                } else {
                    first = Machine.union_find((Store.Structure) first);
                    second = Machine.union_find((Store.Structure) second);
                    if (first != second) {
                        if (Machine.is_frozen(first) && Machine.is_frozen(second) &&
                                ((Machine.Frozen)first).hash != ((Machine.Frozen)second).hash)
                            break;
                        if (!((Store.Structure) first).functor.equals(
                                ((Store.Structure) second).functor))
                            break;
                        log = Machine.union_add(log, (Store.Structure) first,
                                (Store.Structure) second);
                        if (0 != ((Store.Structure) first).args.length - 1) {
                            Store.Item item2 = new Store.Item((Store.Structure)first, second, 0);
                            stack = Store.stack_push(stack, item2);
                        }
                        first = ((Store.Structure) first).args[0];
                        second = ((Store.Structure) second).args[0];
                        continue;
                    }
                }
                Store.Item item = (Store.Item)Store.stack_peek(stack);
                if (item == null) {
                    return true;
                } else {
                    item.idx++;
                    first = item.first.args[item.idx];
                    second = ((Store.Structure)item.second).args[item.idx];
                    if (item.idx == item.first.args.length - 1)
                        Store.stack_pop(stack);
                }
            }
            return false;
        } finally {
            Machine.union_undo(log);
        }
    }

    /******************************************************************/
    /* Visitor Triple                                                 */
    /******************************************************************/

    public static class Triple {
        public Object backup;
        public Object accum;
        public Object[] children;

        /**
         * Create a visitor triple.
         *
         * @param backup The backup functor.
         * @param accum The accumlated value.
         * @param children The argument children.
         */
        public Triple(Object backup, Object accum, Object[] children) {
            this.backup = backup;
            this.accum = accum;
            this.children = children;
        }

    }

    /**
     * Check whether an object is a visitor triple.
     *
     * @param obj The object.
     * @return boolean True if the object is a triple, otherwise false.
     */
    public static boolean is_triple(Object obj) {
        return obj instanceof Triple;
    }

    /***************************************************************/
    /* term_hash/2                                                 */
    /***************************************************************/

    /**
     * term_hash(X, H):
     * The predicate succeeds in H with the term hash of the term X.
     */
    private static boolean test_term_hash(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object res;
        try {
            res = walk_compute(alpha, eval::init_hash, eval::union_hash);
        } finally {
            walk_uncompute(alpha);
        }
        return Machine.exec_unify(args[1], res);
    }

    private static Integer init_hash(Object first) {
        if (Store.is_compound(first)) {
            return Integer.valueOf(Objects.hashCode(((Store.Structure) first).functor));
        } else if (Store.is_variable(first)) {
            return Integer.valueOf(Store.variable_serno((Store.Variable) first));
        } else if (Machine.is_frozen(first)) {
            return Integer.valueOf(((Machine.Frozen)first).hash);
        } else {
            return Integer.valueOf(Objects.hashCode(first));
        }
    }

    private static Integer union_hash(Object alpha, Object beta) {
        int first = ((Integer)alpha).intValue();
        int second = ((Integer)beta).intValue();
        return Integer.valueOf(first * 31 + second);
    }

    public static Object walk_compute(Object first, Function init, BiFunction reduce) {
        List stack = null;
        for (; ; ) {
            first = Store.deref(first);
            if (Store.is_compound(first)) {
                if (!is_triple(((Store.Structure) first).functor)) {
                    ((Store.Structure) first).functor = new Triple(
                            ((Store.Structure) first).functor,
                            init.apply(first), null);
                    ((Store.Compound) first).walk &= ~Store.VAR_MASK_SERNO;
                    stack = Store.stack_push(stack, first);
                    first = ((Store.Structure) first).args[0];
                    continue;
                } else {
                    first = ((Triple) ((Store.Structure) first).functor).accum;
                }
            } else {
                first = init.apply(first);
            }
            Store.Compound item = (Store.Compound) Store.stack_peek(stack);
            while (item != null &&
                    (item.walk & Store.VAR_MASK_SERNO) == item.args.length - 1) {
                first = reduce.apply(((Triple) item.functor).accum, first);
                ((Triple) item.functor).accum = first;
                Store.stack_pop(stack);
                item = (Store.Compound) Store.stack_peek(stack);
            }
            if (item == null) {
                return first;
            } else {
                first = reduce.apply(((Triple) item.functor).accum, first);
                ((Triple) item.functor).accum = first;
                item.walk++;
                first = item.args[item.walk & Store.VAR_MASK_SERNO];
            }
        }
    }

    public static void walk_uncompute(Object first) {
        List stack = null;
        for (; ; ) {
            first = Store.deref(first);
            if (Store.is_compound(first)) {
                if (is_triple(((Store.Structure) first).functor)) {
                    ((Store.Structure) first).functor = ((Triple) ((Store.Structure) first).functor).backup;
                    if (0 != ((Store.Structure) first).args.length - 1) {
                        ((Store.Compound) first).walk &= ~Store.VAR_MASK_SERNO;
                        stack = Store.stack_push(stack, first);
                    }
                    first = ((Store.Structure) first).args[0];
                    continue;
                }
            }
            Store.Compound item = (Store.Compound) Store.stack_peek(stack);
            if (item == null) {
                return;
            } else {
                item.walk++;
                first = item.args[item.walk & Store.VAR_MASK_SERNO];
                if ((item.walk & Store.VAR_MASK_SERNO) == item.args.length - 1)
                    Store.stack_pop(stack);
            }
        }
    }

    /******************************************************************/
    /* atom_codes/2 and char_code/2                                   */
    /******************************************************************/

    /**
     * atom_codes(A, L): [ISO 8.16.5]
     * If A is a variable, the built-in succeeds in A with the atom
     * for the Prolog list L. Otherwise the built-in succeeds in L
     * with the Prolog list from the atom A.
     */
    private static boolean test_atom_codes(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object res = Machine.exec_build(args[1]);
            res = atom_codes_pack(res);
            return Machine.exec_unify(text, res);
        } else {
            text = Machine.exec_build(text);
            special.check_atom(text);
            text = atom_codes_unpack((String) text);
            return Machine.exec_unify(args[1], text);
        }
    }

    private static String atom_codes_pack(Object peek) {
        int i = 0;
        StringBuilder buf = new StringBuilder();
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            Object alpha = Store.deref(((Store.Structure) peek).args[0]);
            special.check_integer(alpha);
            int ch = (!Machine.is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
            if (ch < 0 || ch > 0x10FFFF)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"character_code", alpha}));
            buf.appendCodePoint(ch);
            i++;
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        special.check_nil(peek);
        return buf.toString();
    }

    private static Object atom_codes_unpack(String text) {
        Store.Structure back = null;
        Object res = null;
        int i = 0;
        while (i < text.length()) {
            int ch = text.codePointAt(i);
            Store.Structure peek = new Store.Compound(".",
                    new Object[]{Integer.valueOf(ch), Store.UNDEF_OBJ});
            if (back != null) {
                back.args[1] = peek;
            } else {
                res = peek;
            }
            back = peek;
            i += Character.charCount(ch);
        }
        if (back != null) {
            back.args[1] = "[]";
        } else {
            res = "[]";
        }
        return res;
    }

    /**
     * char_code(C, N): [ISO 8.16.6]
     * If C is a variable, the built-in succeeds in C with the
     * character for the code N. Otherwise the built-in succeeds
     * in N with the code from character C.
     */
    private static boolean test_char_code(Object[] args) {
        Object text = Machine.exec_deref(args[0]);
        if (Store.is_variable(text) || Machine.is_pending(text)) {
            Object alpha = Machine.exec_build(args[1]);
            special.check_integer(alpha);
            int ch = (!Machine.is_bigint(alpha) ? ((Integer) alpha).intValue() : -1);
            if (ch < 0 || ch > 0x10FFFF)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"character_code", alpha}));
            if (Character.isBmpCodePoint(ch)) {
                alpha = String.valueOf((char) ch);
            } else {
                alpha = new String(Character.toChars(ch));
            }
            return Machine.exec_unify(text, alpha);
        } else {
            text = Machine.exec_build(text);
            special.check_atom(text);
            int ch;
            if (((String) text).length() == 0 ||
                    ((String) text).length() != Character.charCount(
                            ch = ((String) text).codePointAt(0)))
                throw Machine.make_error(new Store.Compound("type_error",
                        new Object[]{"character", text}));
            return Machine.exec_unify(args[1], Integer.valueOf(ch));
        }
    }

    /******************************************************************/
    /* atom_length/2 and atom_join/2                                  */
    /******************************************************************/

    /**
     * atom_length(X, Y): [ISO 8.16.1]
     * The predicate succeeds in Y with the length of the atom X.
     */
    private static boolean test_atom_length(Object[] args) {
        Object text = Machine.exec_build(args[0]);
        special.check_atom(text);
        int res = ((String)text).codePointCount(0, ((String) text).length());
        return Machine.exec_unify(args[1], Integer.valueOf(res));
    }

    /**
     * atom_join(L, A):
     * The built-in succeeds in A with the join of the atoms L.
     */
    private static boolean test_atom_join(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object res = atom_join(alpha);
        return Machine.exec_unify(args[1], res);
    }

    private static String atom_join(Object peek) {
        StringBuilder buf = new StringBuilder();
        int i = 0;
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            Object val = Store.deref(((Store.Structure) peek).args[0]);
            special.check_atom(val);
            buf.append((String) val);
            peek = Store.deref(((Store.Structure) peek).args[1]);
            i++;
        }
        special.check_nil(peek);
        return buf.toString();
    }

    /******************************************************************/
    /* atom_concat/3                                                  */
    /******************************************************************/

    /**
     * atom_concat(X, Y, Z): [ISO 8.16.2]
     * The built-in succeeds when Z is the concatenation of X and Y.
     */
    private static Object special_atom_concat(Object[] args) {
        Object first = Store.deref(args[0]);
        Object second = Store.deref(args[1]);
        Object third = Store.deref(args[2]);
        if (Store.is_variable(second)) {
            if (Store.is_variable(first)) {
                special.check_atom(third);
                return solve2_concat(args, null, 0, null);
            } else {
                special.check_atom(first);
                special.check_atom(third);
                if (!((String) third).startsWith((String) first))
                    return Boolean.FALSE;
                if (!Machine.unify(second, ((String) third).substring(((String) first).length())))
                    return Boolean.FALSE;
            }
        } else if (Store.is_variable(first)) {
            special.check_atom(second);
            special.check_atom(third);
            if (!((String) third).endsWith((String) second))
                return Boolean.FALSE;
            if (!Machine.unify(first, ((String) third).substring(0,
                    ((String) third).length() - ((String) second).length())))
                return Boolean.FALSE;
        } else {
            special.check_atom(first);
            special.check_atom(second);
            if (!Machine.unify(third, first + (String) second))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_concat(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_concat(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_concat(Object[] args, Object rope, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[2]);
        Store.Variable mark = Machine.trail;
        while (at <= text.length()) {
            if (Machine.unify(args[0], text.substring(0, at))) {
                if (Machine.unify(args[1], text.substring(at))) {
                    at = atom_succ(text, at);
                    if (at <= text.length()) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_concat, null, at, mark);
                        } else {
                            choice.at = at;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            at = atom_succ(text, at);
        }
        return Boolean.FALSE;
    }

    private static int atom_succ(String text, int at) {
        if (at < text.length()) {
            int ch = text.codePointAt(at);
            return at + Character.charCount(ch);
        } else {
            return at + 1;
        }
    }

    /******************************************************************/
    /* sys_atom_match/3 and sys_atom_part/4                           */
    /******************************************************************/

    /**
     * sys_atom_match(X, Y, Z):
     * The built-in succeeds if X has substring Y at Z.
     */
    private static Object special_sys_atom_match(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object part = Store.deref(args[1]);
        special.check_atom(part);
        Object alpha = Store.deref(args[2]);
        if (Store.is_variable(alpha)) {
            return solve2_match(args, Integer.valueOf(0), 0, null);
        } else {
            special.check_integer(alpha);
            if (Machine.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (!((String) text).startsWith((String) part, from))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    public static int atom_offset(String text, int pos, int alpha) {
        if (alpha < 0)
            return -1;
        while (alpha > 0 && pos < text.length()) {
            int ch = text.codePointAt(pos);
            pos += Character.charCount(ch);
            alpha--;
        }
        if (alpha > 0)
            return -1;
        return pos;
    }

    private static Object solve_match(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_match(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_match(Object[] args, Object data, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        String part = (String) Store.deref(args[1]);
        int res = ((Integer) data).intValue();
        Store.Variable mark = Machine.trail;
        while (at + part.length() <= text.length()) {
            int pos = text.indexOf(part, at);
            if (pos < 0)
                return Boolean.FALSE;
            res += text.codePointCount(at, pos);
            at = pos;
            if (Machine.unify(args[2], Integer.valueOf(res))) {
                at = atom_succ(text, at);
                res++;
                if (at + part.length() <= text.length()) {
                    if (choice == null) {
                        choice = new Machine.Choice(eval::solve_match, Integer.valueOf(res), at, mark);
                    } else {
                        choice.data = Integer.valueOf(res);
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return Boolean.TRUE;
            }
            Machine.unbind(mark);
            at = atom_succ(text, at);
            res++;
        }
        return Boolean.FALSE;
    }

    /**
     * sys_atom_part(X, Y, Z, T):
     * The built-in succeeds in T with the substring at
     * offset Y and with length Z from X.
     */
    private static Object special_sys_atom_part(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object alpha = Store.deref(args[1]);
        if (Store.is_variable(alpha)) {
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (Machine.is_bigint(beta))
                return Boolean.FALSE;
            int at = ((Integer) beta).intValue();
            int res = atom_offset((String) text, 0, at);
            if (res < 0)
                return Boolean.FALSE;
            return solve2_part(args, new int[]{0, 0}, res, null);
        } else {
            special.check_integer(alpha);
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (Machine.is_bigint(alpha))
                return Boolean.FALSE;
            int at = ((Integer) alpha).intValue();
            int from = atom_offset((String) text, 0, at);
            if (from < 0)
                return Boolean.FALSE;
            if (Machine.is_bigint(beta))
                return Boolean.FALSE;
            at = ((Integer) beta).intValue();
            int to = atom_offset((String) text, from, at);
            if (to < 0)
                return Boolean.FALSE;
            if (!Machine.unify(args[3], ((String) text).substring(from, to)))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_part(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_part(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve2_part(Object[] args, Object data, int to, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        int[] pair = (int[]) data;
        Store.Variable mark = Machine.trail;
        while (to <= text.length()) {
            if (Machine.unify(args[1], Integer.valueOf(pair[1]))) {
                if (Machine.unify(args[3], text.substring(pair[0], to))) {
                    pair[0] = atom_succ(text, pair[0]);
                    pair[1]++;
                    to = atom_succ(text, to);
                    if (to <= text.length()) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_part, pair, to, mark);
                        } else {
                            choice.at = to;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            pair[0] = atom_succ(text, pair[0]);
            pair[1]++;
            to = atom_succ(text, to);
        }
        return Boolean.FALSE;
    }

    /******************************************************************/
    /* sys_last_atom_match/3 and sys_last_atom_part/4                 */
    /******************************************************************/

    /**
     * sys_last_atom_match(X, Y, Z):
     * The built-in succeeds if X has substring Y at Z.
     */
    public static Object special_sys_last_atom_match(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object part = Store.deref(args[1]);
        special.check_atom(part);
        Object alpha = Store.deref(args[2]);
        if (Store.is_variable(alpha)) {
            int at = ((String) text).length() - ((String) part).length();
            if (at < 0)
                return Boolean.FALSE;
            int res = ((String) text).codePointCount(0, at);
            return solve2_last_match(args, Integer.valueOf(res), at, null);
        } else {
            special.check_integer(alpha);
            if (Machine.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (!((String) text).startsWith((String) part, from))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static Object solve_last_match(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_last_match(((Store.Structure) goal).args, rope, at, choice);
    }

    public static Object solve2_last_match(Object[] args, Object data, int at, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        String part = (String) Store.deref(args[1]);
        int res = ((Integer) data).intValue();
        Store.Variable mark = Machine.trail;
        while (at >= 0) {
            int pos = text.lastIndexOf(part, at);
            if (pos < 0)
                return Boolean.FALSE;
            res -= text.codePointCount(pos, at);
            at = pos;
            if (Machine.unify(args[2], Integer.valueOf(res))) {
                at = atom_pred(text, at);
                res--;
                if (at >= 0) {
                    if (choice == null) {
                        choice = new Machine.Choice(eval::solve_last_match, res, at, mark);
                    } else {
                        choice.data = res;
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return Boolean.TRUE;
            }
            Machine.unbind(mark);
            at = atom_pred(text, at);
            res--;
        }
        return Boolean.FALSE;
    }

    private static int atom_pred(String text, int at) {
        if (0 < at) {
            int ch = text.codePointBefore(at);
            return at - Character.charCount(ch);
        } else {
            return at - 1;
        }
    }

    /**
     * sys_last_atom_part(X, Y, Z, T):
     * The built-in succeeds in T with the substring at
     * offset Y and with length Z from X.
     */
    public static Object special_sys_last_atom_part(Object[] args) {
        Object text = Store.deref(args[0]);
        special.check_atom(text);
        Object alpha = Store.deref(args[1]);
        if (Store.is_variable(alpha)) {
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (Machine.is_bigint(beta))
                return Boolean.FALSE;
            int pos = last_atom_offset((String) text, ((String) text).length(),
                    ((Integer) beta).intValue());
            if (pos < 0)
                return Boolean.FALSE;
            int res = ((String) text).codePointCount(0, pos);
            return solve2_last_part(args, new int[]{pos, res},
                    ((String) text).length(), null);
        } else {
            special.check_integer(alpha);
            Object beta = Store.deref(args[2]);
            special.check_integer(beta);
            if (Machine.is_bigint(alpha))
                return Boolean.FALSE;
            int from = atom_offset((String) text, 0, ((Integer) alpha).intValue());
            if (from < 0)
                return Boolean.FALSE;
            if (Machine.is_bigint(beta))
                return Boolean.FALSE;
            int to = atom_offset((String) text, from, ((Integer) beta).intValue());
            if (to < 0)
                return Boolean.FALSE;
            if (!Machine.unify(args[3], ((String) text).substring(from, to)))
                return Boolean.FALSE;
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    private static int last_atom_offset(String text, int pos, int alpha) {
        if (alpha < 0)
            return -1;
        while (alpha > 0 && 0 < pos) {
            int ch = text.codePointBefore(pos);
            pos -= Character.charCount(ch);
            alpha--;
        }
        if (alpha > 0)
            return -1;
        return pos;
    }

    private static Object solve_last_part(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_last_part(((Store.Structure) goal).args, rope, at, choice);
    }

    public static Object solve2_last_part(Object[] args, Object data, int to, Machine.Choice choice) {
        String text = (String) Store.deref(args[0]);
        int[] pair = (int[]) data;
        Store.Variable mark = Machine.trail;
        while (pair[0] >= 0) {
            if (Machine.unify(args[1], Integer.valueOf(pair[1]))) {
                if (Machine.unify(args[3], text.substring(pair[0], to))) {
                    pair[0] = atom_pred(text, pair[0]);
                    pair[1]--;
                    to = atom_pred(text, to);
                    if (pair[0] >= 0) {
                        if (choice == null) {
                            choice = new Machine.Choice(eval::solve_last_part, pair, to, mark);
                        } else {
                            choice.at = to;
                        }
                        Machine.more(choice);
                    }
                    Machine.cont(((Store.Structure) Machine.call).args[1]);
                    return Boolean.TRUE;
                }
            }
            Machine.unbind(mark);
            pair[0] = atom_pred(text, pair[0]);
            pair[1]--;
            to = atom_pred(text, to);
        }
        return Boolean.FALSE;
    }

    /*********************************************************************/
    /* Eval Init                                                         */
    /*********************************************************************/

    public static void boot() {
        // number specials, basic predicates
        Store.set("is", 2, special.make_check(eval::test_eval));
        Store.set("-", 2, special.make_arithmetic(eval::arit_neg));
        Store.set("abs", 2, special.make_arithmetic(eval::arit_abs));
        Store.set("sign", 2, special.make_arithmetic(eval::arit_sign));

        // number specials, basic operations
        Store.set("+", 3, special.make_arithmetic(eval::arit_add));
        Store.set("-", 3, special.make_arithmetic(eval::arit_sub));
        Store.set("*", 3, special.make_arithmetic(eval::arit_mul));
        Store.set("/", 3, special.make_arithmetic(eval::arit_quot));
        Store.set("//", 3, special.make_arithmetic(eval::arit_intquot));
        Store.set("rem", 3, special.make_arithmetic(eval::arit_rem));

        // number specials, more operations
        Store.set("float", 2, special.make_arithmetic(eval::arit_float));
        Store.set("^", 3, special.make_arithmetic(eval::arit_intpow));
        Store.set("div", 3, special.make_arithmetic(eval::arit_div));
        Store.set("mod", 3, special.make_arithmetic(eval::arit_mod));

        // number specials, magnitude operations
        Store.set("min", 3, special.make_arithmetic(eval::arit_min));
        Store.set("max", 3, special.make_arithmetic(eval::arit_max));
        Store.set("inf", 1, special.make_arithmetic(eval::arit_inf));
        Store.set("nan", 1, special.make_arithmetic(eval::arit_nan));

        // number specials, rounding operations
        Store.set("truncate", 2, special.make_arithmetic(eval::arit_truncate));
        Store.set("floor", 2, special.make_arithmetic(eval::arit_floor));
        Store.set("ceiling", 2, special.make_arithmetic(eval::arit_ceiling));
        Store.set("round", 2, special.make_arithmetic(eval::arit_round));

        // number specials, magnitude predicates
        Store.set("=:=", 2, special.make_check(eval::test_numberequal));
        Store.set("=\\=", 2, special.make_check(eval::test_numbernotequal));
        Store.set("<", 2, special.make_check(eval::test_numberless));
        Store.set(">=", 2, special.make_check(eval::test_numbergreaterequal));
        Store.set(">", 2, special.make_check(eval::test_numbergreater));
        Store.set("=<", 2, special.make_check(eval::test_numberlessequal));

        // number specials, trigonometric operations
        Store.set("sin", 2, special.make_arithmetic(eval::arit_sin));
        Store.set("cos", 2, special.make_arithmetic(eval::arit_cos));
        Store.set("tan", 2, special.make_arithmetic(eval::arit_tan));
        Store.set("asin", 2, special.make_arithmetic(eval::arit_asin));
        Store.set("acos", 2, special.make_arithmetic(eval::arit_acos));
        Store.set("atan", 2, special.make_arithmetic(eval::arit_atan));
        Store.set("pi", 1, special.make_arithmetic(eval::arit_pi));

        // number specials, exponential operations
        Store.set("**", 3, special.make_arithmetic(eval::arit_pow));
        Store.set("exp", 2, special.make_arithmetic(eval::arit_exp));
        Store.set("log", 2, special.make_arithmetic(eval::arit_log));
        Store.set("sqrt", 2, special.make_arithmetic(eval::arit_sqrt));
        Store.set("e", 1, special.make_arithmetic(eval::arit_e));
        Store.set("epsilon", 1, special.make_arithmetic(eval::arit_epsilon));
        Store.set("atan2", 3, special.make_arithmetic(eval::arit_atan2));

        // number specials, bitwise operations
        Store.set("\\", 2, special.make_arithmetic(eval::arit_not));
        Store.set("/\\", 3, special.make_arithmetic(eval::arit_and));
        Store.set("\\/", 3, special.make_arithmetic(eval::arit_or));
        Store.set("xor", 3, special.make_arithmetic(eval::arit_xor));
        Store.set(">>", 3, special.make_arithmetic(eval::arit_shiftright));
        Store.set("<<", 3, special.make_arithmetic(eval::arit_shiftleft));

        // term specials, miscellaneous equality
        Store.set("==", 2, special.make_check(eval::test_equal));
        Store.set("\\==", 2, special.make_check(eval::test_notequal));
        Store.set("term_hash", 2, special.make_check(eval::test_term_hash));

        // atom specials, deterministic
        Store.set("atom_codes", 2, special.make_check(eval::test_atom_codes));
        Store.set("char_code", 2, special.make_check(eval::test_char_code));
        Store.set("atom_length", 2, special.make_check(eval::test_atom_length));
        Store.set("atom_join", 2, special.make_check(eval::test_atom_join));

        // atom specials, non-deterministic
        Store.set("atom_concat", 3, special.make_special(eval::special_atom_concat));
        Store.set("sys_atom_match", 3, special.make_special(eval::special_sys_atom_match));
        Store.set("sys_atom_part", 4, special.make_special(eval::special_sys_atom_part));
        Store.set("sys_last_atom_match", 3, special.make_special(eval::special_sys_last_atom_match));
        Store.set("sys_last_atom_part", 4, special.make_special(eval::special_sys_last_atom_part));
    }

}
