#!/usr/bin/env -S swipl
%-- mode: prolog; --

:- module(prj4_sol, [
      sublist_lengths/2,
      same_length_sublists/1,
      fibonacci_sublists/1,
      assoc_lookup/3,
      assoc_replace/3,
      add_to_plus_expr/2,
      named_to_op_expr/2,
      named_expr_eval/2,
      named_expr_to_prefix_tokens/2,
      op_expr_to_prefix_tokens/2
  ]).

%%%%%%%%%%%%%%%%%%%%%%%%%%% sublist_lengths/2 %%%%%%%%%%%%%%%%%%%%%%%%%%%

sublist_lengths([], []).
sublist_lengths([H|T], [Len|LT]) :- length(H, Len), sublist_lengths(T, LT).

:-begin_tests(sublist_lengths).
test(empty, [nondet]) :-
    sublist_lengths([], Lengths), Lengths = [].
test(sublist_lengths1, [nondet]) :-
    sublist_lengths([[a, b]], Lengths), Lengths = [2].
test(sublist_lengths3, [nondet]) :-
    sublist_lengths([[2], [a, b], [x, y, z]], Lengths), Lengths = [1, 2, 3].
test(sublist_lengths_var_list, [nondet]) :-
    sublist_lengths(List, [1, 2, 3]), length(List, 3).
:-end_tests(sublist_lengths).

%%%%%%%%%%%%%%%%%%%%%%%%% same_length_sublists/1 %%%%%%%%%%%%%%%%%%%%%%%%

all_same_length([]).
all_same_length([_]).
all_same_length([X, Y | T]) :- length(X, L), length(Y, L), all_same_length([Y | T]).

same_length_sublists(List) :- all_same_length(List).

:-begin_tests(same_length_sublists).
test(empty, [nondet]) :-
    same_length_sublists([]).
test(empties, [nondet]) :-
    same_length_sublists([[], [], []]).
test(empties_fail, [fail]) :-
    same_length_sublists([[], [2], []]).
test(sublists1, [nondet]) :-
    same_length_sublists([[[a, 2]], [[]], [c]]).
test(sublists1_fail, [fail]) :-
    same_length_sublists([[a], [[]], [c, 2]]).
test(sublists3, [nondet]) :-
    same_length_sublists([[a, [2], 4], [b, 5, [1]], [3, 2, c]]).
test(sublists3_fail, [fail]) :-
    same_length_sublists([[a, 2, 4], [b, 5, 1], [3, [2, c]]]).
:-end_tests(same_length_sublists).


%%%%%%%%%%%%%%%%%%%%%%%%%% fibonacci_sublists/1 %%%%%%%%%%%%%%%%%%%%%%%%%

fibonacci_sublists([]).
fibonacci_sublists([_]).
fibonacci_sublists([_, _]).
fibonacci_sublists([A, B, C|T]) :-
    length(A, AL), length(B, BL), length(C, CL),
    CL is AL + BL,
    fibonacci_sublists([B, C|T]).

:-begin_tests(fibonacci_sublists).
test(empty, [nondet]) :-
    fibonacci_sublists([]).
test(zero, [nondet]) :-
    fibonacci_sublists([[]]).
test(one, [nondet]) :-
    fibonacci_sublists([[], [a]]).
test(two, [nondet]) :-
    fibonacci_sublists([[], [a], [c]]).
test(three, [nondet]) :-
    fibonacci_sublists([[], [a], [c], [a, c]]).
test(three_fail, [fail]) :-
    fibonacci_sublists([[], [a], [c], [a, c, c]]).
test(four, [nondet]) :-
    fibonacci_sublists([[], [a], [c], [a, c], [1, 2, 3] ]).
test(four_fail, [fail]) :-
    fibonacci_sublists([[], [a], [c], [a, c], [1, 2, 3, 4] ]).
:-end_tests(fibonacci_sublists).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%% assoc_lookup/3 %%%%%%%%%%%%%%%%%%%%%%%%%%%%

assoc_lookup(Assoc, Key, Value) :- member((Key, Value), Assoc).

:-begin_tests(assoc_lookup).
test(empty, [fail]) :-
    assoc_lookup([], key, _Value).
test(first, [nondet]) :-
    assoc_lookup([(key, 42), (a, 22), (b, 33)], key, Value),
    42 = Value.
:-end_tests(assoc_lookup).

%%%%%%%%%%%%%%%%%%%%%%%%%%%% assoc_replace/3 %%%%%%%%%%%%%%%%%%%%%%%%%%%%

assoc_replace([], _, []).
assoc_replace([H|T], Assoc, [Z|ZT]) :-
    (atom(H) -> assoc_lookup(Assoc, H, Z); Z = H),
    assoc_replace(T, Assoc, ZT).

:-begin_tests(assoc_replace).
test(empty, [nondet]) :-
    assoc_replace([], [(a,22), (b, 33), (c, 42)], Z),
    Z = [].
:-end_tests(assoc_replace).

%%%%%%%%%%%%%%%%%%%%%%%%%%% add_to_plus_expr/2 %%%%%%%%%%%%%%%%%%%%%%%%%%

add_to_plus_expr(Expr, Expr) :- integer(Expr).
add_to_plus_expr(add(X, Y), PlusExpr) :-
    add_to_plus_expr(X, XExpr),
    add_to_plus_expr(Y, YExpr),
    PlusExpr = XExpr + YExpr.

:-begin_tests(add_to_plus_expr).
test(int, [nondet]) :-
    add_to_plus_expr(42, Z), Z = 42.
:-end_tests(add_to_plus_expr).

%%%%%%%%%%%%%%%%%%%%%%%%%%% named_to_op_expr/2 %%%%%%%%%%%%%%%%%%%%%%%%%%

named_to_op_expr(Expr, Expr) :- integer(Expr).
named_to_op_expr(add(X, Y), XExpr + YExpr) :-
    named_to_op_expr(X, XExpr),
    named_to_op_expr(Y, YExpr).
named_to_op_expr(mul(X, Y), XExpr * YExpr) :-
    named_to_op_expr(X, XExpr),
    named_to_op_expr(Y, YExpr).

:-begin_tests(named_to_op_expr).
test(int, [nondet]) :-
    named_to_op_expr(42, Z), Z = 42.
:-end_tests(named_to_op_expr).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%% named_expr_eval/2 %%%%%%%%%%%%%%%%%%%%%%%%%

named_expr_eval(Expr, Val) :- integer(Expr), Val = Expr.
named_expr_eval(add(X, Y), Val) :-
    named_expr_eval(X, XVal),
    named_expr_eval(Y, YVal),
    Val is XVal + YVal.
named_expr_eval(mul(X, Y), Val) :-
    named_expr_eval(X, XVal),
    named_expr_eval(Y, YVal),
    Val is XVal * YVal.

:-begin_tests(named_expr_eval).
test(int, [nondet]) :-
    named_expr_eval(42, 42).
:-end_tests(named_expr_eval).

%%%%%%%%%%%%%%%%%%%%% named_expr_to_prefix_tokens/2 %%%%%%%%%%%%%%%%%%%%%

named_expr_to_prefix_tokens(N, [N]) :- integer(N).
named_expr_to_prefix_tokens(add(X, Y), [add|T]) :-
    named_expr_to_prefix_tokens(X, TX), named_expr_to_prefix_tokens(Y, TY), append(TX, TY, T).
named_expr_to_prefix_tokens(mul(X, Y), [mul|T]) :-
    named_expr_to_prefix_tokens(X, TX), named_expr_to_prefix_tokens(Y, TY), append(TX, TY, T).

:-begin_tests(named_expr_to_prefix_tokens).
test(int, [nondet]) :-
    named_expr_to_prefix_tokens(42, [42]).
:-end_tests(named_expr_to_prefix_tokens).

%%%%%%%%%%%%%%%%%%%%%%% op_expr_to_prefix_expr/2 %%%%%%%%%%%%%%%%%%%%%%%

% #10: 10-points

% op_to_prefix_expr(OpExpr, PrefixTokens): Given a OpExpr involving
% integers, + and *, set PrefixTokens to a list containing its tokens
% in prefix notation.
%
% *Restriction*: must be implemented using *only* earlier procedures;
% cannot directly use recursion or Prolog built-ins.

op_expr_to_prefix_tokens(_OpExpr, _PrefixTokens) :- 'TODO'.

:-begin_tests(op_expr_to_prefix_tokens).
test(int, [nondet]) :-
    op_expr_to_prefix_tokens(42, [42]).

test(add_2_3, [nondet]) :-
    op_expr_to_prefix_tokens(+(2, 3), [+, 2, 3]).
test(add_add_2_3_add_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(+(+(2, 3), +(4, 5)),
			 [+, +, 2, 3, +, 4, 5]).
test(add_add_add_add_1_2_3_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(+(+(+(+(1, 2), 3), 4), 5),
			 [+, +, +, +, 1, 2, 3, 4, 5]).
test(add_add_add_add_1_2_3_4_5_fail, [fail]) :-
    op_expr_to_prefix_tokens(+(+(+(+(1, 2), 3), 4), 5), 
			 [+, +, +, 1, 2, 3, 4, 5]).
test(add_1_add_2_add_3_add_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(+(1, +(2, +(3, +(4, 5)))),
			 [+, 1, +, 2, +, 3, +, 4, 5]).

test(mul_2_3, [nondet]) :-
    op_expr_to_prefix_tokens(*(2, 3), [*, 2, 3]).
test(mul_mul_2_3_mul_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(*(*(2, 3), *(4, 5)),
			 [*, *, 2, 3, *, 4, 5]).
test(mul_mul_mul_mul_1_2_3_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(*(*(*(*(1, 2), 3), 4), 5),
			 [*, *, *, *, 1, 2, 3, 4, 5]).
test(mul_mul_mul_mul_1_2_3_4_5_fail, [fail]) :-
    op_expr_to_prefix_tokens(*(*(*(*(1, 2), 3), 4), 5), 
			 [*, *, *, 1, 2, 3, 4, 5]).
test(mul_1_mul_2_mul_3_mul_4_5, [nondet]) :-
    op_expr_to_prefix_tokens(*(1, *(2, *(3, *(4, 5)))),
			 [*, 1, *, 2, *, 3, *, 4, 5]).

test(mul_add_1_2_3, [nondet]) :-
    op_expr_to_prefix_tokens(*(+(1, 2), 3), [*, +, 1, 2, 3]).
test(add_1_mul_2_3, [nondet]) :-
    op_expr_to_prefix_tokens(+(1, *(2, 3)), [+, 1, *, 2, 3]).
test(add_mul_1_2_add_3_4, [nondet]) :-
    op_expr_to_prefix_tokens(+(*(1, 2), *(3, 4)),
			[+, *, 1, 2, *, 3, 4]).
test(mul_add_1_2_mul_3_4, [nondet]) :-
    op_expr_to_prefix_tokens(*(+(1, 2), +(3, 4)),
			[*, +, 1, 2, +, 3, 4]).
:-end_tests(op_expr_to_prefix_tokens).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% main/0 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

main :-
    current_prolog_flag(argv, Argv),
    (length(Argv, 0) -> run_tests ; run_tests(Argv)).

:-initialization(main, main).


 Understand the logic for the previous implemented procedures and then apply the logic for task 10 following all the restrictions.