:- module(test_interval, [test_interval/0]).

:- use_module(library(plunit)).
:- use_module(library(debug)).
:- use_module(library(interval)).

:- set_prolog_flag(float_overflow, infinity).
:- set_prolog_flag(float_undefined, nan).
:- set_prolog_flag(float_zero_div, infinity).

test_interval :-
    run_tests([comparison, division, sqrt, power, abs, round, sin, list, no_evaluation, addition, subtraction, multiplication, exponential, unary_plus, unary_minus, max, min, errors]).

:- begin_tests(comparison).

test(<) :-
    A = 1...2,
    B = 3...4,
    interval(A < B, true).

test(=<) :-
    A = 1...3,
    B = 2...4,
    interval(A =< B, true).

test(=<) :-
    A = 1...3,
    B = 1...3,
    interval(A =< B, true).

test(>) :-
    A = 3...4,
    B = 1...2,
    interval(A > B, true).

test(>=) :-
    A = 3...4,
    B = 1...2,
    interval(A >= B, true).

test(>=) :-
    A = 3...4,
    B = 3...4,
    interval(A >= B, true).

test(=\=) :-
    A = 3...4,
    B = 1...2,
    interval(A =\= B, true).

test(=\=) :-
    A = 1...2,
    B = 1...2,
    interval(A =\= B, false).

test(=:=) :-
    A = 3...4,
    B = 1...2,
    interval(A =:= B, false).

test(=:=) :-
    A = 1...2,
    B = 1...2,
    interval(A =:= B, true).

:- end_tests(comparison).

:- begin_tests(division).

test(dividend_strictpos_divisor_zeropos) :-
    A = 1...2,
    B = 0...2,
    interval(A / B, L...U),
    L is 0.5,
    U is 1.0Inf.

test(dividend_strictpos_divisor_positive) :-
    A = 1...2,
    B = 1...2,
    interval(A / B, L...U),
    L is 0.5,
    U is 2.

test(dividend_zeropos_divisor_zeropos) :-
    A = 0...2,
    B = 0...1,
    interval(A / B, L...U),
    L is 0.0,
    U is 1.0Inf.

test(dividend_zeropos_divisor_positive) :-
    A = 0...2,
    B = 1...2,
    interval(A / B, L...U),
    L is 0.0,
    U is 2.

test(dividend_mixed_divisor_zeropos) :-
    A = -1...1,
    B = 0...1,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 1.0Inf.

test(dividend_mixed_divisor_positive) :-
    A = -1...1,
    B = 2...4,
    interval(A / B, L...U),
    L is -0.5,
    U is 0.5.

test(dividend_zeroneg_divisor_zeropos) :-
    A = -1...0,
    B = 0...1,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 0.0.

test(dividend_zeroneg_divisor_positive) :-
    A = -1...0,
    B = 2...3,
    interval(A / B, L...U),
    L is -0.5,
    U is 0.0.

test(dividend_strictneg_divisor_zeropos) :-
    A = -2... -1,
    B = 0...2,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is -0.5.

test(dividend_strictneg_divisor_positive) :-
    A = -2... -1,
    B = 2...4,
    interval(A / B, L...U),
    L is -1,
    U is -0.25.

test(dividend_strictpos_divisor_mixed) :-
    A = 1...2,
    B = -2...1,
    setof(Res, interval(A / B, Res), Results), 
    L1 is -1.0Inf,
    U1 is -0.5,
    L2 is 1,
    U2 is 1.0Inf,
    Results = [L1...U1, L2...U2].
    

test(dividend_zeropos_divisor_mixed) :-
    A = 0...1,
    B = -1...1,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 1.0Inf.

test(dividend_mixed_divisor_mixed) :-
    A = -1...1,
    B = -2...2,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 1.0Inf.

test(dividend_zeroneg_divisor_mixed) :-
    A = -1...0,
    B = -2...2,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 1.0Inf.

test(dividend_strictneg_divisor_mixed) :-
    A = -2... -1,
    B = -2...2,
    setof(Res, interval(A / B, Res), Results), 
    L1 is -1.0Inf,
    U1 is -0.5,
    L2 is 0.5,
    U2 is 1.0Inf,
    Results = [L1...U1, L2...U2].

test(dividend_strictpos_divisor_zeroneg) :-
    A = 1...2,
    B = -2...0.0,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is -0.5.

test(dividend_strictpos_divisor_negative) :-
    A = 1...2,
    B = -2... -1,
    interval(A / B, L...U),
    L is -2,
    U is -0.5.

test(dividend_zeropos_divisor_zeroneg) :-
    A = 0...1,
    B = -1...0.0,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 0.0.

test(dividend_zeropos_divisor_negative) :-
    A = 0...1,
    B = -2... -1,
    interval(A / B, L...U),
    L is -1,
    U is 0.0.

test(dividend_mixed_divisor_zeroneg) :-
    A = -1...2,
    B = -2...0.0,
    interval(A / B, L...U),
    L is -1.0Inf,
    U is 1.0Inf.

test(dividend_mixed_divisor_negative) :-
    A = -1...2,
    B = -2... -1,
    interval(A / B, L...U),
    L is -2,
    U is 1.

test(dividend_zeroneg_divisor_zeroneg) :-
    A = -1...0,
    B = -2...0.0,
    interval(A / B, L...U),
    L is 0.0,
    U is 1.0Inf.

test(dividend_zeroneg_divisor_negative) :-
    A = -1...0,
    B = -2... -1,
    interval(A / B, L...U),
    L is 0.0,
    U is 1.

test(dividend_strictneg_divisor_zeroneg) :-
    A = -2... -1,
    B = -2...0.0,
    interval(A / B, L...U),
    L is 0.5,
    U is 1.0Inf.

test(dividend_strictneg_divisor_negative) :-
    A = -2... -1,
    B = -2... -1,
    interval(A / B, L...U),
    L is 0.5,
    U is 2.

test(dividend_interval_divisor_number) :-
    A = -2... -1,
    B is 2,
   interval(A / B, L...U),
    L is -1,
    U is -0.5.

test(dividend_number_divisor_interval) :-
    A = 2,
    B = -2... -1,
    interval(A / B, L...U),
    L is -2,
    U is -1.

test(dividend_number_divisor_number) :-
    A = 1,
    B = 2,
    interval(A / B, Res),
    Res = 0.5.

:- end_tests(division).

:- begin_tests(sqrt).

test(sqrt1) :-
    A = 1...2,
    interval(sqrt(A), L...U),
    L > 0.99999,
    L < 1.00001,
    U > 1.41421,
    U < 1.41422.

test(sqrt2) :-
    A = 0...2,
    interval(sqrt(A), L...U),
    L = 0.0,
    U > 1.41421,
    U < 1.41422.

test(sqrt3) :-
    A = -1...2,
    interval(sqrt(A), Res),
    Res = 1.5NaN.

test(sqrt4) :-
    A = -2...0,
    interval(sqrt(A), Res),
    Res = 1.5NaN.

test(sqrt5) :-
    A = -2... -1,
    interval(sqrt0(A), X),
    X = 1.5NaN.

test(sqrt6) :-
    A = 1...2,
    interval(sqrt0(A), L...U),
    L > 0.99999,
    L < 1.00001,
    U > 1.41421,
    U < 1.41422.

test(sqrt7) :-
    A = 0...2,
    interval(sqrt0(A), L...U),
    L = 0.0,
    U > 1.41421,
    U < 1.41422.

test(sqrt8) :-
    A = -1...2,
    interval(sqrt0(A), L...U),
    L = 0.0,
    U > 1.41421,
    U < 1.41422.

test(sqrt9) :-
    A = -2...0,
    interval(sqrt0(A), X),
    X = 0.0.

test(sqrt10) :-
    A = -2... -1,
    interval(sqrt0(A), X),
    X = 1.5NaN.

test(sqrt11) :-
    interval(sqrt(9), X),
    X = 3.0.

test(sqrt12) :-
    interval(sqrt(-9), X),
    X = 1.5NaN.

:- end_tests(sqrt).

:- begin_tests(power).

test(power_number) :-
    Base = 2,
    Exp = 5,
    interval(Base ^ Exp, Res),
    Res is 32.

test(power_other_symbol) :-
    Base = 2,
    Exp = 5,
    interval(Base ** Exp, Res),
    Res is 32.

test(power_pos_base_even_expon) :-
    Base = 2...3,
    Exp = 2,
    interval(Base ^ Exp, L...U),
    L is 4,
    U is 9.

test(power_pos_base_odd_expon) :-
    Base = 2...3,
    Exp = 3,
    interval(Base ^ Exp, L...U),
    L is 8,
    U is 27.

test(power_neg_base_even_expon) :-
    Base = -3... -2,
    Exp = 2,
    interval(Base ^ Exp, L...U),
    L is 4,
    U is 9.

test(power_neg_base_odd_expon) :-
    Base = -3... -2,
    Exp = 3,
    interval(Base ^ Exp, L...U),
    L is -27,
    U is -8.

test(power_mixed_base_even_expon) :-
    Base = -3...2,
    Exp = 2,
    interval(Base ^ Exp, L...U),
    L is 0,
    U is 9.

test(power_mixed_base_odd_expon) :-
    Base = -3...2,
    Exp = 3,
    interval(Base ^ Exp, L...U),
    L is -27,
    U is 8.

test(power_positive_base_neg_expon) :-
    Base = 2...4,
    Exp = -2,
    interval(Base ^ Exp, L...U),
    L is 0.0625,
    U is 0.25.

:- end_tests(power).

:- begin_tests(abs).

test(abs1) :-
    A = 1...2,
    interval(abs(A), L...U),
    L > 0.99999,
    L < 1.00001,
    U > 1.99999,
    U < 2.00001.

test(abs2) :-
    A = -1...2,
    interval(abs(A), L...U),
    L = 0.0,
    U > 1.99999,
    U < 2.00001.

test(abs3) :-
    A = -2...1,
    interval(abs(A), L...U),
    L = 0.0,
    U > 1.99999,
    U < 2.00001.

test(abs4) :-
    A = -2... -1,
    interval(abs(A), L...U),
    L > 0.99999,
    L < 1.00001,
    U > 1.99999,
    U < 2.00001.

test(abs5) :-
    A = -1,
    interval(abs(A), Res),
    Res = 1.

test(abs6) :-
    A = 1,
    interval(abs(A), Res),
    Res is 1.

:- end_tests(abs).

:- begin_tests(round).

test(round1) :-
    A = 2.71828...3.14159,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 2.71,
    U = 3.15.

test(round2) :-
    A = 0.333...0.334,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 0.33,
    U = 0.34.

test(round3) :-
    A = 0.333...0.333,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 0.33,
    U = 0.34.

test(round4) :-
    A = 0.333,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 0.33,
    U = 0.34.

test(round5) :-
    A = 42,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 41.99,
    U = 42.01.

test(round6) :-
    A = 42.1,
    Dig = 2,
    interval(round(A, Dig), L...U),
    L = 42.09,
    U = 42.11.

:- end_tests(round).

:- begin_tests(sin).

test(sin_2max) :-
    L = 2,
    U = 9,
    interval(sin(L...U), Res),
    Res = -1...1.

test(sin_max) :-
    L = 1,
    U = 3,
    interval(sin(L...U), Res),
    equal(Res, 0.1411...1.0001).

test(sin_min) :-
    L = 4,
    U = 5,
    interval(sin(L...U), Res),
    equal(Res, -1.0001... -0.7568).

test(sin_rising) :-
    L = 1,
    U = 1.2,
    interval(sin(L...U), Res),
    equal(Res, 0.8414...0.9321).

test(sin_falling) :-
    L = 2,
    U = 2.2,
    interval(sin(L...U), Res),
    equal(Res, 0.8084...0.9093).

:- end_tests(sin).

:- begin_tests(list).

test(list1) :-
    interval([], Res),
    Res = [].

test(list2) :-
    interval([1, 2+3, "age"], Res),
    Res = [1, 5, "age"].

:- end_tests(list).

:- begin_tests(no_evaluation).

test(number1) :-
    interval(5, Res),
    Res = 5.

test(number2) :-
    interval(5, L...U),
    L...U = 5...5.

test(interval) :-
    interval(1...2, Res),
    Res = 1...2.

test(logical1) :-
    interval(true, Res),
    Res = true.

test(logical2) :-
    interval(false, Res),
    Res = false.

test(atomic) :-
    interval(a, Res),
    Res = a.

test(string) :-
    interval("abc", Res),
    Res = "abc".

:- end_tests(no_evaluation).

:- begin_tests(addition).

test(addition1) :-
    interval(1.5 + 2, Res),
    Res = 3.5.

test(addition2) :-
    interval(1...2 + 1, Res),
    Res = 2...3.

test(addition3) :-
    interval(1 + 1...2, Res),
    Res = 2...3.    

test(addition4) :-
    interval(1...2 + 1...2, Res),
    Res = 2...4.

:- end_tests(addition).

:- begin_tests(subtraction).

test(subtraction1) :-
    interval(3 - 1.5, Res),
    Res = 1.5.

test(subtraction2) :-
    interval(1...2 - 1, Res),
    Res = 0...1.

test(subtraction3) :-
    interval(1 - 1...2, Res),
    Res = -1...0.    

test(addition4) :-
    interval(3...4 - 1...2, Res),
    Res = 1...3.

:- end_tests(subtraction).

:- begin_tests(multiplication).

test(multiplication1) :-
    interval(3 * 1.5, Res),
    Res = 4.5.

test(multiplication2) :-
    interval(1...2 * 3, Res),
    Res = 3...6.

test(multiplication3) :-
    interval(2 * 1...2, Res),
    Res = 2...4.    

test(multiplication4) :-
    interval(3...4 * 1...2, Res),
    Res = 3...8.

:- end_tests(multiplication).

:- begin_tests(exponential).

test(exponential1) :-
    interval(exp(2), Res),
    equal(Res, 7.3890...7.3891).

test(exponential2) :-
    interval(exp(1...2), Res),
    equal(Res, 2.7182...7.3891).

:- end_tests(exponential).

:- begin_tests(unary_plus).

test(unary_plus1) :-
    interval(+(-2), Res),
    Res is -2.

test(unary_plus2) :-
    interval(+(-1...3), Res),
    Res = -1...3.

:- end_tests(unary_plus).

:- begin_tests(unary_minus).

test(unary_minus1) :-
    interval(-(-2), Res),
    Res is 2.

test(unary_minus2) :-
    interval(-(-1...3), Res),
    Res = -3...1.

:- end_tests(unary_minus).

:- begin_tests(max).

test(max1) :-
    interval(max(1, 2), Res),
    Res is 2.

test(max2) :-
    interval(max(1...3, 1...4), Res),
    Res = 1...4.

:- end_tests(max).

:- begin_tests(min).

test(min1) :-
    interval(min(1, 2), Res),
    Res is 1.

test(min2) :-
    interval(min(1...3, 1...4), Res),
    Res = 1...3.

:- end_tests(min).

:- begin_tests(errors).

test(test1, [throws(error(existence_error(function, log/1), _))]) :-
    interval(log(10), _Res).

:- end_tests(errors).

% Helper predicate to check equality
equal(Res0, Res) :-
    interval(round(Res0, 4), Res1),
    Res = Res1.

