% vim: ft=mercury ts=4 sw=4 et
%
% This is part of the construct_bug test case.

:- module construct_bug_submodule.

:- interface.

:- import_module string, list, int, assoc_list.

:- type stat.
:- type statkey == list(string).

:- func init = (stat::out) is det.
:- func blank = (stat::out) is det.

:- pred count(statkey, stat, stat).
:- mode count(in,      in,   out) is det.

:- pred count(int, statkey, stat, stat).
:- mode count(in,  in,      in,   out) is det.

% Same as count/4, just reversed the first two args
:- pred count_more(statkey, int, stat, stat).
:- mode count_more(in,  in,      in,   out) is det.

:- func count(statkey, stat) = stat.
:- mode count(in, in) = out is det.

:- func count(int, statkey, stat) = stat.
:- mode count(in, in, in) = out is det.

:- func union(stat, stat) = stat.
% Sums the information in two stat stores.

:- func to_assoc_list(stat) = assoc_list(statkey, int).

% :- import_module pprint.

% :- func to_doc(stat::in) = (doc::out) is det.
:- func plain_to_string(stat::in) = (string::out) is det.
:- func to_string(stat::in) = (string::out) is det.
:- func subs_to_string(stat::in) = (string::out) is det.

%% Other useful predicates for evaluating something

:- func minavgmax_string(list(int)::in) = (string::out) is det.

:- implementation.

:- import_module bag.
:- import_module pair.
:- import_module solutions.

:- type stat == bag(list(string)).

init = bag.init.
blank = construct_bug_submodule.init.

count(Elem, InStat, OutStat) :-
  bag.insert(Elem, InStat, OutStat).
count(Count, Elem, InStat, OutStat) :-
  bag.insert_list(list.duplicate(Count, Elem), InStat, OutStat).

count_more(Elem, Count, InStat, OutStat) :-
  count(Count, Elem, InStat, OutStat).

count(Elem, InStat) = OutStat :-
  count(Elem, InStat, OutStat).

count(Count, Elem, InStat) = OutStat :-
  count(Count, Elem, InStat, OutStat).

union(A, B) = bag.union(A, B).
to_assoc_list(Stat) = bag.to_assoc_list(Stat).

to_string(Stat) =
  "% Base Counts:\n"
  ++ plain_to_string(Stat)
  ++ "% Subtotals:\n"
  ++ plain_to_string(calc_subtotals(Stat)).
subs_to_string(Stat) =
  plain_to_string(calc_subtotals(Stat)).

plain_to_string(Stat) = Out :-
  Counts = bag.to_assoc_list(Stat),
  list.sort(comparator(Stat,0), Counts, CountsS),
  list.map(
    (pred((Name - Count)::in, Line::out) is det :-
      Line = string.int_to_string(Count) ++ "\t" ++ join_list("-", Name)
    ), CountsS, Lines),
  Out = join_list("\n", Lines) ++ "\n".

:- pred comparator(stat::in, int::in,
    pair(statkey, int)::in, pair(statkey, int)::in, comparison_result::out)
    is det.

comparator(Stats, Level, (ADescr-ANum), (BDescr-BNum), Out) :-
    ( take(Level, ADescr, ALevel) ->
        ( take(Level, BDescr, BLevel) ->
            ( count_value(Stats, ALevel) < count_value(Stats, BLevel) ->
                Out = (>)
            ; count_value(Stats, ALevel) > count_value(Stats, BLevel) ->
                Out = (<)
            ;
                % same value
                ( ALevel = BLevel ->
                    comparator(Stats, Level+1, (ADescr-ANum), (BDescr-BNum),
                        Out)
                ; compare((<), ALevel, BLevel) ->
                    Out = (>)
                ;
                    Out = (<)
                )
            )
        ;
            Out = (>)
        )
    ; take(Level, BDescr, _BLevel) ->
        Out = (<)
    ;
        Out = (=)
    ).

:- func calc_subtotals(stat::in) = (stat::out) is det.

calc_subtotals(Stat) = OutStat :-
  Counts = bag.to_assoc_list(Stat),
  list.foldl(
    (pred((Name - Count)::in, InBag::in, OutBag::out) is det :-
      aggregate(substarts(Name), count(Count), InBag, OutBag)
    ), Counts, Stat, OutStat).

/*
 ** buggy pprint, do not use.
to_string(Stat) = Str :-
  Str = pprint.to_string(150, construct_bug_submodule.to_doc(Stat)).

to_doc(Stat) =
  text("% Base Counts:\n")
  `<>` plain_to_doc(Stat)
  `</>`
  text("% Subtotals:\n")
  `<>` subtotals_to_doc(Stat).

:- func plain_to_doc(stat::in) = (doc::out) is det.
:- func subtotals_to_doc(stat::in) = (doc::out) is det.

plain_to_doc(Stat) = Out :-
  Counts = bag.to_assoc_list(Stat),
  Out =
    % text("% Statistiky") `</>`
    separated(
      (func((Name-Count)::in) = (Doc::out) is det :-
        Doc = to_doc(Count) `<>` text("\t")
          `<>` text(join("-", Name))
      ), line, Counts)
    `<>` line.

subtotals_to_doc(Stat) = Out :-
  Counts = bag.to_assoc_list(Stat),
  list.foldl(
    (pred((Name - Count)::in, InBag::in, OutBag::out) is det :-
      aggregate(substarts(Name), count(Count), InBag, OutBag)
    ), Counts, Stat, Subtotals),
  Out = plain_to_doc(Subtotals).
*/
:- pred substarts(list(T)::in, list(T)::out) is multi.

substarts([], []).
substarts([_LastElem], []).
substarts([Elem1, Elem2 | Rest], Out) :-
    (
        Out = []
    ;
        substarts([Elem2 | Rest], TOut),
        Out = [Elem1 | TOut]
    ).

:- import_module float.

minavgmax_string(List) = Str :-
    (
        List = [],
        Min = "-", Avg = "-", Max = "-"
    ;
        List = [H | Tail],
        list.foldl3(
            (pred(N::in, MinSoFar::in, NewMin::out, MaxSoFar::in, NewMax::out,
                    SumSoFar::in, NewSum::out) is det:-
                NewSum = SumSoFar + N,
                NewMin = (if N < MinSoFar then N else MinSoFar),
                NewMax = (if N > MaxSoFar then N else MaxSoFar)
            ), Tail, H, MinN, H, MaxN, H, SumN),
        Min = int_to_string(MinN),
        Max = int_to_string(MaxN),
        Prec = float(10),
        Avg = float_to_string(float(round_to_int(
            (float(SumN) / float(length(List))) * Prec)) / Prec)
    ),
    Str = int_to_string(length(List)) ++ ":"
        ++ Min ++ "/" ++ Avg ++ "/" ++ Max.
