%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Initial Code Base for the CS101/Spr08 Prolog Homework
%  You can run this by
%  % swipl
%  ?- [spr09,hw4code].
%  ?- loop.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% this is the predicate that 
loop :- interact,!,loop.

interact :-
  write_prompt,
  read_request(UserRequest),
  UserRequest \= [quit],             % this allows the user to end the loop by typing quit
  question(Tree, UserRequest, []),
  findanswers(Tree,Answer),
  showanswers(Answer).


 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% parsing the question into a tree representation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

question(show_courses(Q)) --> [show,courses],qualifiers(Q).

qualifiers([]) --> [].
qualifiers([dept(Ds)|Qs]) --> [in],depts(Ds),qualifiers(Qs).
qualifiers([block(B)|Qs]) --> [in,block],blocks(B),qualifiers(Qs).
qualifiers([notblock(B)|Qs]) --> [not,in,block],blocks(B),qualifiers(Qs).
qualifiers([instr(I)|Qs]) --> [taught,by],[I],qualifiers(Qs).
qualifiers([not_onday(Ds)|Qs]) --> [not,meeting,on],days(Ds),qualifiers(Qs).

depts([D]) --> [D].
depts([D|Ds]) --> [D],[or],depts(Ds).

days([D]) --> day(D).
days([D|Ds]) --> day(D),[or],days(Ds).

day(X) --> [X],{member(X,[mon,tue,wed,thu,fri])}.

% This is equivalent to the following more verbose DCGs
% day(mon) --> [mon].
% day(tue) --> [tue].
% day(wed) --> [wed].
% day(thu) --> [thu].
% day(fri) --> [fri].


blocks([B]) --> block_id(B).
blocks([B|Bs]) --> block_id(B), [or], blocks(Bs).

block_id(B) --> [B],{block_time(B,_)}.




%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Finding the answers to the query
% The course predicate has the form:
% course(Semester,Year,Dept,Num,Suffix,Section,Title,Enrollment,Instructor)
% course(S,Y,D,N,Su,Sc,T,E,I)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

findanswers(show_courses(Qs),courses(L)) :-
  setof(N,satisfies(N,Qs),L).
findanswers(_,[]).  % setof fails if there are no answers, findanswers doesn't


% first we handle lists of constraints
% and a course number must statify everything in the list
satisfies(N,[]) :- course_dept(N,_).  % any course satisfies the empty constraint
satisfies(N,[Q|Qs]) :-
  satisfies(N,Q),
  satisfies(N,Qs).

% next we handle individual constraints..

% dept(Ds) requires the course dept to be one of the specified departments
satisfies(N,dept(Ds)) :- course_dept(N,D),member(D,Ds).

% instr(Is) requires the instructor to be in the specified list
satisfies(N,instr(I)) :- course_instr(N,I).

% block(Bs) succeeds if course N is in one of the specified blocks
satisfies(N,block(Bs)) :- course_block(N,B),member(B,Bs).

% notblock(Bs) succeeds if course N is not in one of the specified blocks
satisfies(N,notblock(Bs)) :- course_block(N,B),not(member(B,Bs)).





% not_onday(Ds) takes a list of days
satisfies(N,not_onday(Ds)) :- course_free_on_day(N,Ds).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this shows the answers if they are a set of courses
showanswers(courses(L)) :- 
  write_sorted_classes(L).
showanswers([]) :- write('Your query had no results'),nl.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%  accessor predicates
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

course_dept(ID,Dept) :- course(ID,Dept,_Num,_Suffix,_Sec,_Title,_Block,_LN,_FN).
course_num(ID,Num) :- course(ID,_Dept,Num,_Suffix,_Sec,_Title,_Block,_LN,_FN).
course_suffix(ID,Suffix) :- course(ID,_Dept,_Num,Suffix,_Sec,_Title,_Block,_LN,_FN).
course_block(ID,Block) :- course(ID,_Dept,_Num,_Suffix,_Sec,_Title,Block,_LN,_FN).
course_instr(ID,LN) :- course(ID,_Dept,_Num,_Suffix,_Sec,_Title,_Block,LN,_FN).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%  Code to handle scheduling
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Here are all of the blocks and their times ...

block_time(a, 'MTWTF 9-10  ').
block_time(b, 'MWT 9-10    ').
block_time(c, 'MWT 10-11   ').
block_time(d, 'MWT 11-12   ').
block_time(e, 'MWT 12-1    ').
block_time(f, 'MWT 1-2     ').
block_time(g, 'TF 9-10:30  ').
block_time(h, 'TF 10:30-12 ').
block_time(j, 'TF 12-1:30  ').
block_time(k, 'TF 2-3:30   ').
block_time(l, 'MW 3:30-5   ').
block_time(m, 'MW 5-6:30   ').
block_time(n, 'TF 1:30-3   ').
block_time(p, 'TF 3-4:30   ').
block_time(s1,'M 2-5       ').
block_time(s2,'T 1:30-4:30 ').
block_time(s3,'W 2-5       ').
block_time(s4,'F 1:30-4:30 ').
block_time(s5,'T 4:30-7:30 ').
block_time(s6,'T 9-12      ').
block_time(s7,'F 9-12      ').
block_time(v,'TT 5-6:30    ').
block_time(x1,'M 6:30-9:30 ').
block_time(x2,'W 6:30-9:30 ').
block_time(x3,'Th 6:30-9:30').
block_time(x4,'T 6:30-9:30 ').
block_time(y ,'MW 6:30-8   ').
block_time(tba,'TBA').

% Here is the rule to tell when two blocks have a conflict

conflicts(X,X). % any course conflicts with itself!

conflicts(k,s1).
conflicts(l,s1).
conflicts(s1,k).
conflicts(s1,l).

conflicts(k,s3).
conflicts(l,s3).
conflicts(k,s3).
conflicts(l,s3).


conflicts(g,s6).
conflicts(h,s6).
conflicts(s6,g).
conflicts(s6,h).

conflicts(g,s7).
conflicts(h,s7).
conflicts(s7,g).
conflicts(s7,h).

conflicts(n,s2).
conflicts(p,s2).
conflicts(s2,n).
conflicts(s2,p).

conflicts(n,s4).
conflicts(p,s4).
conflicts(s4,n).
conflicts(s4,p).

conflicts(n,r).
conflicts(s2,r).
conflicts(s4,r).

conflicts(r,n).
conflicts(r,s2).
conflicts(r,s4).

conflicts(v,s5).
conflicts(v,s8).

conflicts(s5,v).
conflicts(s5,x4).

conflicts(q,x1).
conflicts(q,x2).
conflicts(q,x3).
conflicts(q,y).

conflicts(x1,y).

conflicts(x4,5).

conflicts(x2,q).
conflicts(x2,y).

conflicts(x3,q).
conflicts(x3,s8).

% this tests to see if a course with number N does not meet on day D
course_free_on_day(N,D) :- course_block(N,B),free_day(B,D).

% this tests if a block B does not meet on some particular day
free_day(B,mon) :- member(B,[g,h,j,k,n,p,s2,s3,s4,s5,s6,s7,v,x2,x3,x4]).
free_day(B,tue) :- member(B,[b,c,d,e,f,l,m,s1,s3,s4,s7,x1,x2,x3,y]).
free_day(B,wed) :- member(B,[g,h,j,k,n,p,s1,s2,s4,s5,s6,s7,v,x1,x3,x4]).
free_day(B,thu) :- member(B,[g,h,j,k,n,l,m,n,p,s1,s2,s3,s4,s5,s6,s7,x1,x2,x4,y]).
free_day(B,fri) :- member(B,[b,c,d,e,f,l,m,s1,s2,s3,s5,s6,v,x1,x2,x3,x4,y]).

% and this extends it to not meet on a list of days...
free_day(_,[]).
free_day(B,[D|Ds]) :- free_day(B,D),free_day(B,Ds).


% this tests if two courses do not conflict
courses_no_conflict(N1,N2) :- 
  course_block(N1,B1),course_block(N2,B2),not(conflicts(B1,B2)).

% this tests if a course does not conflict with a list of courses
course_doesnot_conflict(_N1,[]).
course_doesnot_conflict(N1,[N|Ns]) :- 
  course_doesnot_conflict(N1,Ns),
  courses_no_conflict(N1,N).

% this tests if a list of courses has no conflicts
no_conflicts([]).
no_conflicts([B|Bs]) :-
  no_conflicts(Bs),
  course_doesnot_conflict(B,Bs).


schedule1(L) :-
   L=[A,B,C,D],
   course_dept(A,cosi),course_num(A,N1),N1>100, 
   course_dept(B,cosi),course_num(B,N2),N2>100, 
   course_dept(C,econ),
   member(X,[eng,hist]),course_dept(D,X), 
   no_conflicts(L).


schedule([],[]).
schedule([A|As],Cs) :-
  schedule(As,Bs),
  ( (course_doesnot_conflict(A,Bs),Cs=[A|Bs]);Cs=Bs).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Auxilliary Procedures for I/O
% These are I/O procedures need to read/write strings...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

write_prompt :- write('>> '),flush.

read_request(U) :-
  readLine(U1),
  tokenize_atom(U1,U).


% readLine(L) ---
%   This procedure uses the Java readLine method on System.in
%   to read in a line of text from the terminal....
%   it corresponds to the Java call
%     (new java.io.BufferedReader
%          (new java.io.InputStreamReader(
%               java.lang.System.in))).readLine()
% or equivalently to the JScheme code
%    (.readLine 
%       (java.io.BufferedReader.
%          (java.io.InputStreamReader.
%             java.lang.System.in$))))
%
readLine(L) :-
  jpl_get('java.lang.System','in',In),
  jpl_new('java.io.InputStreamReader',[In],ISReader),
  jpl_new('java.io.BufferedReader',[ISReader],BReader),
  jpl_call(BReader,'readLine',[],L).


writelist([]).
writelist([A|As]) :- write(A),nl,writelist(As).

% this write the list of classes L in order by their block number
% it uses the predsort method to specify the ordering to be used
write_sorted_classes(L) :-
  predsort(blockorder,L,S),
  writeclasses(S).

blockorder(<,N1,N2) :- course_block(N1,B1),course_block(N2,B1).
blockorder(A,N1,N2) :- course_block(N1,B1),course_block(N2,B2),B1\=B2,compare(A,B1,B2).


% this takes a list L of course numbers and writes out the elements in a nice format
writeclasses([]).
writeclasses([A|As]) :-
  course(A,Dept,Num,Suf,Sec,Title,Block,Last,First),
  block_time(Block,Time),  write(Time),write(': '),
  write(course(A,Dept,Num,Suf,Sec,Title,Block,Last,First)),nl,
  writeclasses(As).

