package doglet;

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

import java.math.BigInteger;

/**
 * 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, sqrtrem/3 and isqrt/3                               */
    /******************************************************************/

    /**
     * testbit(X, Y):
     * The predicate succeeds when X /\ (1 << Y) =\= 0.
     */
    private static boolean test_testbit(Object[] args) {
        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 Machine.make_error(new Store.Compound("representation_error",
                    new Object[]{"int"}));
        int k = ((Number) beta).intValue();
        if (k < 0)
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"not_less_than_zero", 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);
        }
    }

    /**
     * sqrtrem(X, Y, Z):
     * If X is an integer then the predicate succeeds in Y with the
     * integer square root of X, and in Z with the remainder X-Y*Y.
     */
    private static boolean test_sqrtrem(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        special.check_integer(alpha);
        Number ressqrt;
        Number resrem;
        if (alpha instanceof Integer) {
            int k = ((Integer)alpha).intValue();
            if (k < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", alpha}));
            int j = (int)Math.floor(Math.sqrt(k));
            ressqrt = Integer.valueOf(j);
            resrem = Integer.valueOf(k-j*j);
        } else {
            if (((BigInteger)alpha).signum() < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", alpha}));
            BigInteger[] res2 = ((BigInteger)alpha).sqrtAndRemainder();
            ressqrt = special.norm_bigint(res2[0]);
            resrem = special.norm_bigint(res2[1]);
        }
        if (!Machine.exec_unify(args[1],ressqrt))
            return false;
        return Machine.exec_unify(args[2],resrem);
    }

    /**
     * isqrt(X, Y):
     * If X is an integer then the predicate succeeds in Y with the
     * integer square root of X.
     */
    private static Number arit_isqrt(Object[] args) {
        Number alpha = Machine.exec_eval(args[0]);
        special.check_integer(alpha);
        Number ressqrt;
        if (alpha instanceof Integer) {
            int k = alpha.intValue();
            if (k < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", alpha}));
            int j = (int)Math.floor(Math.sqrt(k));
            ressqrt = Integer.valueOf(j);
        } else {
            if (((BigInteger)alpha).signum() < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", alpha}));
            BigInteger res2 = ((BigInteger)alpha).sqrt();
            ressqrt = special.norm_bigint(res2);
        }
        return ressqrt;
    }

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

    public static void main() {
        Store.set("random", 1, special.make_check(bitlib::test_random));
        Store.set("msb", 2, special.make_arithmetic(bitlib::arit_msb));
        Store.set("lsb", 2, special.make_arithmetic(bitlib::arit_lsb));
        Store.set("popcount", 2, special.make_arithmetic(bitlib::arit_popcount));
        Store.set("testbit", 2, special.make_check(bitlib::test_testbit));
        Store.set("sqrtrem", 3, special.make_check(bitlib::test_sqrtrem));
        Store.set("isqrt", 2, special.make_arithmetic(bitlib::arit_isqrt));
    }

}
