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

:- module mutrec_higher_order.

:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

%---------------------------------------------------------------------------%

:- import_module int.
:- import_module list.

:- type step
    --->    self
    ;       mut(int)
    ;       rep(int)
    ;       down.

main(!IO) :-
    Steps1 = [self, mut(1), mut(3), self, mut(2), mut(2), self, rep(2),
        mut(1), mut(3), mut(3), mut(3), mut(3), self, self, rep(1),
        self, mut(2), mut(2), self, mut(3), self, down],
    Steps2 = [self, mut(1), mut(3), self, mut(2), mut(2), self, rep(3),
        mut(1), mut(3), mut(3), mut(3), mut(3), self, self, rep(4),
        self, self, self, self, mut(3), self, down],

    test(Steps1, RevStrs1),
    test(Steps2, RevStrs2),

    test_output(RevStrs1, !IO),
    test_output(RevStrs2, !IO).

:- pred test(list(step)::in, list(string)::out) is det.

test(Steps, RevStrs) :-
    p1(Steps, [], RevStrs).

:- pred test_output(list(string)::in, io::di, io::uo) is det.

test_output(RevStrs, !IO) :-
    list.reverse(RevStrs, Strs),
    (
        Strs = [],
        io.write_string("[]\n", !IO)
    ;
        Strs = [HeadStr | TailStrs],
        io.write_string("[", !IO),
        io.write_string(HeadStr, !IO),
        write_comma_strings(TailStrs, !IO),
        io.write_string("]\n", !IO)
    ).

:- pred write_comma_strings(list(string)::in, io::di, io::uo) is det.

write_comma_strings([], !IO).
write_comma_strings([Str | Strs], !IO) :-
    io.write_string(", ", !IO),
    io.write_string(Str, !IO),
    write_comma_strings(Strs, !IO).

%---------------------------------------------------------------------------%

:- pred repeat_steps(
    pred(list(step), list(string), list(string))::in(pred(in, in, out) is det),
    list(step)::in, int::in, list(string)::in, list(string)::out) is det.

repeat_steps(P, Steps, N, A, R) :-
    ( if N =< 0 then
        R = A
    else
        P(Steps, A, B),
        repeat_steps(P, Steps, N-1, B, R)
    ).

%---------------------------------------------------------------------------%

:- pred p1(list(step)::in, list(string)::in, list(string)::out) is det.

p1([], A, A).
p1([Step | Steps], A, R) :-
    B = ["p1" | A],
    (
        Step = self,
        p1(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 2 then
            p2(Steps, B, R)
        else
            p3(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(q1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(q2, Steps, 2, A, R)
        else
            repeat_steps(q3, Steps, 3, A, R)
        )
    ;
        Step = down,
        q1(Steps, B, R)
    ).

:- pred p2(list(step)::in, list(string)::in, list(string)::out) is det.

p2([], A, A).
p2([Step | Steps], A, R) :-
    B = ["p2" | A],
    (
        Step = self,
        p2(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            p1(Steps, B, R)
        else
            p3(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(q1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(q2, Steps, 2, A, R)
        else
            repeat_steps(q3, Steps, 3, A, R)
        )
    ;
        Step = down,
        q1(Steps, B, R)
    ).

:- pred p3(list(step)::in, list(string)::in, list(string)::out) is det.

p3([], A, A).
p3([Step | Steps], A, R) :-
    B = ["p3" | A],
    (
        Step = self,
        p3(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            p1(Steps, B, R)
        else
            p2(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(q1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(q2, Steps, 2, A, R)
        else
            repeat_steps(q3, Steps, 3, A, R)
        )
    ;
        Step = down,
        q1(Steps, B, R)
    ).

%---------------------------------------------------------------------------%

:- pred q1(list(step)::in, list(string)::in, list(string)::out) is det.

q1([], A, A).
q1([Step | Steps], A, R) :-
    B = ["q1" | A],
    (
        Step = self,
        q1(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 2 then
            q2(Steps, B, R)
        else
            q3(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(r1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(r2, Steps, 2, A, R)
        else
            repeat_steps(r3, Steps, 3, A, R)
        )
    ;
        Step = down,
        r0a(Steps, B, R)
    ).

:- pred q2(list(step)::in, list(string)::in, list(string)::out) is det.

q2([], A, A).
q2([Step | Steps], A, R) :-
    B = ["q2" | A],
    (
        Step = self,
        q2(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            q1(Steps, B, R)
        else
            q3(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(r1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(r2, Steps, 2, A, R)
        else
            repeat_steps(r3, Steps, 3, A, R)
        )
    ;
        Step = down,
        r0b(Steps, B, R)
    ).

:- pred q3(list(step)::in, list(string)::in, list(string)::out) is det.

q3([], A, A).
q3([Step | Steps], A, R) :-
    B = ["q3" | A],
    (
        Step = self,
        q3(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            q1(Steps, B, R)
        else
            q2(Steps, B, R)
        )
    ;
        Step = rep(N),
        ( if N = 1 then
            repeat_steps(r1, Steps, 1, A, R)
        else if N = 2 then
            repeat_steps(r2, Steps, 2, A, R)
        else
            repeat_steps(r3, Steps, 3, A, R)
        )
    ;
        Step = down,
        r0c(Steps, B, R)
    ).

%---------------------------------------------------------------------------%

:- pred r0a(list(step)::in, list(string)::in, list(string)::out) is det.

r0a(Steps, A, R) :-
    B = ["r0a" | A],
    r0b(Steps, B, R).

:- pred r0b(list(step)::in, list(string)::in, list(string)::out) is det.

r0b(Steps, A, R) :-
    B = ["r0b" | A],
    r0c(Steps, B, R).

:- pred r0c(list(step)::in, list(string)::in, list(string)::out) is det.

r0c(Steps, A, R) :-
    B = ["r0c" | A],
    r1(Steps, B, R).

:- pred r1(list(step)::in, list(string)::in, list(string)::out) is det.

r1([], A, A).
r1([Step | Steps], A, R) :-
    B = ["r1" | A],
    (
        Step = self,
        r1(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 2 then
            r2(Steps, B, R)
        else
            r3(Steps, B, R)
        )
    ;
        Step = rep(_),
        R = B
    ;
        Step = down,
        R = B
    ).

:- pred r2(list(step)::in, list(string)::in, list(string)::out) is det.

r2([], A, A).
r2([Step | Steps], A, R) :-
    B = ["r2" | A],
    (
        Step = self,
        r2(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            r1(Steps, B, R)
        else
            r3(Steps, B, R)
        )
    ;
        Step = rep(_),
        R = B
    ;
        Step = down,
        s(1, B, R)
    ).

:- pred r3(list(step)::in, list(string)::in, list(string)::out) is det.

r3([], A, A).
r3([Step | Steps], A, R) :-
    B = ["r3" | A],
    (
        Step = self,
        r3(Steps, B, R)
    ;
        Step = mut(N),
        ( if N = 1 then
            r1(Steps, B, R)
        else
            r2(Steps, B, R)
        )
    ;
        Step = rep(_),
        R = B
    ;
        Step = down,
        s(3, B, R)
    ).

%---------------------------------------------------------------------------%

:- pred s(int::in, list(string)::in, list(string)::out) is det.

s(N, A, R) :-
    B = ["s" | A],
    ( if N > 0 then
        s(N-1, B, R)
    else
        R = B
    ).

%---------------------------------------------------------------------------%
