% RUN WITH
%dl schedmin -wctrace -stats -n=1 -pfilter=shift -pfilter=present -pfilter=missing -pfilter=superflous

% If you want to see all best models, then you should replace option
% -n=1 by -n=all.

#maxint=100.
%  we can use arithmetics only on values <= than maxint!
% you must take care that maxint is greater than any value
% the program generates.

% DEFINITION OF LEGAL SHIFTS:

time(T) :- #int(T), T<=23.
evenTime(T) :- time(T), evenTime(T1), +(T1,2,T).
evenTime(0).

%  Allowed Shifts' lenght 
maxShiftLenght(8).
minShiftLenght(6).

%  legal starting and ending times for shifts. 
%  we assume that starting time must be even.
startTime(Start) :- evenTime(Start).
endTime(End) :- time(End).

%  for efficiency, it is crucial to keep as small as possible
%  the generation of legal shifts.
%  The best would be to list them as facts (if they are a few).
%  Otherwise, you should add some body predicate to the following
%  rules to constraint the shifts.
legalPeriod(Start,End) :- 
	startTime(Start), endTime(End), lenght(Start,End,Lenght),
        maxShiftLenght(MaxLenght), Lenght<=MaxLenght,
        minShiftLenght(MinLenght), Lenght>=MinLenght,
        not forbiddenPeriod(Start,End).

% you can forbid expicitly a shift by adding a fact forbiddenPeriod.
% for instance, forbiddenPeriod(5,13).

% we assume that the lenght of a shift is less than 11 hours.
% if this is not the case, then it's probably easier to encode legal shifts
% as triples legalPeriod(Start,End,Lenght).
lenght(Start,End,L) :- startTime(Start), endTime(End),
	Start<End, +(Start,L,End).
lenght(Start,End,L) :- startTime(Start), endTime(End),
	End<Start, +(Start,L1,24), +(End,L1,L).
	
%RUNNING THE ABOVE PART OF THE PROGRAM, YOU WILL SEE THE LIST
%OF LEGAL SHIFTS. YOU COULD THEN LET THE USER SELECT THE GOOD
%ONES INTERACTIVELY AND GIVE THEM IN INPUT TO THE NEXT MODULE OF THE PROGRAM,
%AS YOU DO WITH YOUR PROGRAM CURRENTLY.
%to see only the legal shifts, you can run the module with
% option -pfilter=legalPeriod


% DEFINITION OF THE ADMISSIBLE NUMBERS OF WORKERS IN EACH SHIFT
% canWork() predicate

%  min/max numbers of employees that are allowed to work during the day
% canWork(From,To,MinLimit#,MaxLimit#).
% if there is no limit, you can write "canWork(0,24,0,MAX)
% where MAX is the total number of employees.

canWork(22,6,1,2).
canWork(6,14,1,5).
canWork(14,20,2,6).

maxLimit(M) :- canWork(_,_,_,M), not higherLimit(M).
higherLimit(M) :-  canWork(_,_,_,M),  canWork(_,_,_,M1), M1>M.

minLimit(M) :- canWork(_,_,M,_), not lowerLimit(M).
lowerLimit(M) :-  canWork(_,_,M,_),  canWork(_,_,M1,_), M1<M.

empNumber(N) :- #int(N), minLimit(Min), maxLimit(Max), Min<=N, N<=Max.

isIn(H,Start,End) :- time(H), time(Start), time(End),
	Start<=H, H<End.

allowed(Emps,H) :- time(H), canWork(S,E,Min,Max), isIn(H,S,E), 
	empNumber(Emps), Min<=Emps, Emps<=Max.

violatesAllowance(Start,End,Emps) :- 
	isIn(H,Start,End), empNumber(Emps), not allowed(Emps,H).

legalShift(Start,End,Emps) :-
	legalPeriod(Start,End), allowed(Emps,Start),
	not violatesAllowance(Start,End,Emps),
	not forbiddenShift(Start,End,Emps).

% ForbiddenShift allows you to explicitly forbid
% undesired shifts. It is not use now.

%RUNNING THE ABOVE PART OF THE PROGRAM, YOU WILL SEE THE LIST
%OF SHIFTS WITH THE RESPECTIVE EMPLOYEES ASSIGNMENTS.
%YOU COULD THEN LET THE USER SELECT THE GOOD
%ONES INTERACTIVELY AND GIVE THEM IN INPUT TO THE NEXT MODULE OF THE PROGRAM,
%AS YOU DO WITH YOUR PROGRAM CURRENTLY.
%YOU CAN ALTERNATIVELY ELIMINATE UNDESIRED SHIFTS BY ADDING FACTS
%FOR "forbiddenShift", AND RUN THE ENTIRE PROGRAM AGAIN.
%to see only the shifts, you can run the module with
% option -pfilter=legalShift


%SELECTION OF THE SHIFTS:
%This is the hard part of the computation.
%Its efficiency strongly depends on the number of legalShift
%generated by previous modules.

%  Max number of shift-types that can be selected
maxNumberOfShifts(4).


%  "Guess" the shifts.
shift(Start,End,Emps) v -shift(Start,End,Emps) :- legalShift(Start,End,Emps).

:- shift(Start,End,Emps),shift(Start,End,Emps1), Emps!=Emps1.

takenShift(Start,End) :- shift(Start,End,_).

% We need an ordering on the legalShifts, to implement the "count" predicate.
prec(S,E,S1,E1):- legalShift(S,E,_), legalShift(S1,E1,_), S<S1.
prec(S,E,S,E1):- legalShift(S,E,_), legalShift(S,E1,_), E<E1.

preceeded(S,E) :- prec(_,_,S,E).
followed(S,E) :- prec(S,E,_,_).


first(S,E) :- legalShift(S,E,_), not preceeded(S,E).
last(S,E) :- legalShift(S,E,_), not followed(S,E).
next(S,E,S1,E1):- prec(S,E,S1,E1), not anyShiftInBetween(S,E,S1,E1).

anyShiftInBetween(S,E,S1,E1):- prec(S,E,S2,E2),prec(S2,E2,S1,E1).

%count the number of taken shifts

countTaken(S,E,1) :- first(S,E), takenShift(S,E).
countTaken(S,E,0) :- first(S,E), not takenShift(S,E).

countTaken(S1,E1,N1) :- countTaken(S,E,N), next(S,E,S1,E1),
	takenShift(S1,E1),#succ(N,N1).
countTaken(S1,E1,N) :- countTaken(S,E,N), next(S,E,S1,E1),
	not takenShift(S1,E1).

takenShifts(N) :- last(S,E), countTaken(S,E,N).

% once the "count" built-in will be ready, the program will be much simpler
% the recursive definition of countTaken will be erased.
% takenShifts(N) :- #int(N), N=count(takenShift(_,_)).
% or takenShifts(N) :- #int(N), N=count(S,E:takenShift(S,E)).
  

:- maxNumberOfShifts(Max), takenShifts(N), N>Max.

%  if we want that exacly maxNumberOfShifts(.) are taken,
%  then we replace N>Max by N!=Max.

%  requirements on the presence of employees (Wishes).
%  (From/To/Emp#)
required(0,6,1).
required(6,12,3).
required(12,19,4).
required(19,24,2). 

%  possible constraints on assignments should be defined
%  ........

%  count the number of employees present in each time frame (hour)
%  the first two arguments are the shift needed to handle recursion
count(S,E,H,Emps) :-  time(H),first(S,E), shift(S,E,Emps), isIn(H,S,E).
count(S,E,H,0) :-  time(H),first(S,E), shift(S,E,Emps), not isIn(H,S,E).
count(S,E,H,0) :-  time(H),first(S,E), not takenShift(S,E).
	
count(S1,E1,H,Emps) :- 
	count(S,E,H,EN),
 	next(S,E,S1,E1), isIn(H,S1,E1),
	shift(S1,E1,EN1), #int(Emps),
 	+(EN,EN1,Emps).
	
count(S1,E1,H,EN) :- 
	count(S,E,H,EN),
 	next(S,E,S1,E1), not takenShift(S1,E1).
	
count(S1,E1,H,EN) :- 
	count(S,E,H,EN),
 	next(S,E,S1,E1), not isIn(H,S1,E1).
	
present(Hour,Emps) :- last(S,E), count(S,E,Hour,Emps).

% once the "sum" built-in will be ready, the program will be much simpler
% the recursive definition of count will be erased.
% present(Hour,Emps) :- time(H), #int(Emps), Emps=sum(X:shift(_,_,_,X).

%  MINIMIZATION

superflous(H,Emps) :- required(St,End,ER), St<=H, H<End, 
	present(H,EP), EP>ER, +(ER,Emps,EP).

missing(H,Emps) :- required(St,End,ER), St<=H, H<End, 
	present(H,EP), EP<ER, +(EP,Emps,ER).

:- superflous(H,Emps).
:- missing(H,Emps). 

