:- module(_, [crypt/2], [assertions, regtypes, nativeprops]).

% crypt
%
% Cryptomultiplication:
% Find the unique answer to:
% OEE
% EE
% ---
% EOEE
% EOE
% ----
% OOEE
%
% where E=even, O=odd.
% This program generalizes easily
% to any such problem.
% Written by Peter Van Roy

:- entry crypt(In, Xs) : term * var.
:- pred crypt(In, Xs) : term * var => term * listnum.
crypt(_In, Xs) :- crypt_(Xs).

:- pred crypt_(Xs) : var => listnum.
crypt_([A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P]) :-
        odd(A), even(B), even(C), even(E),
        mult([C, B, A], E, [I, H, G, F | X]),
        lefteven(F), odd(G), even(H), even(I), zero(X), lefteven(D),
        mult([C, B, A], D, [L, K, J | Y]),
        lefteven(J), odd(K), even(L), zero(Y),
        sum2([I, H, G, F], [0, L, K, J], [P, O, N, M | Z]),
        odd(M), odd(N), even(O), even(P), zero(Z).
        % write(' '), write(A), write(B), write(C), nl,
        % write('  '), write(D), write(E), nl,
        % write(F), write(G), write(H), write(I), nl,
        % write(J), write(K), write(L), nl,
        % write(M), write(N), write(O), write(P), nl.

% In the usual source this predicate is named sum. However, sum is a
% language construct in NU-Prolog, and cannot be defined as a predicate.
% If you try, nc comes up with an obscure error message.

:- pred sum2(A, B, C) : listnum * listnum * term => listnum * listnum * listnum.
sum2(AL, BL, CL) :-
        sum3(AL, BL, 0, CL).

sum3([A | AL], [B | BL], Carry, [C | CL]) :- !,
        X0 is A + B,
        X is X0 + Carry,
        C is X mod 10,
        NewCarry is X // 10,
        sum3(AL, BL, NewCarry, CL).
sum3([], BL, 0, BL) :- !.
sum3(AL, [], 0, AL) :- !.
sum3([], [B | BL], Carry, [C | CL]) :- !,
        X is B + Carry,
        NewCarry is X // 10,
        C is X mod 10,
        sum3([], BL, NewCarry, CL).
sum3([A | AL], [], Carry, [C | CL]) :- !,
        X is A + Carry,
        NewCarry is X // 10,
        C is X mod 10,
        sum3([], AL, NewCarry, CL).
sum3([], [], Carry, [Carry]).

:- pred mult(A, D, B) : listnum * num * term => listnum * num * listnum.
mult(AL, D, BL) :- mult2(AL, D, 0, BL).

mult2([], _, Carry, [C, Cend]) :-
        C is Carry mod 10,
        Cend is Carry // 10.
mult2([A | AL], D, Carry, [B | BL] ) :-
        X0 is A * D,
        X is X0 + Carry,
        B is X mod 10,
        NewCarry is X // 10,
        mult2(AL, D, NewCarry, BL).

:- pred zero(X) : term => listnum.
zero([]).
zero([X | L]) :-
        X = 0,
        zero(L).

:- pred odd(X) : term => num.
odd(1).
odd(3).
odd(5).
odd(7).
odd(9).

:- pred even(X) : term => num.
even(0).
even(2).
even(4).
even(6).
even(8).

:- pred lefteven(X) : term => num.
lefteven(2).
lefteven(4).
lefteven(6).
lefteven(8).

:- regtype listnum/1.
listnum([]).
listnum([X|Xs]) :- num(X), listnum(Xs).
