advisor :-
    load_data,
    intro,
    query('Please enter student name', StudentName), nl,
    menu(StudentName).

load_data :-
    format('Loading Course Database...'),
    flush_output,
    consult('ics-courses.pl'), 
    format('done.\n\n'), 
 
    format('Loading Course Offerings Database...'),
    flush_output,
    consult('ics-offerings.pl'), 
    format('done.\n\n'),

    format('Loading Graduation Requirements Database...'),
    flush_output,
    consult('ics-requirements.pl'), 
    format('done.\n\n'),
 
    assertz(passed('','')). 


intro :-
    format('General Instructions\n\n'),
    format('This is the ICS advisor program.\n'). 

menu(StudentName) :-
    !, repeat,
        format('\nMain Menu [~w]\n', StudentName), 
        format('1. Enter Passed Courses\n'), 
        format('2. View Passed Courses\n'), 
        format('3. Find Course Prerequisites\n'),
        format('4. View Courses Offerings\n'), 
        format('5. Find Unfulfilled Graduation Requirements\n'), 
        format('6. Exit\n'),
        get_line(Line),
        length(Line, LineLength),
        (LineLength \== 0 *->
            nth0(0, Line, Input),
            Option is Input - 0'0 %'
        ;
            Option is 0),

        menu_option(StudentName, Option), 
    Option == 6.

menu_option(StudentName, 1) :- 
    enter_passed_courses(StudentName).

menu_option(StudentName, 2) :- 
    format('\n~w has passed: ', StudentName),
    print_passed_courses(StudentName).

menu_option(_, 3) :- 
    find_course_prequisites.

menu_option(_, 4) :- 
    format('\nPlease enter semester that you would like to see courses for\n'), 
    format('You can just press enter to see all courses from every semester\n'),
    format('\nPossible Choices:\n'),
    print_available_semesters,
    query('\nWhich semester ', Query),
    string_length(Query, QueryLength),
    (QueryLength == 0 *-> 
        print_available_courses
     ;
        string_to_atom(Query, Semester),
        (print_available_courses(Semester) *->
            true
        ; format('\nInvalid Choice!\n'))
    ).

menu_option(StudentName, 5) :- 
    find_graduation_reqs(StudentName).

menu_option(_, _) :- 
    true.

enter_passed_courses(StudentName) :-
    format('\nPlease enter courses that ~w has taken and passed ', 
           StudentName),
    format('(one per line).\n'),
    format('When you are done just press enter on a blank line.\n'),
    !, repeat,
        format('\nPossible Choices:\n'), 
        print_eligible_courses(StudentName),
        query('\nEnter Course [Blank Line to Stop] ', Query), 
        string_length(Query, QueryLength),
        (QueryLength \== 0 *-> 
            string_to_atom(Query, Course),
            (add_course(StudentName, Course) *-> 
                format('\nCourse Added\n')
            ;
                format('\nInvalid Choice\n'))
        ;
            true), 
    QueryLength == 0, !.

print_passed_courses(StudentName) :-
    setof(Course, passed(StudentName, Course), Courses) *->
        print_list(Courses), nl
    ;
        format('(none)\n').

print_eligible_courses(StudentName) :-
    setof(Course, eligible(StudentName, Course), Courses) *->
        print_list(Courses), nl
    ;
        format('(none)\n').

find_course_prequisites :-
    query('\nEnter Course to find its prerequisites:', Query),
    string_to_atom(Query, Course),
    (course(Course) *->
        format('Prerequisites: '),
        (setof(Prereq, trans_prereq(Prereq, Course), Prereq) *->
            print_list(Prereq), nl
         ;
            format('(none)')), nl
    ;
        format('Not a valid course!\n')
    ).

print_available_courses :-
    setof(Course, course(Course), Courses),
    print_list(Courses, ', ').

print_available_courses(Semester) :-
    setof(Course, offered(Course, Semester), Courses),
    print_list(Courses, ', ').

print_available_semesters :-
    findall(Semester, offered(_, Semester), Semesters),
    list_to_set(Semesters, UniqueSemesters),
    print_list(UniqueSemesters, ', ').

find_graduation_reqs(StudentName) :- 
    format('~w needs to graduate: \n', StudentName),
    setof(Course, required(Course), RequiredCourses),
    findall(Course, passed(StudentName, Course), PassedCoursesList),
    list_to_set(PassedCoursesList, PassedCourses),
    subtract(RequiredCourses, PassedCourses, UnfulfilledCourses),
    length(UnfulfilledCourses, Length),
    (Length \== 0 *-> 
        print_list(UnfulfilledCourses),nl
    ; 
        format('(none)\n')
    ),
    verify_atleast_5(StudentName, Num, Left), 
    (Num > 0 *->
        format('You need atleast ~d of: \n', Num),
        print_list(Left), nl
    ; 
        true
    ).

verify_atleast_5(StudentName, Num, Left) :-
    findall(Course, require_atleast_5(Course), List),
    list_to_set(List, Set),
    findall(Course, passed(StudentName, Course), PassedCoursesList),
    list_to_set(PassedCoursesList, PassedCourses),
    length(List, Length),
    (Length < 5 *->
        format('\nDB Corrupt.\n'),
        fail
    ; 
        intersection(PassedCourses, List, Intersection),
        length(Intersection, InterLength),
        Num is 5 - InterLength,
        subtract(List, Intersection, Left)
    ).
     
add_course(StudentName, Course)  :-
    eligible(StudentName, Course),
    asserta(passed(StudentName, Course)).

eligible(StudentName, Course) :-
    course(Course),
    not(has_passed(StudentName, Course)),
    forall(prerequisite(Prerequisite,Course), 
           has_passed(StudentName, Prerequisite)). 

trans_prereq(P, C) :-
    prerequisite(P, C).

trans_prereq(P, C) :-
    prerequisite(X, C),
    trans_prereq(P, X).

has_passed(StudentName, CoursesList) :-
    is_list(CoursesList),
    findall(Course, passed(StudentName, Course), PassedCoursesList),
    list_to_set(PassedCoursesList, PassedCourses),
    list_to_set(CoursesList, Courses),
    intersection(Courses, PassedCourses, Intersection),
    length(Intersection, Length),
    Length \== 0.

has_passed(StudentName, Course) :-
    passed(StudentName, Course).

%%%%  General Purpose Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_line(Line) :-
    current_input(Stream),
    read_line_to_codes(Stream, Line).
 
query(Message, Response) :- 
    format('~w\n', Message),
    get_line(List),
    string_to_list(Response, List).

print_list([]).
print_list(List) :-
    print_list(List, ' '). 
print_list([Head|Tail], Delimiter) :-
    Tail == [] *-> 
        format('~w', Head),
        print_list(Tail)
    ; 
        format('~w~w', [Head, Delimiter]),
        print_list(Tail, Delimiter). 
