%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% A regression test: the Mercury compiler dated May 20 1997
% generated incorrect code for this program.
%
% The figure_key predicate also serves as a test case for string trie
% lookup switches.
%
%---------------------------------------------------------------------------%
%
% space : an ambient music generator
% usage: space  [-h | --help] \
%       [-k | --key {a-g}[#][M]]  (default "c")
%       [-l | --length n]     (default 16)
%       [-s | --seed n]       (default 0)
%
%---------------------------------------------------------------------------%

:- module space.

:- interface.

:- import_module io.

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

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

:- implementation.

:- import_module bool.
:- import_module char.
:- import_module getopt.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module solutions.
:- import_module string.

:- type option
    --->    help
    ;       key
    ;       length
    ;       seed.

:- type note
    --->    note(rank, modifier, octave).

:- type rank
    --->    c
    ;       d
    ;       e
    ;       f
    ;       g
    ;       a
    ;       b.

:- type modifier
    --->    natural
    ;       sharp
    ;       flat.

:- type octave == int.

:- type interval
    --->    i
    ;       ii
    ;       iii
    ;       iv
    ;       v
    ;       vi
    ;       vii.

:- type qualifier
    --->    q_maj
    ;       q_min.

:- type chord
    --->    chord(interval, kind, inversion).

:- type kind
    --->    maj
    ;       min
    ;       open
    ;       seven
    ;       maj_seven
    ;       ninth
    ;       eleventh
    ;       dim.

:- type inversion
    --->    o
    ;       up(degree)
    ;       down(degree).

:- type degree
    --->    i
    ;       ii
    ;       iii.

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

main(!IO) :-
    % io.command_line_arguments(Args0),
    Args0 = ["-k", "dM", "-l", "30", "-s", "1"],

    Ops = option_ops_multi(short, long, defaults),
    getopt.process_options(Ops, Args0, _Args, MOptTable),
    (
        MOptTable = error(Error),
        ErrorMsg = option_error_to_string(Error),
        io.stderr_stream(Stderr, !IO),
        io.format(Stderr, "bad option: %s\n", [s(ErrorMsg)], !IO)
    ;
        MOptTable = ok(Opts),
        ( if
            getopt.lookup_bool_option(Opts, help, yes)
        then
            help(!IO)
        else if
            getopt.lookup_string_option(Opts, key, KeyStr),
            figure_key(KeyStr, Note, Qual, Kind)
        then
            getopt.lookup_int_option(Opts, length, Len),
            getopt.lookup_int_option(Opts, seed, Seed),
            random_init(Seed, Rnd),
            Chord0 = chord(i, Kind, up(ii)),
            doit(Len, Note, Qual, Chord0, Rnd, !IO)
        else
            getopt.lookup_string_option(Opts, key, KeyStr),
            string.format("illegal key: `%s'\n", [s(KeyStr)], Msg),
            io.stderr_stream(Stderr, !IO),
            io.write_string(Stderr, Msg, !IO)
        )
    ).

:- pred figure_key(string, note, qualifier, kind).
:- mode figure_key(in, out, out, out) is semidet.

figure_key("c",   note(c, natural, 2), q_maj, maj).
figure_key("cM",  note(c, natural, 2), q_min, min).
figure_key("c#",  note(c, sharp,   2), q_maj, maj).
figure_key("c#M", note(c, sharp,   2), q_min, min).
figure_key("d",   note(d, natural, 2), q_maj, maj).
figure_key("dM",  note(d, natural, 2), q_min, min).
figure_key("d#",  note(d, sharp,   2), q_maj, maj).
figure_key("d#M", note(d, sharp,   2), q_min, min).
figure_key("e",   note(e, natural, 2), q_maj, maj).
figure_key("eM",  note(e, natural, 2), q_min, min).
figure_key("f",   note(f, natural, 2), q_maj, maj).
figure_key("fM",  note(f, natural, 2), q_min, min).
figure_key("f#",  note(f, sharp,   2), q_maj, maj).
figure_key("f#M", note(f, sharp,   2), q_min, min).
figure_key("g",   note(g, natural, 2), q_maj, maj).
figure_key("gM",  note(g, natural, 2), q_min, min).
figure_key("g#",  note(g, sharp,   2), q_maj, maj).
figure_key("g#M", note(g, sharp,   2), q_min, min).
figure_key("a",   note(a, natural, 2), q_maj, maj).
figure_key("aM",  note(a, natural, 2), q_min, min).
figure_key("a#",  note(a, sharp,   2), q_maj, maj).
figure_key("a#M", note(a, sharp,   2), q_min, min).
figure_key("b",   note(b, natural, 2), q_maj, maj).
figure_key("bM",  note(b, natural, 2), q_min, min).

:- pred doit(int::in, note::in, qualifier::in, chord::in,
    random_supply::mdi, io::di, io::uo) is det.

doit(N, Trans, Qual, Chord0, Rnd0, !IO) :-
    ( if N =< 0 then
        true
    else
        chord_notes(Chord0, Qual, Notes0),
        list.map(trans(Trans), Notes0, Notes),
        write_chord(Notes, !IO),
        ( if
            random_random(I, Rnd0, Rnd),
            next_chord(Chord0, Qual, I, Chord1)
        then
            doit(N - 1, Trans, Qual, Chord1, Rnd, !IO)
        else
            io.write_string("next_chord failed\n", !IO)
        )
    ).

:- pred write_chord(list(note)::in, io::di, io::uo) is det.

write_chord([], !IO) :-
    io.nl(!IO).
write_chord([Note], !IO) :-
    Note = note(Rank, Mod, Oct),
    write_note(Rank, Mod, Oct, !IO),
    io.nl(!IO).
write_chord([Note | Notes], !IO) :-
    Notes = [_ | _],
    Note = note(Rank, Mod, Oct),
    write_note(Rank, Mod, Oct, !IO),
    io.write_string(" ", !IO),
    write_chord(Notes, !IO).

:- pred write_note(rank::in, modifier::in, octave::in, io::di, io::uo) is det.

write_note(c, flat, Oct, !IO) :-
    io.format("b%d", [i(Oct - 1)], !IO).
write_note(c, natural, Oct, !IO) :-
    io.format("c%d", [i(Oct)], !IO).
write_note(c, sharp, Oct, !IO) :-
    io.format("c#%d", [i(Oct)], !IO).
write_note(d, flat, Oct, !IO) :-
    io.format("c#%d", [i(Oct)], !IO).
write_note(d, natural, Oct, !IO) :-
    io.format("d%d", [i(Oct)], !IO).
write_note(d, sharp, Oct, !IO) :-
    io.format("d#%d", [i(Oct)], !IO).
write_note(e, flat, Oct, !IO) :-
    io.format("d#%d", [i(Oct)], !IO).
write_note(e, natural, Oct, !IO) :-
    io.format("e%d", [i(Oct)], !IO).
write_note(e, sharp, Oct, !IO) :-
    io.format("f%d", [i(Oct)], !IO).
write_note(f, flat, Oct, !IO) :-
    io.format("e%d", [i(Oct)], !IO).
write_note(f, natural, Oct, !IO) :-
    io.format("f%d", [i(Oct)], !IO).
write_note(f, sharp, Oct, !IO) :-
    io.format("f#%d", [i(Oct)], !IO).
write_note(g, flat, Oct, !IO) :-
    io.format("f#%d", [i(Oct)], !IO).
write_note(g, natural, Oct, !IO) :-
    io.format("g%d", [i(Oct)], !IO).
write_note(g, sharp, Oct, !IO) :-
    io.format("g#%d", [i(Oct)], !IO).
write_note(a, flat, Oct, !IO) :-
    io.format("g#%d", [i(Oct)], !IO).
write_note(a, natural, Oct, !IO) :-
    io.format("a%d", [i(Oct)], !IO).
write_note(a, sharp, Oct, !IO) :-
    io.format("a#%d", [i(Oct)], !IO).
write_note(b, flat, Oct, !IO) :-
    io.format("a#%d", [i(Oct)], !IO).
write_note(b, natural, Oct, !IO) :-
    io.format("b%d", [i(Oct)], !IO).
write_note(b, sharp, Oct, !IO) :-
    io.format("c%d", [i(Oct + 1)], !IO).

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

:- pred next_chord(chord::in, qualifier::in, int::in, chord::out) is semidet.

next_chord(Chord0, Qual, Pr, Chord) :-
    chord_notes(Chord0, Qual, Notes0),
    last(Notes0, TopNote0, _),
    Chord0 = chord(Int0, _, _),

    solutions(try_next_chord(Qual, Int0, TopNote0), List),
    list.length(List, Len),
    Len > 0,
    Ind = Pr mod Len,
    list.index0(List, Ind, Chord).

:- pred try_next_chord(qualifier::in, interval::in, note::in, chord::out)
    is nondet.

try_next_chord(Qual, Int0, TopNote0, Ch) :-
    next_interval(Int0, Qual, Int, Kind),
    next_inversion(Inv),
    Ch = chord(Int, Kind, Inv),
    chord_notes(Ch, Qual, Notes),
    last(Notes, TopNote, _),
    next_topnote(TopNote0, Qual, TopNote).

:- pred rotate(int::in, list(T)::in, list(T)::out) is det.

rotate(_, [], []).
rotate(I, [X | Xs], Zs) :-
    ( if I > 0 then
        list.append(Xs, [X], Ys),
        rotate(I - 1, Ys, Zs)
    else if I < 0 then
        list.append(Xs, [X], Ys),
        rotate(I + 1, Ys, Zs)
    else
        Zs = [X | Xs]
    ).

:- pred next_interval(interval::in, qualifier::in, interval::out, kind::out)
    is nondet.

next_interval(i,   q_maj, iv,  maj).
next_interval(i,   q_maj, v,   maj).
next_interval(i,   q_maj, vi,  min).

next_interval(ii,  q_maj, iv,  maj).
next_interval(ii,  q_maj, v,   maj).
next_interval(ii,  q_maj, i,   maj).

next_interval(iii, q_maj, i,   maj).
next_interval(iii, q_maj, v,   maj).
next_interval(iii, q_maj, iv,  maj).

next_interval(iv,  q_maj, i,   maj).
next_interval(iv,  q_maj, ii,  min).
next_interval(iv,  q_maj, v,   maj).

next_interval(v,   q_maj, i,   maj).
next_interval(v,   q_maj, iv,  maj).

next_interval(vi,  q_maj, v,   maj).
next_interval(vi,  q_maj, i,   maj).

next_interval(i,   q_min, iii, maj).
next_interval(i,   q_min, iv,  min).
next_interval(i,   q_min, v,   min).
next_interval(i,   q_min, vi,  maj).
next_interval(i,   q_min, vii, maj).

next_interval(iii, q_min, i,   min).
next_interval(iii, q_min, iv,  min).
next_interval(iii, q_min, v,   min).
next_interval(iii, q_min, vi,  maj).
next_interval(iii, q_min, vii, maj).

next_interval(iv,  q_min, i,   min).
next_interval(iv,  q_min, iii, maj).
next_interval(iv,  q_min, v,   min).
next_interval(iv,  q_min, vi,  maj).
next_interval(iv,  q_min, vii, maj).

next_interval(v,   q_min, i,   min).
next_interval(v,   q_min, iii, min).
next_interval(v,   q_min, iv,  min).
next_interval(v,   q_min, vi,  maj).
next_interval(v,   q_min, vii, maj).

next_interval(vi,  q_min, i,   min).
next_interval(vi,  q_min, iii, maj).
next_interval(vi,  q_min, iv,  min).
next_interval(vi,  q_min, v,   min).
next_interval(vi,  q_min, vii, maj).

next_interval(vii, q_min, i,   min).
next_interval(vii, q_min, iii, maj).
next_interval(vii, q_min, iv,  min).
next_interval(vii, q_min, v,   min).
next_interval(vii, q_min, vi,  maj).

:- pred next_inversion(inversion::out) is multi.

next_inversion(o).
next_inversion(up(i)).
next_inversion(up(ii)).
next_inversion(up(iii)).
next_inversion(down(i)).
next_inversion(down(ii)).
next_inversion(down(iii)).

:- pred next_topnote(note::in, qualifier::in, note::out) is nondet.

next_topnote(Note0, Qual, Note) :-
    note_to_interval(Note0, Qual, Int0, Oct0),
    adj_interval(Int0, Oct0, Int, Oct),
    note_to_interval(Note, Qual, Int, Oct).

:- pred note_to_interval(note, qualifier, interval, octave).
:- mode note_to_interval(in, in, out, out) is semidet.
:- mode note_to_interval(out, in, in, in) is multi.

note_to_interval(note(c, natural, Oct), _,     i,   Oct).
note_to_interval(note(d, natural, Oct), _,     ii,  Oct).
note_to_interval(note(d, sharp,   Oct), q_min, iii, Oct).
note_to_interval(note(e, flat,    Oct), q_min, iii, Oct).
note_to_interval(note(e, natural, Oct), q_maj, iii, Oct).
note_to_interval(note(f, natural, Oct), _,     iv,  Oct).
note_to_interval(note(g, natural, Oct), _,     v,   Oct).
note_to_interval(note(g, sharp,   Oct), q_min, vi,  Oct).
note_to_interval(note(a, flat,    Oct), q_min, vi,  Oct).
note_to_interval(note(a, natural, Oct), q_maj, vi,  Oct).
note_to_interval(note(a, sharp,   Oct), q_min, vii, Oct).
note_to_interval(note(b, flat,    Oct), q_min, vii, Oct).
note_to_interval(note(b, natural, Oct), q_maj, vii, Oct).

:- pred adj_interval(interval::in, octave::in, interval::out, octave::out)
    is multi.

adj_interval(i,   Oct, vii, Oct - 1).
adj_interval(i,   Oct, ii,  Oct).
adj_interval(ii,  Oct, i,   Oct).
adj_interval(ii,  Oct, iii, Oct).
adj_interval(iii, Oct, ii,  Oct).
adj_interval(iii, Oct, iv,  Oct).
adj_interval(iv,  Oct, iii, Oct).
adj_interval(iv,  Oct, v,   Oct).
adj_interval(v,   Oct, iv,  Oct).
adj_interval(v,   Oct, vi,  Oct).
adj_interval(vi,  Oct, v,   Oct).
adj_interval(vi,  Oct, vii, Oct).
adj_interval(vii, Oct, vi,  Oct).
adj_interval(vii, Oct, i,   Oct + 1).

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

:- pred chord_notes(chord::in, qualifier::in, list(note)::out) is det.

chord_notes(chord(Interval, Kind, Inversion), Qual, Notes) :-
    base_notes(Kind, Notes0),
    list.map(transpose(Interval, Qual), Notes0, Notes1),
    invert(Notes1, Inversion, Notes).

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

:- pred base_notes(kind::in, list(note)::out) is det.

base_notes(maj, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, natural, 0),
        note(g, natural, 0)
    ].
base_notes(min, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, flat,    0),
        note(g, natural, 0)
    ].
base_notes(open, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(g, natural, 0),
        note(c, natural, 1)
    ].
base_notes(seven, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, natural, 0),
        note(g, natural, 0),
        note(b, flat,    0)
    ].
base_notes(maj_seven, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, natural, 0),
        note(g, natural, 0),
        note(b, natural, 0)
    ].
base_notes(ninth, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(d, natural, 0),
        note(e, natural, 0),
        note(g, natural, 0)
    ].
base_notes(eleventh, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, natural, 0),
        note(g, natural, 0),
        note(a, natural, 0)
    ].
base_notes(dim, Notes) :-
    Notes = [
        note(c, natural, 0),
        note(e, flat,    0),
        note(g, flat,    0),
        note(a, natural, 0)
    ].

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

:- pred transpose(interval::in, qualifier::in, note::in, note::out) is det.

transpose(Trans, Qual, Note0, Note) :-
    interval_to_int(Trans, Qual, TNum),
    note_to_int(Note0, NNum0),
    NNum = TNum + NNum0,
    int_to_note(NNum, Note).

:- pred trans(note::in, note::in, note::out) is det.

trans(Trans, Note0, Note) :-
    note_to_int(Trans, TNum),
    note_to_int(Note0, NNum0),
    NNum = TNum + NNum0,
    int_to_note(NNum, Note).

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

:- type direction
    --->    up
    ;       down.

:- pred invert(list(note)::in, inversion::in, list(note)::out) is det.

invert(Notes, o, Notes).
invert(Notes0, up(Degree), Notes) :-
    degree_to_int(Degree, N),
    invert_list(Notes0, up, N, Notes).
invert(Notes0, down(Degree), Notes) :-
    degree_to_int(Degree, N),
    invert_list(Notes0, down, -N, Notes).

:- pred invert_list(list(note)::in, direction::in, int::in, list(note)::out)
    is det.

invert_list(Notes0, Dir, N, Notes) :-
    ( if
        N > 0,
        Notes0 = [Note0 | Notes1]
    then
        shift(Dir, Note0, Note),
        list.append(Notes1, [Note], Notes2),
        invert_list(Notes2, Dir, N - 1, Notes)
    else if
        N < 0,
        last(Notes0, Note0, Notes1)
    then
        shift(Dir, Note0, Note),
        invert_list([Note | Notes1], Dir, N + 1, Notes)
    else
        Notes = Notes0
    ).

:- pred shift(direction::in, note::in, note::out) is det.

shift(up,   note(Rank, Mod, Oct), note(Rank, Mod, Oct + 1)).
shift(down, note(Rank, Mod, Oct), note(Rank, Mod, Oct - 1)).

:- pred last(list(T)::in, T::out, list(T)::out) is semidet.

last([X], X, []).
last([X | Xs], Z, [X | Ys]) :-
    Xs = [_ | _],
    last(Xs, Z, Ys).

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

:- pred inversion_to_int(inversion::in, int::out) is det.

inversion_to_int(o, 0).
inversion_to_int(up(Deg), Int) :-
    degree_to_int(Deg, Int).
inversion_to_int(down(Deg), -Int) :-
    degree_to_int(Deg, Int).

:- pred degree_to_int(degree::in, int::out) is det.

degree_to_int(i,    1).
degree_to_int(ii,   2).
degree_to_int(iii,  3).

:- pred interval_to_int(interval::in, qualifier::in, int::out) is det.

interval_to_int(i,   _,     0).
interval_to_int(ii,  _,     2).
interval_to_int(iii, q_min, 3).
interval_to_int(iii, q_maj, 4).
interval_to_int(iv,  _,     5).
interval_to_int(v,   _,     7).
interval_to_int(vi,  q_min, 8).
interval_to_int(vi,  q_maj, 9).
interval_to_int(vii, q_min, 10).
interval_to_int(vii, q_maj, 11).

:- pred note_to_int(note::in, int::out) is det.

note_to_int(note(c, flat, Oct),     I) :-
    I = -1 + 12 * Oct.
note_to_int(note(c, natural, Oct),  I) :-
    I = 0 + 12 * Oct.
note_to_int(note(c, sharp, Oct),    I) :-
    I = 1 + 12 * Oct.
note_to_int(note(d, flat, Oct),     I) :-
    I = 1 + 12 * Oct.
note_to_int(note(d, natural, Oct),  I) :-
    I = 2 + 12 * Oct.
note_to_int(note(d, sharp, Oct),    I) :-
    I = 3 + 12 * Oct.
note_to_int(note(e, flat, Oct),     I) :-
    I = 3 + 12 * Oct.
note_to_int(note(e, natural, Oct),  I) :-
    I = 4 + 12 * Oct.
note_to_int(note(f, flat, Oct),     I) :-
    I = 4 + 12 * Oct.
note_to_int(note(e, sharp, Oct),    I) :-
    I = 5 + 12 * Oct.
note_to_int(note(f, natural, Oct),  I) :-
    I = 5 + 12 * Oct.
note_to_int(note(f, sharp, Oct),    I) :-
    I = 6 + 12 * Oct.
note_to_int(note(g, flat, Oct),     I) :-
    I = 6 + 12 * Oct.
note_to_int(note(g, natural, Oct),  I) :-
    I = 7 + 12 * Oct.
note_to_int(note(g, sharp, Oct),    I) :-
    I = 8 + 12 * Oct.
note_to_int(note(a, flat, Oct),     I) :-
    I = 8 + 12 * Oct.
note_to_int(note(a, natural, Oct),  I) :-
    I = 9 + 12 * Oct.
note_to_int(note(a, sharp, Oct),    I) :-
    I = 10 + 12 * Oct.
note_to_int(note(b, flat, Oct),     I) :-
    I = 10 + 12 * Oct.
note_to_int(note(b, natural, Oct),  I) :-
    I = 11 + 12 * Oct.
note_to_int(note(b, sharp, Oct),    I) :-
    I = 12 + 12 * Oct.

:- pred int_to_note(int::in, note::out) is det.

int_to_note(Num, note(Rank, Mod, Oct)) :-
    Oct = Num // 12,
    Off = Num mod 12,
    ( if
        ( Off = 0, Rank0 = c, Mod0 = natural
        ; Off = 1, Rank0 = c, Mod0 = sharp
        ; Off = 2, Rank0 = d, Mod0 = natural
        ; Off = 3, Rank0 = d, Mod0 = sharp
        ; Off = 4, Rank0 = e, Mod0 = natural
        ; Off = 5, Rank0 = f, Mod0 = natural
        ; Off = 6, Rank0 = f, Mod0 = sharp
        ; Off = 7, Rank0 = g, Mod0 = natural
        ; Off = 8, Rank0 = g, Mod0 = sharp
        ; Off = 9, Rank0 = a, Mod0 = natural
        ; Off = 10, Rank0 = a, Mod0 = sharp
        ; Off = 11, Rank0 = b, Mod0 = natural
        )
    then
        Rank = Rank0,
        Mod = Mod0
    else
        error("Num mod 12 gave an illegal result!")
    ).

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

:- pred short(char::in, option::out) is semidet.

short('h', help).
short('k', key).
short('l', length).
short('s', seed).

:- pred long(string::in, option::out) is semidet.

long("help",    help).
long("key",     key).
long("length",  length).
long("seed",    seed).

:- pred defaults(option::out, option_data::out) is multi.

defaults(help,      bool(no)).
defaults(key,       string("c")).
defaults(length,    int(16)).
defaults(seed,      int(0)).

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

help(!IO) :-
    io.stderr_stream(StdErr, !IO),
    io.write_string(StdErr,
        "usage: space [-h | --help] [-k | --key key[M]] " ++
        "[-l | --length n] [-s | --seed n]\n", !IO).

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

% This is a very bad number generator. Its only virtue is that
% unlike random.m, its behavior does not depend on word size.

:- type random_supply == int.

:- pred random_init(int::in, random_supply::muo) is det.

random_init(Seed, Supply) :-
    copy(Seed, Supply).

:- pred random_random(int::out, random_supply::mdi, random_supply::muo) is det.

random_random(Value, Supply0, Supply) :-
    Value = Supply0,
    ( if Supply0 < 100 then
        Supply = Supply0 + 1
    else
        Supply = 0
    ).

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