###
# Modern Albufeira Prolog Interpreter
#
# 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.
##

from nova.core import (set, make_arithmetic, exec_build,
     make_check, exec_unify, exec_eval, check_integer,
     make_error, Compound)
import random
import math

#####################################################################
# Random Numbers                                                    #
#####################################################################

###
# random(F):
# The predicate succeeds in F with a uniform random 64-bit
# floating point value in the interval [0..1).
##
def test_random(args):
    alpha = random.random()
    return 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.
##
def arit_msb(args):
    alpha = exec_eval(args[0])
    check_integer(alpha)
    return (~alpha if alpha < 0 else alpha).bit_length() - 1


###
# lsb(X):
# If X is an integer, then the function returns the least significant bit.
##
def arit_lsb(args):
    alpha = exec_eval(args[0])
    check_integer(alpha)
    return (alpha & -alpha).bit_length() - 1


###
# popcount(X):
# If X is an integer, then the function returns the number of ones.
##
def arit_popcount(args):
    alpha = exec_eval(args[0])
    check_integer(alpha)
    if alpha < 0:
        alpha = -alpha
        return alpha.bit_count() + (alpha & -alpha).bit_length() - 2
    else:
        return alpha.bit_count()


#######################################################################
# testbit/2, sqrtrem/3 and isqrt/2                                    #
#######################################################################

###
# testbit(X, Y):
# The predicate succeeds when X /\ (1 << Y) =\= 0.
##
def test_testbit(args):
    alpha = exec_build(args[0])
    check_integer(alpha)
    beta = exec_build(args[1])
    check_integer(beta)
    if beta < 0:
        raise make_error(Compound("domain_error",
                  ["not_less_than_zero", beta]))
    return (alpha & (1 << beta)) != 0


###
# 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.
##
def test_sqrtrem(args):
    alpha = exec_build(args[0])
    check_integer(alpha)
    if alpha < 0:
        raise make_error(Compound("domain_error",
             ["not_less_than_zero", alpha]))
    ressqrt = math.isqrt(alpha)
    resrem = alpha-ressqrt*ressqrt
    if not exec_unify(args[1],ressqrt):
        return False
    return 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.
##
def arit_isqrt(args):
    alpha = exec_eval(args[0])
    check_integer(alpha)
    if alpha < 0:
        raise make_error(Compound("domain_error",
             ["not_less_than_zero", alpha]))
    return math.isqrt(alpha)


#####################################################################
# Bit Lib Init                                                      #
#####################################################################

def main():
    set("random", 1, make_check(test_random))
    set("msb", 2, make_arithmetic(arit_msb))
    set("lsb", 2, make_arithmetic(arit_lsb))
    set("popcount", 2, make_arithmetic(arit_popcount))
    set("testbit", 2, make_check(test_testbit))
    set("sqrtrem", 3, make_check(test_sqrtrem))
    set("isqrt", 2, make_arithmetic(arit_isqrt))