package foreign;

import nova.*;

import java.math.*;

/**
 * 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 bitlib {

    /******************************************************************/
    /* Random Numbers                                                 */
    /******************************************************************/

    /**
     * random(F):
     * The predicate succeeds in F with a uniform random 64-bit
     * floating point value in the interval [0..1).
     */
    private static boolean test_random(Object[] args) {
        Object alpha = Double.valueOf(Math.random());
        return Machine.exec_unify(args[0], alpha);
    }

    /******************************************************************/
    /* msb/2, lsb/2 and popcount/2                                    */
    /******************************************************************/

    /**
     * msb(X):
     * If X is an integer, then the function returns the most significant bit.
     */
    private static Number arit_msb(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        int result;
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            result = 31 - Integer.numberOfLeadingZeros((x < 0 ? ~x : x));
        } else {
            result = ((BigInteger) alpha).bitLength() - 1;
        }
        return Integer.valueOf(result);
    }

    /**
     * lsb(X):
     * If X is an integer, then the function returns the least significant bit.
     */
    private static Number arit_lsb(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        int result;
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x == 0) {
                result = -1;
            } else {
                result = Integer.numberOfTrailingZeros(x);
            }
        } else {
            result = ((BigInteger) alpha).getLowestSetBit();
        }
        return Integer.valueOf(result);
    }

    /**
     * popcount(X):
     * If X is an integer, then the function returns the number of ones.
     */
    private static Number arit_popcount(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        int result;
        if (alpha instanceof Integer) {
            int x = alpha.intValue();
            if (x < 0) {
                x = -x;
                result = Integer.bitCount(x) + Integer.numberOfTrailingZeros(x) - 1;
            } else {
                result = Integer.bitCount(x);
            }
        } else {
            result = ((BigInteger) alpha).bitCount();
        }
        return Integer.valueOf(result);
    }

    /******************************************************************/
    /* testbit/2 and divmod/4                                         */
    /******************************************************************/

    /**
     * testbit(X, Y):
     * The predicate succeeds when X /\ (1 << Y) =\= 0.
     */
    private static boolean test_testbit(Object[] args) {
        try {
            Object alpha = Machine.exec_build(args[0]);
            special.check_integer(alpha);
            Object beta = Machine.exec_build(args[1]);
            special.check_integer(beta);
            if (beta instanceof BigInteger)
                throw new ArithmeticException("representation_error");
            int k = ((Number) beta).intValue();
            if (k < 0)
                throw new Handler.RangeException(eval.OP_DOMAIN_NOT_LESS_THAN_ZERO, (Number) beta);
            if (alpha instanceof Integer) {
                if (k < 31) {
                    return (((Number) alpha).intValue() & (1 << k)) != 0;
                } else {
                    return ((Number) alpha).intValue() < 0;
                }
            } else {
                return ((BigInteger)alpha).testBit(k);
            }
        } catch (RuntimeException x) {
            throw eval.mapRuntime(x);
        }
    }

    /**
     * divmod(X, Y, Z, T):
     * If X and Y are both integers then the predicate succeeds in
     * Z with the division of X by Y, and in T with the modulo of X by Y.
     */
    private static boolean test_divmod(Object[] args) {
        try {
            Object alpha = Machine.exec_build(args[0]);
            special.check_integer(alpha);
            Object beta = Machine.exec_build(args[1]);
            special.check_integer(beta);
            Number quot;
            Number rems;
            if (alpha instanceof Integer && beta instanceof Integer) {
                int u = ((Integer) beta).intValue();
                if (u == 0)
                    throw new ArithmeticException(eval.OP_EVALUATION_ZERO_DIVISOR);
                int v = ((Integer) alpha).intValue();
                if (v == Integer.MIN_VALUE && u == -1) {
                    quot = special.NEG_MIN_INTEGER;
                    rems = Integer.valueOf(0);
                } else {
                    int[] res2 = intDivMod(v, u);
                    quot = Integer.valueOf(res2[0]);
                    rems = Integer.valueOf(res2[1]);
                }
            } else {
                BigInteger p = special.widen_bigint((Number) beta);
                if (p.signum() == 0)
                    throw new ArithmeticException(eval.OP_EVALUATION_ZERO_DIVISOR);
                BigInteger[] res2 = bigDivMod(special.widen_bigint((Number) alpha), p);
                quot = special.norm_bigint(res2[0]);
                rems = special.norm_bigint(res2[1]);
            }
            if (!Machine.exec_unify(args[2],quot))
                return false;
            return Machine.exec_unify(args[3],rems);
        } catch (RuntimeException x) {
            throw eval.mapRuntime(x);
        }
    }

    private static int[] intDivMod(int v, int u) {
        int[] res = new int[2];
        res[0] = v / u;
        res[1] = v % u;
        if ((v < 0) != (u < 0)) {
            if (res[1] != 0) {
                res[0]--;
                res[1] += u;
            }
        }
        return res;
    }

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

    /******************************************************************/
    /* Bit Lib Init                                                   */
    /******************************************************************/

    public static void main() {
        Store.add("random", 1, special.make_check(bitlib::test_random));
        Store.add("msb", 2, special.make_arithmetic(bitlib::arit_msb));
        Store.add("lsb", 2, special.make_arithmetic(bitlib::arit_lsb));
        Store.add("popcount", 2, special.make_arithmetic(bitlib::arit_popcount));
        Store.add("testbit", 2, special.make_check(bitlib::test_testbit));
        Store.add("divmod", 4, special.make_check(bitlib::test_divmod));
    }

}
