%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  This file is part of Logtalk <https://logtalk.org/>
%  SPDX-FileCopyrightText: 1998-2026 Paulo Moura <pmoura@logtalk.org>
%  SPDX-License-Identifier: Apache-2.0
%
%  Licensed under the Apache License, Version 2.0 (the "License");
%  you may not use this file except in compliance with the License.
%  You may obtain a copy of the License at
%
%      http://www.apache.org/licenses/LICENSE-2.0
%
%  Unless required by applicable law or agreed to in writing, software
%  distributed under the License is distributed on an "AS IS" BASIS,
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%  See the License for the specific language governing permissions and
%  limitations under the License.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


:- object(tests,
	extends(lgtunit)).

	:- info([
		version is 2:2:0,
		author is 'Paulo Moura',
		date is 2026-02-21,
		comment is 'Unit tests for the "optionals" library.'
	]).

	cover(optional).
	cover(optional(_)).
	cover(maybe).

	% type and arbitrary support

	test(optional_type_1_01, true) :-
		type::type(optional).

	test(maybe_type_1_01, true) :-
		type::type(maybe(_)).

	test(maybe_arbitrary_1_01, true) :-
		type::arbitrary(maybe(_)).

	% from_goal/3 tests

	test(optional_from_goal_3_01, true) :-
		optional::from_goal(Y is 1+2, Y, Optional),
		optional(Optional)::is_present.

	test(optional_from_goal_3_02, true(Term == 3)) :-
		optional::from_goal(Y is 1+2, Y, Optional),
		optional(Optional)::get(Term).

	test(optional_from_goal_3_03, true) :-
		optional::from_goal(Y is _, Y, Optional),
		optional(Optional)::is_empty.

	test(optional_from_goal_3_04, true) :-
		optional::from_goal(2 is 3, _, Optional),
		optional(Optional)::is_empty.

	% from_goal/2 tests

	test(optional_from_goal_2_01, true) :-
		optional::from_goal([Y]>>(Y is 1+2), Optional),
		optional(Optional)::is_present.

	test(optional_from_goal_2_02, true(Term == 3)) :-
		optional::from_goal([Y]>>(Y is 1+2), Optional),
		optional(Optional)::get(Term).

	test(optional_from_goal_2_03, true) :-
		optional::from_goal(is(_), Optional),
		optional(Optional)::is_empty.

	test(optional_from_goal_2_04, true) :-
		optional::from_goal(nonvar, Optional),
		optional(Optional)::is_empty.

	% from_generator/3 tests

	test(optional_from_generator_3_01, true) :-
		findall(Optional, optional::from_generator(a(X), X, Optional), [Optional1,Optional2,Optional3]),
		optional(Optional1)::is_present,
		optional(Optional2)::is_present,
		optional(Optional3)::is_empty.

	test(optional_from_generator_3_02, true) :-
		findall(Optional, optional::from_generator(b(X), X, Optional), [Optional1,Optional2,Optional3]),
		optional(Optional1)::get(Value1), Value1 == 1,
		optional(Optional2)::get(Value2), Value2 == 2,
		optional(Optional3)::is_empty.

	% from_generator/2 tests

	test(optional_from_generator_2_01, true) :-
		findall(Optional, optional::from_generator(a, Optional), [Optional1,Optional2,Optional3]),
		optional(Optional1)::is_present,
		optional(Optional2)::is_present,
		optional(Optional3)::is_empty.

	test(optional_from_generator_2_02, true(Value1-Value2 == 1-2)) :-
		findall(Optional, optional::from_generator(b, Optional), [Optional1,Optional2,Optional3]),
		optional(Optional1)::get(Value1),
		optional(Optional2)::get(Value2),
		optional(Optional3)::is_empty.

	% is_empty/0 tests

	test(optional_is_empty_1_01, true) :-
		optional::empty(Optional),
		optional(Optional)::is_empty.

	test(optional_is_empty_1_02, false) :-
		optional::of(0, Optional),
		optional(Optional)::is_empty.

	% is_present/0 tests

	test(optional_is_present_1_01, false) :-
		optional::empty(Optional),
		optional(Optional)::is_present.

	test(optional_is_present_1_02, true) :-
		optional::of(0, Optional),
		optional(Optional)::is_present.

	% if_empty/1 tests

	test(optional_if_empty_1_01, true(X == 1)) :-
		optional::empty(Optional),
		optional(Optional)::if_empty(X = 1).

	test(optional_if_empty_1_02, true(var(X))) :-
		optional::of(0, Optional),
		optional(Optional)::if_empty(X = 1).

	% if_present/1 tests

	test(optional_if_present_1_01, true(var(Y))) :-
		optional::empty(Optional),
		optional(Optional)::if_present({Y}/[X]>>(Y is X + 1)).

	test(optional_if_present_1_02, true(Y == 1)) :-
		optional::of(0, Optional),
		optional(Optional)::if_present({Y}/[X]>>(Y is X + 1)).

	% if_present_or_else/1 tests

	test(optional_if_present_or_else_2_01, true(X == b)) :-
		optional::empty(Optional),
		optional(Optional)::if_present_or_else('='(X), X = b).

	test(optional_if_present_or_else_2_02, true(X == a)) :-
		optional::of(a, Optional),
		optional(Optional)::if_present_or_else('='(X), X = b).

	% filter/2 tests

	test(optional_filter_2_01, true) :-
		optional::of(1, Optional),
		optional(Optional)::filter(integer, NewOptional),
		optional(NewOptional)::is_present.

	test(optional_filter_2_02, true) :-
		optional::of(a, Optional),
		optional(Optional)::filter(integer, NewOptional),
		optional(NewOptional)::is_empty.

	% map/2 tests

	test(optional_map_2_01, true) :-
		optional::empty(Optional),
		optional(Optional)::map(char_code, NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_map_2_02, true(Term == 97)) :-
		optional::of(a, Optional),
		optional(Optional)::map(char_code, NewOptional),
		optional(NewOptional)::get(Term).

	test(optional_map_2_03, true) :-
		optional::of(a, Optional),
		optional(Optional)::map(is(_), NewOptional),
		optional(NewOptional)::is_empty.

	% flat_map/2 tests

	test(optional_flat_map_2_01, true) :-
		optional::empty(Optional),
		optional(Optional)::flat_map(flat_map_closure, NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_flat_map_2_02, true(Term == 97)) :-
		optional::of(a, Optional),
		optional(Optional)::flat_map(flat_map_closure, NewOptional),
		optional(NewOptional)::get(Term).

	test(optional_flat_map_2_03, true) :-
		optional::of(a, Optional),
		optional(Optional)::flat_map(is(_), NewOptional),
		optional(NewOptional)::is_empty.

	% or/2 tests

	test(optional_or_2_01, true) :-
		optional::empty(Optional),
		optional(Optional)::or(NewOptional, optional::empty),
		optional(NewOptional)::is_empty.

	test(optional_or_2_02, true(Term == a)) :-
		optional::empty(Optional),
		optional(Optional)::or(NewOptional, optional::of(a)),
		optional(NewOptional)::get(Term).

	test(optional_or_2_03, true(NewOptional == Optional)) :-
		optional::of(a, Optional),
		optional(Optional)::or(NewOptional, optional::empty).

	% get/1 tests

	test(optional_get_1_01, error(existence_error(optional_term,_))) :-
		optional::empty(Optional),
		optional(Optional)::get(_).

	test(optional_get_1_02, true(Term == 0)) :-
		optional::of(0, Optional),
		optional(Optional)::get(Term).

	% or_else/2 tests

	test(optional_or_else_2_01, true(Term == 0)) :-
		optional::empty(Optional),
		optional(Optional)::or_else(Term, 0).

	test(optional_or_else_2_02, true(Term == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::or_else(Term, 0).

	% or_else_get/2 tests

	test(optional_or_else_get_2_01, true(atom(Term))) :-
		optional::empty(Optional),
		optional(Optional)::or_else_get(Term, current_logtalk_flag(prolog_dialect)).

	test(optional_or_else_get_2_02, true(Term == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::or_else_get(Term, current_logtalk_flag(prolog_dialect)).

	% or_else_call/2 tests

	test(optional_or_else_call_2_01, true(X == 1)) :-
		optional::empty(Optional),
		optional(Optional)::or_else_call(_, X = 1).

	test(optional_or_else_call_2_02, true(var(X))) :-
		optional::of(1, Optional),
		optional(Optional)::or_else_call(_, X = 1).

	% or_else_fail/1 tests

	test(optional_or_else_fail_1_01, false) :-
		optional::empty(Optional),
		optional(Optional)::or_else_fail(_).

	test(optional_or_else_fail_1_02, true(Term == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::or_else_fail(Term).

	% or_else_throw/2 tests

	test(optional_or_else_throw_2_01, ball(some_error)) :-
		optional::empty(Optional),
		optional(Optional)::or_else_throw(_, some_error).

	test(optional_or_else_throw_2_02, true(Term == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::or_else_throw(Term, some_error).

	% map_or_else/3 tests

	test(optional_map_or_else_3_01, true(Value == 0)) :-
		optional::empty(Optional),
		optional(Optional)::map_or_else(char_code, 0, Value).

	test(optional_map_or_else_3_02, true(Value == 97)) :-
		optional::of(a, Optional),
		optional(Optional)::map_or_else(char_code, 0, Value).

	test(optional_map_or_else_3_03, true(Value == 0)) :-
		optional::of(a, Optional),
		optional(Optional)::map_or_else(is(_), 0, Value).

	% zip/3 tests

	test(optional_zip_3_01, true) :-
		optional::empty(Optional1),
		optional::of(2, Optional2),
		optional(Optional1)::zip(zip_closure, Optional2, NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_zip_3_02, true) :-
		optional::of(1, Optional1),
		optional::empty(Optional2),
		optional(Optional1)::zip(zip_closure, Optional2, NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_zip_3_03, true) :-
		optional::empty(Optional1),
		optional::empty(Optional2),
		optional(Optional1)::zip(zip_closure, Optional2, NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_zip_3_04, true(Value == 4)) :-
		optional::of(1, Optional1),
		optional::of(3, Optional2),
		optional(Optional1)::zip(zip_closure, Optional2, NewOptional),
		optional(NewOptional)::get(Value).

	% from_goal_or_throw/3 tests

	test(optional_from_goal_or_throw_3_01, true) :-
		optional::from_goal_or_throw(Y is 1+2, Y, Optional),
		optional(Optional)::is_present.

	test(optional_from_goal_or_throw_3_02, true(Term == 3)) :-
		optional::from_goal_or_throw(Y is 1+2, Y, Optional),
		optional(Optional)::get(Term).

	test(optional_from_goal_or_throw_3_03, true) :-
		optional::from_goal_or_throw(2 is 3, _, Optional),
		optional(Optional)::is_empty.

	test(optional_from_goal_or_throw_3_04, error(instantiation_error)) :-
		optional::from_goal_or_throw(Y is _, Y, _).

	% from_goal_or_throw/2 tests

	test(optional_from_goal_or_throw_2_01, true) :-
		optional::from_goal_or_throw([Y]>>(Y is 1+2), Optional),
		optional(Optional)::is_present.

	test(optional_from_goal_or_throw_2_02, true(Term == 3)) :-
		optional::from_goal_or_throw([Y]>>(Y is 1+2), Optional),
		optional(Optional)::get(Term).

	test(optional_from_goal_or_throw_2_03, true) :-
		optional::from_goal_or_throw(nonvar, Optional),
		optional(Optional)::is_empty.

	test(optional_from_goal_or_throw_2_04, error(instantiation_error)) :-
		optional::from_goal_or_throw(is(_), _).

	% flatten/1 tests

	test(optional_flatten_1_01, true) :-
		optional::empty(Optional),
		optional(Optional)::flatten(NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_flatten_1_02, true(Value == 1)) :-
		optional::of(1, Inner),
		optional::of(Inner, Outer),
		optional(Outer)::flatten(NewOptional),
		optional(NewOptional)::get(Value).

	test(optional_flatten_1_03, true) :-
		optional::empty(Inner),
		optional::of(Inner, Outer),
		optional(Outer)::flatten(NewOptional),
		optional(NewOptional)::is_empty.

	test(optional_flatten_1_04, true(Value == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::flatten(NewOptional),
		optional(NewOptional)::get(Value).

	% to_expected/2 tests

	test(optional_to_expected_2_01, true(Value == 1)) :-
		optional::of(1, Optional),
		optional(Optional)::to_expected(missing, Expected),
		expected(Expected)::expected(Value).

	test(optional_to_expected_2_02, true(Error == missing)) :-
		optional::empty(Optional),
		optional(Optional)::to_expected(missing, Expected),
		expected(Expected)::unexpected(Error).

	% "optional" type tests

	test(optional_type_checking_support_01, true) :-
		optional::empty(Optional),
		type::check(optional, Optional).

	test(optional_type_checking_support_02, true) :-
		optional::of(1, Optional),
		type::check(optional, Optional).

	test(optional_type_checking_support_03, true) :-
		optional::from_goal(Y is 1+2, Y, Optional),
		type::check(optional, Optional).

	test(optional_type_checking_support_04, true) :-
		optional::from_goal(Y is _, Y, Optional),
		type::check(optional, Optional).

	test(optional_type_checking_support_05, ball(instantiation_error)) :-
		type::check(optional, _).

	test(optional_type_checking_support_06, ball(type_error(optional,12345))) :-
		type::check(optional, 12345).

	test(optional_type_checking_support_07, ball(type_error(optional,foobar))) :-
		type::check(optional, foobar).

	test(optional_type_checking_support_08, ball(type_error(optional,foo(bar,baz)))) :-
		type::check(optional, foo(bar,baz)).

	% cat/2 tests

	test(maybe_cat_2_01, true(Terms == [])) :-
		maybe::cat([], Terms).

	test(maybe_cat_2_02, true(Terms == [1, 2])) :-
		optional::empty(Optional1),
		optional::of(1, Optional2),
		optional::empty(Optional3),
		optional::of(2, Optional4),
		maybe::cat([Optional1, Optional2, Optional3, Optional4], Terms).

	% sequence/2 tests

	test(maybe_sequence_2_01, true(Terms == [])) :-
		maybe::sequence([], Optional),
		optional(Optional)::get(Terms).

	test(maybe_sequence_2_02, true(Terms == [1,2])) :-
		optional::of(1, Optional1),
		optional::of(2, Optional2),
		maybe::sequence([Optional1, Optional2], Optional),
		optional(Optional)::get(Terms).

	test(maybe_sequence_2_03, true) :-
		optional::of(1, Optional1),
		optional::empty(Optional2),
		optional::of(2, Optional3),
		maybe::sequence([Optional1, Optional2, Optional3], Optional),
		optional(Optional)::is_empty.

	% traverse/3 tests

	test(maybe_traverse_3_01, true(Terms == [1,2])) :-
		maybe::traverse(traverse_optional_closure, [1,2], Optional),
		optional(Optional)::get(Terms).

	test(maybe_traverse_3_02, true) :-
		maybe::traverse(traverse_optional_closure, [1,a,2], Optional),
		optional(Optional)::is_empty.

	% "maybe" type tests

	test(maybe_type_checking_support_01, true) :-
		optional::empty(Optional),
		type::check(maybe(integer), Optional).

	test(maybe_type_checking_support_02, true) :-
		optional::of(1, Optional),
		type::check(maybe(integer), Optional).

	test(maybe_type_checking_support_03, true) :-
		optional::from_goal(Y is 1+2, Y, Optional),
		type::check(maybe(integer), Optional).

	test(maybe_type_checking_support_04, true) :-
		optional::from_goal(Y is _, Y, Optional),
		type::check(maybe(integer), Optional).

	test(maybe_type_checking_support_05, ball(instantiation_error)) :-
		type::check(maybe(integer), _).

	test(maybe_type_checking_support_06, ball(type_error(optional,12345))) :-
		type::check(maybe(integer), 12345).

	test(maybe_type_checking_support_07, ball(type_error(optional,foobar))) :-
		type::check(maybe(integer), foobar).

	test(maybe_type_checking_support_08, ball(type_error(optional,foo(bar,baz)))) :-
		type::check(maybe(integer), foo(bar,baz)).

	test(maybe_type_checking_support_09, ball(type_error(integer,a))) :-
		optional::of(a, Optional),
		type::check(maybe(integer), Optional).

	% "maybe" arbitrary tests

	quick_check(
		maybe_arbitrary_support_01,
		type::arbitrary({maybe(integer)}, -maybe(integer))
	).

	% "maybe" shrinker tests

	test(maybe_shrinker_1_01, true) :-
		type::shrinker(maybe(integer)).

	test(maybe_shrink_3_01, true(type::check(maybe(integer), Small))) :-
		optional::of(42, Optional),
		type::shrink(maybe(integer), Optional, Small).

	test(maybe_shrink_3_02, all(type::check(maybe(atom), Small))) :-
		optional::of(abcde, Optional),
		type::shrink(maybe(atom), Optional, Small).

	test(maybe_shrink_3_03, false) :-
		optional::empty(Optional),
		type::shrink(maybe(integer), Optional, _).

	test(maybe_shrink_3_04, false) :-
		optional::of(0, Optional),
		type::shrink(maybe(integer), Optional, _).

	% "maybe" edge case tests

	test(maybe_edge_case_2_01, true) :-
		type::edge_case(maybe(integer), empty).

	test(maybe_edge_case_2_02, true) :-
		findall(T, type::edge_case(maybe(integer), T), Cases),
		Cases = [empty| Rest],
		Rest \== [].

	% auxiliary predicates

	flat_map_closure(Value, Optional) :-
		char_code(Value, NewValue),
		optional::of(NewValue, Optional).

	zip_closure(X, Y, Z) :-
		Z is X + Y.

	traverse_optional_closure(Term, Optional) :-
		( 	integer(Term) ->
			optional::of(Term, Optional)
		; 	optional::empty(Optional)
		).

	a(1).
	a(2).
	a(_) :-
		throw(e).

	b(1).
	b(2).

:- end_object.
