:- module(lists, [
		member/2, memberchk/2,
		select/3, selectchk/3,
		append/2, append/3,
		subtract/3, union/3, intersection/3, is_set/1,
		nth1/3, nth0/3, nth1/4, nth0/4,
		last/2, same_length/2, transpose/2,
		sum_list/2, max_list/2, min_list/2,	% SWI
		list_sum/2, list_max/2, list_min/2,	% Modern
		list_to_set/2, length/2, reverse/2,
		exclude/3, include/3, permutation/2,
		foldl/4, foldl/5, foldl/6,
		flatten/2, list_to_conjunction/2,
		maplist/2, maplist/3, maplist/4, maplist/5, maplist/6, maplist/7, maplist/8,
		tasklist/2, tasklist/3, tasklist/4, tasklist/5, tasklist/6, tasklist/7, tasklist/8
	]).

list_to_conjunction(List0, T) :-
	reverse(List0, List),
	toconjunction_(List, true, T).

toconjunction_([], In, In).
toconjunction_([H|T], true, Out) :- !,
	Out2 = H,
	toconjunction_(T, Out2, Out).
toconjunction_([H|T], In, Out) :-
	Out2 = (H, In),
	toconjunction_(T, Out2, Out).

conjunction_to_list(T, List) :-
	tolist_(T, List).

tolist_((T1,T2), [T1|Rest]) :- !,
	tolist_(T2, Rest).
tolist_(T, [T|[]]).

:- help(list_to_conjunction(?list,?list), [iso(false), desc('Does as it says.')]).

flatten(List, FlatList) :-
	flatten_(List, [], FlatList0),
	!,
	FlatList = FlatList0.

flatten_(Var, Tl, [Var|Tl]) :-
	var(Var),
	!.
flatten_([], Tl, Tl) :- !.
flatten_([Hd|Tl], Tail, List) :-
	!,
	flatten_(Hd, FlatHeadTail, List),
	flatten_(Tl, Tail, FlatHeadTail).
flatten_(NonList, Tl, [NonList|Tl]).

:- help(flatten(?list,?list), [iso(false), desc('Does as it says.')]).

reverse(Xs, Ys) :-
	(	nonvar(Xs) -> reverse_(Xs, Ys, [], Xs)
	;	reverse_(Ys, Xs, [], Ys)
	).

reverse_([], [], YsRev, YsRev).
reverse_([_|Xs], [Y1|Ys], YsPreludeRev, Xss) :-
	reverse_(Xs, Ys, [Y1|YsPreludeRev], Xss).

:- help(reverse(?list,?list), [iso(false), desc('Reverse one list to make another.')]).

append([], []).
append([L0|Ls0], Ls) :-
	append(L0, Rest, Ls),
	append(Ls0, Rest).

:- help(append(?list,?list), [iso(false), desc('The concatention of a list of lists to make a new one.')]).

append([], R, R).
append([X|L], R, [X|S]) :- append(L, R, S).

:- help(append(?term,?term,?term), [iso(false), desc('The concatenation of two lists to make a third.')]).

memberchk(E, List) :-
	'$memberchk'(E, List, Tail),
	(   nonvar(Tail) ->  true
	;   Tail = [_|_], memberchk(E, Tail)
	).

:- help(memberchk(?term,?term), [iso(false), desc('Is element a member of the list.')]).

member(El, [H|T]) :-
	member_(T, El, H).

member_(_, El, El).
member_([H|T], El, _) :-
	member_(T, El, H).

:- help(member(?term,?term), [iso(false), desc('Is element a member of the list.')]).

selectchk(X, L, Rest) :- select(X, L, Rest), !.

:- help(selectchk(+term,?term,?term), [iso(false), desc('Deterministically remove element from list to make a new one.')]).

select(X, [Head|Tail], Rest) :-
	select3_(Tail, Head, X, Rest).

select3_(Tail, Head, Head, Tail).
select3_([Head2|Tail], Head, X, [Head|Rest]) :-
	select3_(Tail, Head2, X, Rest).

:- help(select(+term,+term, ?term), [iso(false), desc('Remove element from a list to make a new one.')]).

subtract([], _, []) :- !.
subtract([H|T], L2, L3) :- memberchk(H, L2), !, subtract(T, L2, L3).
subtract([H|T1], L2, [H|T3]) :- subtract(T1, L2, T3).

:- help(subtract(+list,+list,-list), [iso(false), desc('Delete all elements from set to make a new one.')]).

union([], L, L).
union([H|T], Y, Z):- member(H, Y), !, union(T, Y, Z).
union([H|T], Y, [H|Z]):- union(T, Y, Z).

:- help(union(+list,+list,-list), [iso(false), desc('The union of two sets to produce a third.')]).

intersection([], _, []).
intersection([H|T], Y, [H|Z]) :- member(H, Y), !, intersection(T, Y, Z).
intersection([_|T], Y, Z) :- intersection(T, Y, Z).

:- help(intersection(+list,+list,-list), [iso(false), desc('The intersection of two sets to produce a third.')]).

%% nth0(?N, ?Ls, ?E).
%
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from zero.
%
% ```
% ?- nth0(2, [1,2,3,4], 3).
%    true.
% ```
nth0(N, Es0, E) :-
   nonvar(N),
   '$skip_max_list'(Skip, N, Es0,Es1),
   !,
   (  Skip == N
   -> Es1 = [E|_]
   ;  ( var(Es1) ; Es1 = [_|_] ) % a partial or infinite list
   -> R is N-Skip,
      skipn(R,Es1,Es2),
      Es2 = [E|_]
   ).
nth0(N, Es0, E) :-
   can_be(not_less_than_zero, N),
   Es0 = [E0|Es1],
   nth0_el(0,N, E0,E, Es1).

skipn(N0, Es0,Es) :-
   N0>0,
   N1 is N0-1,
   Es0 = [_|Es1],
   skipn(N1, Es1,Es).
skipn(0, Es,Es).

nth0_el(N0,N, E0,E, Es0) :-
   Es0 == [],
   !, % indexing
   N0 = N,
   E0 = E.
nth0_el(N,N, E,E, _).
nth0_el(N0,N, _,E, [E0|Es0]) :-
   N1 is N0+1,
   nth0_el(N1,N, E0,E, Es0).

%% nth1(?N, ?Ls, ?E).
%
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from one.
%
% ```
% ?- nth1(2, [1,2,3,4], 2).
%    true.
% ```
nth1(N, Es0, E) :-
   N \== 0,
   nth0(N, [_|Es0], E),
   N \== 0.

skipn(N0, Es0,Es, Xs0,Xs) :-
   N0>0,
   N1 is N0-1,
   Es0 = [E|Es1],
   Xs0 = [E|Xs1],
   skipn(N1, Es1,Es, Xs1,Xs).
skipn(0, Es,Es, Xs,Xs).

%% nth0(?N, ?Ls, ?E, ?Rs).
%
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from zero.
%
% ```
% ?- nth0(2, [1,2,3,4], 3, [1,2,4]).
%    true.
% ```
nth0(N, Es0, E, Es) :-
   integer(N),
   N >= 0,
   !,
   skipn(N, Es0,Es1, Es,Es2),
   Es1 = [E|Es2].
nth0(N, Es0, E, Es) :-
   can_be(not_less_than_zero, N),
   Es0 = [E0|Es1],
   nth0_elx(0,N, E0,E, Es1, Es).

nth0_elx(N0,N, E0,E, Es0, Es) :-
   Es0 == [],
   !,
   N0 = N,
   E0 = E,
   Es0 = Es.
nth0_elx(N,N, E,E, Es, Es).
nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
   N1 is N0+1,
   nth0_elx(N1,N, E1,E, Es0, Es).

% p.p.8.5

%% nth1(?N, ?Ls, ?E, ?Rs).
%
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from one.
%
% ```
% ?- nth1(2, [1,2,3,4], 2, [1,3,4]).
%    true.
% ```
nth1(N, Es0, E, Es) :-
   N \== 0,
   nth0(N, [_|Es0], E, [_|Es]),
   N \== 0.



:- help(nth0(?integer,?term,?term), [iso(false), desc('Indexed element (from 0) into list.')]).
:- help(nth0(?integer,?term,?term,?term), [iso(false), desc('Indexed element (from 0) into list with remainder.')]).
:- help(nth1(?integer,?term,?term), [iso(false), desc('Indexed element (from 1) into list.')]).
:- help(nth1(?integer,+term,?term,?term), [iso(false), desc('Indexed element (from 1) into list with remainder.')]).

last([H|T], Last) :- last_(T, H, Last).

last_([], Last, Last).
last_([H|T], _, Last) :- last_(T, H, Last).

:- help(last(+list,-term), [iso(false)]).

same_length([], []).
same_length([_|As], [_|Bs]) :- same_length(As, Bs).

:- help(same_length(?list,?list), [iso(false), desc('Are two list the same length.')]).

list_sum(Xs, Sum) :-
	list_sum_(Xs, 0, Sum).

sum_list(Xs, Sum) :-
	list_sum_(Xs, 0, Sum).

:- help(sum_list(+list,?integer), [iso(false), deprecated(true), desc('Add all values of a list.')]).

list_sum_([], Sum0, Sum) :-
	Sum = Sum0.
list_sum_([H|T], Sum0, Sum) :-
	Sum1 is Sum0 + H,
	list_sum_(T, Sum1, Sum).

:- help(list_sum(+list,?integer), [iso(false), desc('Add all values of a list.')]).

list_max([H|T], Max) :-
	list_max_(T, H, Max).
list_max([], _) :- fail.

max_list([H|T], Max) :-
	list_max_(T, H, Max).
max_list([], _) :- fail.

:- help(max_list(+list,?integer), [iso(false), deprecated(true), desc('Highest value in list.')]).

list_max_([], Max0, Max) :-
	Max = Max0.
list_max_([H|T], Max0, Max) :-
	Max1 is max(H, Max0),
	list_max_(T, Max1, Max).

:- help(list_max(+list,?integer), [iso(false), desc('Highest value in list.')]).

list_min([H|T], Min) :-
	list_min_(T, H, Min).
list_min([], _) :- fail.

min_list([H|T], Min) :-
	list_min_(T, H, Min).
min_list([], _) :- fail.

:- help(min_list(+list,?integer), [iso(false), deprecated(true), desc('Lowest value in list.')]).

list_min_([], Min0, Min) :-
	Min = Min0.
list_min_([H|T], Min0, Min) :-
	Min1 is min(H, Min0),
	list_min_(T, Min1, Min).

:- help(list_min(+list,?integer), [iso(false), desc('Lowest value in list.')]).

list_to_set(Ls0, Ls) :-
		maplist(with_var, Ls0, LVs0),
		keysort(LVs0, LVs),
		same_elements(LVs),
		pick_firsts(LVs0, Ls).

pick_firsts([], []).
pick_firsts([E-V|EVs], Fs0) :-
		(   V == visited ->
			Fs0 = Fs
		;   V = visited,
			Fs0 = [E|Fs]
		),
		pick_firsts(EVs, Fs).

with_var(E, E-_).

same_elements([]).
same_elements([EV|EVs]) :-
		foldl(unify_same, EVs, EV, _).

unify_same(E-V, Prev-Var, E-V) :-
		(   Prev == E ->
			Var = V
		;   true
		).

is_set(Set) :-
	'$skip_list'(Len, Set, Tail),
	Tail == [],
	sort(Set, Sorted),
	length(Sorted, Len)
  .
:- help(is_set(+list), [iso(false), desc('Is it a set.')]).

length(Xs0, N) :-
   '$skip_max_list'(M, N, Xs0,Xs),
   !,
   (  Xs == [] -> N = M
   ;  nonvar(Xs) -> var(N), Xs = [_|_], resource_error(finite_memory,length/2)
   ;  nonvar(N) -> R is N-M, length_rundown(Xs, R)
   ;  N == Xs -> failingvarskip(Xs), resource_error(finite_memory,length/2)
   ;  length_addendum(Xs, N, M)
   ).
length(_, N) :-
   integer(N), !,
   domain_error(not_less_than_zero, N, length/2).
length(_, N) :-
   type_error(integer, N, length/2).

length_rundown(Xs, 0) :- !, Xs = [].
length_rundown(Vs, N) :-
    '$unattributed_var'(Vs), % unconstrained
    !,
    '$det_length_rundown'(Vs, N).
length_rundown([_|Xs], N) :- % force unification
    N1 is N-1,
    length(Xs, N1). % maybe some new info on Xs

failingvarskip(Xs) :-
    '$unattributed_var'(Xs), % unconstrained
    !.
failingvarskip([_|Xs0]) :- % force unification
    '$skip_max_list'(_, _, Xs0,Xs),
    (  nonvar(Xs) -> Xs = [_|_]
	 ;  failingvarskip(Xs)
    ).

length_addendum([], N, N).
length_addendum([_|Xs], N, M) :-
    M1 is M + 1,
    length_addendum(Xs, N, M1).

:- help(length(?term,?integer), [iso(false), desc('Number of elements in list.')]).

list_first_rest([L|Ls], L, Ls).

transpose_(_, Fs, Lists0, Lists) :-
        maplist(list_first_rest, Lists0, Fs, Lists).

lists_transpose_([], []).
lists_transpose_([L|Ls], Ts) :-
        maplist(same_length(L), Ls),
        foldl(transpose_, L, Ts, [L|Ls], _).

transpose(Ls, Ts) :-
        lists_transpose_(Ls, Ts).

:- help(transpose(?list,?list), [iso(false), desc('Transpose list of lists.')]).

foldl(G, L, V0, V) :-
	foldl_(L, G, V0, V).

foldl_([], _, V, V).
foldl_([H|T], G, V0, V) :-
	call(G, H, V0, V1),
	foldl_(T, G, V1, V).

foldl(G, L1, L2, V0, V) :-
	foldl_(L1, L2, G, V0, V).

foldl_([], [], _, V, V).
foldl_([H1|T1], [H2|T2], G, V0, V) :-
	call(G, H1, H2, V0, V1),
	foldl_(T1, T2, G, V1, V).

foldl(G, L1, L2, L3, V0, V) :-
	foldl_(L1, L2, L3, G, V0, V).

foldl_([], [], [], _, V, V).
foldl_([H1|T1], [H2|T2], [H3|T3], G, V0, V) :-
	call(G, H1, H2, H3, V0, V1),
	foldl_(T1, T2, T3, G, V1, V).

:- help(foldl(:callable,+list,+var,-var), [iso(false)]).
:- help(foldl(:callable,+list,+list,+var,-var), [iso(false)]).
:- help(foldl(:callable,+list,+list,+list,+var,-var), [iso(false)]).
:- meta_predicate(foldl(3, ?, ?, ?)).
:- meta_predicate(foldl(4, ?, ?, ?, ?)).
:- meta_predicate(foldl(5, ?, ?, ?, ?, ?)).
:- meta_predicate(foldl(6, ?, ?, ?, ?, ?, ?)).


include(G, L, Included) :-
	include_(L, G, Included).

include_([], _, []).
include_([X1|Xs1], P, Included) :-
	(   call(P, X1) -> Included = [X1|Included1]
	;   Included = Included1
	),
	include_(Xs1, P, Included1).

:- help(include(:callable,?list), [iso(false)]).
:- meta_predicate(include(2, ?, ?)).

exclude(G, L, Included) :-
	exclude_(L, G, Included).

exclude_([], _, []).
exclude_([X1|Xs1], P, Included) :-
	(   call(P, X1) -> Included = Included1
	;   Included = [X1|Included1]
	),
	exclude_(Xs1, P, Included1).

:- help(exclude(:callable,?list), [iso(false)]).
:- meta_predicate(exclude(2, ?, ?)).

permutation(Xs, Ys) :-
    '$skip_max_list'(Xlen, _, Xs, XTail),
    '$skip_max_list'(Ylen, _, Ys, YTail),
    (   XTail == [], YTail == []            % both proper lists
    ->  Xlen == Ylen
    ;   var(XTail), YTail == []             % partial, proper
    ->  length(Xs, Ylen)
    ;   XTail == [], var(YTail)             % proper, partial
    ->  length(Ys, Xlen)
    ;   var(XTail), var(YTail)              % partial, partial
    ->  length(Xs, Len),
        length(Ys, Len)
    ;   must_be(list, Xs),                  % either is not a list
        must_be(list, Ys)
    ),
    perm_(Xs, Ys).

perm_([], []).
perm_(List, [First|Perm]) :-
    select(First, List, Rest),
    perm_(Rest, Perm).

:- help(permutation(?list,?list), [iso(false)]).

:- use_module(library(gensym)).

maplist(G, L) :-
	maplist_(L, G).

maplist_([], _).
maplist_([E|T], G) :-
	call(G, E),
	maplist_(T, G).

maplist(G, L1, L2) :-
	maplist_(L1, L2, G).

maplist_([], [], _).
maplist_([E1|T1], [E2|T2], G) :-
	call(G, E1, E2),
	maplist_(T1, T2, G).

maplist(G, L1, L2, L3) :-
	maplist_(L1, L2, L3, G).

maplist_([], [], [], _).
maplist_([E1|T1], [E2|T2], [E3|T3], G) :-
	call(G, E1, E2, E3),
	maplist_(T1, T2, T3, G).

maplist(G, L1, L2, L3, L4) :-
	maplist_(L1, L2, L3, L4, G).

maplist_([], [], [], [], _).
maplist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], G) :-
	call(G, E1, E2, E3, E4),
	maplist_(T1, T2, T3, T4, G).

maplist(G, L1, L2, L3, L4, L5) :-
	maplist_(L1, L2, L3, L4, L5, G).

maplist_([], [], [], [], [], _).
maplist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], G) :-
	call(G, E1, E2, E3, E4, E5),
	maplist_(T1, T2, T3, T4, T5, G).

maplist(G, L1, L2, L3, L4, L5, L6) :-
	maplist_(L1, L2, L3, L4, L5, L6, G).

maplist_([], [], [], [], [], [], _).
maplist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], [E6|T6], G) :-
	call(G, E1, E2, E3, E4, E5, E6),
	maplist_(T1, T2, T3, T4, T5, T6, G).

maplist(G, L1, L2, L3, L4, L5, L6, L7) :-
	maplist_(L1, L2, L3, L4, L5, L6, L7, G).

maplist_([], [], [], [], [], [], [], _).
maplist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], [E6|T6], [E7|T7], G) :-
	call(G, E1, E2, E3, E4, E5, E6, E7),
	maplist_(T1, T2, T3, T4, T5, T6, T7, G).

:- meta_predicate(maplist(1, ?)).
:- meta_predicate(maplist(2, ?, ?)).
:- meta_predicate(maplist(3, ?, ?, ?)).
:- meta_predicate(maplist(4, ?, ?, ?, ?)).
:- meta_predicate(maplist(5, ?, ?, ?, ?, ?)).
:- meta_predicate(maplist(6, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(maplist(7, ?, ?, ?, ?, ?, ?, ?)).

:- help(maplist(:callable,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list,+list,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list,+list,+list,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list,+list,+list,+list,+list), [iso(false)]).
:- help(maplist(:callable,+list,+list,+list,+list,+list,+list,+list), [iso(false)]).

tasklist(G, L) :-
	tasklist_(L, G).

tasklist_([], _) :- wait.
tasklist_([E|T], G) :-
	call_task(G, E),
	tasklist_(T, G).

tasklist(G, L1, L2) :-
	tasklist_(L1, L2, G).

tasklist_([], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], G) :-
	call_task(G, E1, E2),
	tasklist_(T1, T2, G).

tasklist(G, L1, L2, L3) :-
	tasklist_(L1, L2, L3, G).

tasklist_([], [], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], [E3|T3], G) :-
	call_task(G, E1, E2, E3),
	tasklist_(T1, T2, T3, G).

tasklist(G, L1, L2, L3, L4) :-
	tasklist_(L1, L2, L3, L4, G).

tasklist_([], [], [], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], G) :-
	call_task(G, E1, E2, E3, E4),
	tasklist_(T1, T2, T3, T4, G).

tasklist(G, L1, L2, L3, L4, L5) :-
	tasklist_(L1, L2, L3, L4, L5, G).

tasklist_([], [], [], [], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], G) :-
	call_task(G, E1, E2, E3, E4, E5),
	tasklist_(T1, T2, T3, T4, T5, G).

tasklist(G, L1, L2, L3, L4, L5, L6) :-
	tasklist_(L1, L2, L3, L4, L5, L6, G).

tasklist_([], [], [], [], [], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], [E6|T6], G) :-
	call_task(G, E1, E2, E3, E4, E5, E6),
	tasklist_(T1, T2, T3, T4, T5, T6, G).

tasklist(G, L1, L2, L3, L4, L5, L6, L7) :-
	tasklist_(L1, L2, L3, L4, L5, L6, L7, G).

tasklist_([], [], [], [], [], [], [], _) :- wait.
tasklist_([E1|T1], [E2|T2], [E3|T3], [E4|T4], [E5|T5], [E6|T6], [E7|T7], G) :-
	call_task(G, E1, E2, E3, E4, E5, E6, E7),
	tasklist_(T1, T2, T3, T4, T5, T6, T7, G).

:- meta_predicate(tasklist(1, ?)).
:- meta_predicate(tasklist(2, ?, ?)).
:- meta_predicate(tasklist(3, ?, ?, ?)).
:- meta_predicate(tasklist(4, ?, ?, ?, ?)).
:- meta_predicate(tasklist(5, ?, ?, ?, ?, ?)).
:- meta_predicate(tasklist(6, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(tasklist(7, ?, ?, ?, ?, ?, ?, ?)).

:- help(tasklist(:callable,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list,+list,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list,+list,+list,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list,+list,+list,+list,+list), [iso(false)]).
:- help(tasklist(:callable,+list,+list,+list,+list,+list,+list,+list), [iso(false)]).
