%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%

:- module missionaries_impl.

:- interface.

:- import_module list.

:- type nat
    --->    zero
    ;       s(nat).

:- type state
    --->    state(nat, nat, side).

:- type side
    --->    west
    ;       east.

:- pred missionaries_query(nat::in, nat::in, list(state)::out) is nondet.

:- implementation.

missionaries_query(X, Y, Res) :-
    search(X, Y, west, [state(X, Y, west)], Res).

:- pred go(list(state)::out) is nondet.

go(Res) :-
    search(s(s(s(zero))), s(s(s(zero))), west,
        [state(s(s(s(zero))), s(s(s(zero))), west)], Res).

:- pred search(nat::in, nat::in, side::in,
    list(state)::in, list(state)::out) is nondet.

search(zero, zero, _Boat, Result, Result).
search(WM, WC, east, History, Result) :-
    move_boat_west(WM, WC, NWM, NWC),
    safe(NWM, NWC),
    \+(loop(NWM, NWC, west, History)),
    search(NWM, NWC, west, [state(NWM, NWC, west) | History], Result).
search(WM, WC, west, History, Result) :-
    move_boat_east(WM, WC, NWM, NWC),
    safe(NWM, NWC),
    \+(loop(NWM, NWC, east, History)),
    search(NWM, NWC, east, [state(NWM, NWC, east) | History], Result).

:- pred safe(nat::in, nat::in) is semidet.

safe(s(s(s(zero))), WestCann) :-
    \+(gt(WestCann, s(s(s(zero))) )).
safe(zero, WestCann) :-
    \+(gt(WestCann, s(s(s(zero))) )).
safe(W, W) :-
    \+(ge(zero, W)), \+(ge(W, s(s(s(zero))) )).

:- pred loop(nat::in, nat::in, side::in, list(state)::in) is semidet.

loop(WestMiss, WestCann, Boat, History) :-
    mymember(state(WestMiss, WestCann, Boat), History).

:- pred move_boat_east(nat::in, nat::in, nat::out, nat::out) is nondet.

move_boat_east(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus2(NewWestMiss, WestMiss), NewWestCann = WestCann.
move_boat_east(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus1(NewWestMiss, WestMiss), NewWestCann = WestCann.
move_boat_east(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus1(NewWestMiss, WestMiss),
    plus1(NewWestCann, WestCann).
move_boat_east(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    NewWestMiss = WestMiss, plus1(NewWestCann, WestCann).
move_boat_east(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    NewWestMiss = WestMiss, plus2(NewWestCann, WestCann).

:- pred move_boat_west(nat::in, nat::in, nat::out, nat::out) is multidet.

move_boat_west(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus2(WestMiss, NewWestMiss), NewWestCann = WestCann.
move_boat_west(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus1(WestMiss, NewWestMiss), NewWestCann = WestCann.
move_boat_west(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    plus1(WestMiss, NewWestMiss), plus1(WestCann, NewWestCann).
move_boat_west(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    NewWestMiss = WestMiss, plus1(WestCann, NewWestCann).
move_boat_west(WestMiss, WestCann, NewWestMiss, NewWestCann) :-
    NewWestMiss = WestMiss, plus2(WestCann, NewWestCann).

:- pred mymember(T, list(T)).
:- mode mymember(in, in) is semidet.
:- mode mymember(out, in) is nondet.

mymember(X, [X | _]).
mymember(X, [_ | T]) :-
    mymember(X, T).

:- pred gt(nat::in, nat::in) is semidet.

gt(s(_), zero).
gt(s(X), s(Y)) :-
    gt(X, Y).

:- pred ge(nat::in, nat::in) is semidet.

ge(zero, zero).
ge(s(_), zero).
ge(s(X), s(Y)) :-
    ge(X, Y).

:- pred plus1(nat, nat).
:- mode plus1(in, out) is det.
:- mode plus1(out, in) is semidet.

plus1(X, s(X)).

:- pred plus2(nat, nat).
:- mode plus2(in, out) is det.
:- mode plus2(out, in) is semidet.

plus2(X, s(s(X))).
