% --------------------------------------------------------------------------
%   FILE:       MENU.PL
%   AUTHOR:     Tim Duncan
%   UPDATED:    Tue Jul 28 19:46:42 BST 1987
%   PURPOSE:    General purpose menu driver.
% --------------------------------------------------------------------------


:- op(100,fx,*).
:- op(100,xfy,:).



/*  choose(Items,Header,Menu,Menu2)
Mode: choose(-,+,+,+)
Returns the actual item(s) chosen as a  list. Menu is a  list of lists  where
user may  choose as  many items  as desired  from any  ONE list.  This  allows
different menu behaviour, eg:
    a) strict one choice only - [[a],[b],[c]]
    b) any number may be selected - [[a,b,c]]
    c) any number, or none - [[a,b,c],[none]]
Menu is for display, Menu2 contains items for return.
*/



choose(Items,Header,Menu,Menu2) :-
    select(Chosen,Header,Menu),
    flatten(Menu2,Options),
    convert_to_items(Chosen,Options,Items), !.



/* Present the menu, collect and check response, then return
item numbers to choose/4.
*/

select(Chosen,Header,Menu) :-
    repeat, nl,
    runoff(Header), nl,
    display_menu(Menu,1,Restrictions),
    runoff(['Enter appropriate number:']), nl,
    readline(Response),
    cleanup(Response,Chosen),
    check_valid(Chosen,Restrictions), !.




/* Display the menu items, at the same time calculating the first
and last number of each set, so that the 'one set' restriction
can be checked later.
*/

display_menu([],N,[]).
display_menu([H|T],S,[S-L|F]) :-
    display_set(H,S,X),
    L is X-1, !,
    display_menu(T,X,F).


display_set([],N,N).        ;;; items may only be selected from ONE set
display_set([H|T],I,N) :-
     ( I < 10,
       runoff(['  ',I,' - '])
     ; runoff([' ',I,' - ']) ),
     runoff(H), nl,
     J is I + 1, !,
     display_set(T,J,N).



/* Take raw response, convert ranges (eg: 4-6) into numbers,
remove commas and check that all items are numbers.
*/

cleanup([],[]).
cleanup([N,'-',N|More],[N|Rest]) :-
    integer(N), !,
    cleanup(More,Rest).
cleanup([F,'-',T|More],[F|Rest]) :-
    integer(F),
    integer(T),
    N is F+1, !,
    cleanup([N,'-',T|More],Rest).
cleanup([F,T|More],Return) :-      % if no space around minus sign, eg: 4-6
    integer(T),                    % then numbers read as: 4, -6
    T < 0, !,
    N is -T,
    cleanup([F,'-',N|More],Return).
cleanup([','|More],Rest) :- !,
    cleanup(More,Rest).
cleanup([N|More],[N|Rest]) :- !,
    ( integer(N)
    ; bell,
      runoff(['** Enter only numbers please **']),
      !, fail ),
    cleanup(More,Rest).



/* check that the numbers selected are only from ONE set */     

check_valid([H|T],Restrictions) :-
    member(Min-Max,Restrictions),
    in_range(H,Min,Max),
    ( all_in_range(T,Min,Max)
    ; bell,
      runoff(['** You cannot combine those options **']),
      nl, !, fail ).
check_valid([H|T],Restrictions) :-
    bell,
    runoff(['** That number is out of range **']),
    nl, !, fail.



all_in_range([],Min,Max).
all_in_range([H|T],Min,Max) :-
    in_range(H,Min,Max), !,
    all_in_range(T,Min,Max).                   


in_range(H,Min,Max) :-
    H >= Min,
    Max >= H.


/* Return a list of items chosen given their positions in the list */

convert_to_items([],_,[]).
convert_to_items([H|T],Options,[Item|Items]) :-
    nth_item(H,Options,Item), !,
    convert_to_items(T,Options,Items).

nth_item(1,[Option|_],Option).
nth_item(N,[_|Options],Option) :-
     X is N - 1,  !,
     nth_item(X,Options,Option).
