
database([
  star(["awesome", "movie", "one"],[["emma","z"],["abhijeet","m"]],["lara","b"]),
  star(["awesome", "movie", "two"],[["lara","b"]],["abhijeet","m"]),
  star(["awesome", "movie", "three"],[["emma","z"]],["some","one","else"])])


answer(Query, Answer) :-
  split_string_into_words(Query, WordList) &
  database(D) &
  tokenize(WordList, Answer, D)


/*
================================================================================
  utilities
================================================================================
*/
concat_string_list([],"")
concat_string_list([S],S)
concat_string_list(cons(S, cons(A,Rest)), Result) :-
  concat_string_list(cons(A,Rest), R) & evaluate(stringappend(S, " ", R), Result)


nth1(X,1,X!_)
nth1(X, K, _!L) :- nth1(X, K1, L) & evaluate(plus(1,K1),K)

/*string concat*/
string_concat(A,B,C) :- evaluate(stringappend(A, " ",B),C)

nth1(1, cons(Elem,_), Elem)
nth1(Index, cons(F, Rest), Elem) :-
  distinct(F,Elem) &
  nth1(PrevIndex, Rest, Elem) &
  evaluate(plus(PrevIndex,1),Index)



/*   Formats a list of lists in the following manner:
    Usage: concat_string_list_of_lists([["David", "Lynch"], ["First", "Last"], ["Some",  "Name"]], Result), writeln(Result).
    ==> "David Lynch, First Last, and Some Name"
*/
concat_string_list_of_lists([],"")
concat_string_list_of_lists([Head], H):- concat_string_list(Head, H)
concat_string_list_of_lists(cons(First,cons(Second,nil)),H) :-
    concat_string_list(First,F) &
    concat_string_list(Second, S)&
    concat_string_list(cons(F, cons("and", [S])),H)
concat_string_list_of_lists(cons(Head,cons(Second, cons(Third,Tail))), Result) :-
  concat_string_list_of_lists(cons(Second, cons(Third,Tail)), Rest) &
  concat_string_list(Head, H) &
  string_concat(H, ", ", HeadSpace) &
  string_concat(HeadSpace, Rest, Result)

/*
  split_string_into_words: only works for lower case letters and numbers though...
  split_string_into_words(+String, -WordList)
*/
split_string_into_words(S, Res) :-
    evaluate(readstringall(S),X) & list_stringify(X, Res)
list_stringify([],[])
list_stringify(cons(X, L), cons(S, SL)) :-
    list_stringify(L, SL) &
    evaluate(stringify(X),S)

/*
  drop_last_elem_from_list(+InputList, -ResultList)
*/
drop_last_elem_from_list([X], [])
drop_last_elem_from_list(cons(X, L), cons(X, RL)) :-
   drop_last_elem_from_list(L, RL)

/*
  has_suffix(-Prefix, +Suffix, +Input)
*/
has_suffix(Prefix, Suffix, Input) :-
  evaluate(stringappend("(.*)",Suffix),Pattern) &
  evaluate(matches(Input, Pattern), [A,Prefix]) & same(A, Input)
/*
  has_prefix(+Prefix, -Suffix, +Input)
*/
has_prefix(Prefix, Suffix, Input) :-
  evaluate(stringappend(Prefix, "(.*)"),Pattern) &
  evaluate(matches(Input, Pattern), [A,Suffix]) & same(A, Input)


/*
================================================================================
  query.pl functions
================================================================================
*/

/*
  find_all_movies_by_type(+Role, +ProvidedInfo, -Movies, +Database)
*/
find_all_movies_by_type(NewRole, ProvidedInfo, Movies, Database) :-
	same(NewRole,stardirector) & find_all_movies_by_person(ProvidedInfo, Movies, Database)

find_all_movies_by_type(NewRole, ProvidedInfo, Movies, Database) :-
	same(NewRole,star) &
	find_all_movies_by_star(ProvidedInfo, Movies, Database)

find_all_movies_by_type(NewRole, ProvidedInfo, Movies, Database) :-
	same(NewRole,director) &
	find_all_movies_by_director(ProvidedInfo, Movies, Database)

/*
  find_all_movies_by_person(+Person, -Movies, +Database)
*/
find_all_movies_by_person(Person, Movies, Database) :-
  find_all_movies_by_star(Person, StarredMovies, Database) &
  find_all_movies_by_director(Person, DirectedMovies, Database) &
  evaluate(append(StarredMovies, DirectedMovies), Movies)

/*
  find_all_movies_by_star(+Star, -Movies, +Database)
*/
find_all_movies_by_star(Star, Movies, Database) :-
  evaluate(setofall(Movie,find_one_movie_by_star(Star, Movie, Database)), Movies)
find_one_movie_by_star(Star, Movie, Database) :-
  member(star(Movie, Stars, _), Database) &
  member(Star, Stars)

/*
  find_all_movies_by_director(+Director, -Movies, +Database)
*/
find_all_movies_by_director(Director, Movies, Database) :-
  evaluate(setofall(Movie,find_one_movie_by_director(Director, Movie, Database)), Movies)
find_one_movie_by_director(Director, Movie, Database) :-
  member(star(Movie, _, Director), Database)


/*
================================================================================
  token.pl functions
================================================================================
*/

tokenize(WordList, Result, Database) :-
  find_query_attributes(WordList, QAttr,PAttr, Suffix) &
  eliminate_double_negation(WordList, WordListNoDoubleNegs) &
  tokenize_string_list(WordListNoDoubleNegs, [], _, Database, QAttr, PAttr, Suffix, Result)

tokenize_string_list([],Accumulated,[Result],Database,QAttr, PAttr, Suffix,Message) :-
  finds_closest_in_database(Accumulated, Result, Database, QAttr, PAttr, Suffix,Message)
tokenize_string_list(cons(Word,Rest), Accumulated, cons(Result,cons(Token,RestTokens)),Database, QAttr, PAttr,Suffix,Message) :-
  token(Word, Token, _) &
  finds_closest_in_database(Accumulated, Result, Database, QAttr, PAttr,Suffix,Message) &
  tokenize_string_list(cons(Word,Rest), [], RestTokens, Database, QAttr, PAttr,Suffix,Message)
tokenize_string_list(cons(Word,Rest), _, cons(Token, RestTokens),Database, QAttr, PAttr,Suffix,Message) :-
  token(Word, Token, _) &
  tokenize_string_list(Rest, [], RestTokens, Database, QAttr, PAttr,Suffix,Message)
tokenize_string_list(cons(Word,Rest), Accumulated, RestTokens, Database, QAttr, PAttr,Suffix,Message) :-
  evaluate(append(Accumulated, [Word]), NewlyAccumulated) &
  tokenize_string_list(Rest, NewlyAccumulated, RestTokens, Database, QAttr, PAttr,Suffix,Message)

finds_closest_in_database(WordList, Result, Database, QAttr, PAttr,Suffix, Message) :-
  exists_in_database_left(WordList, Result, Database,RelevantData,ValidRoles, ProvidedInfo) &
  attributeFromList(RelevantData, Database, QAttr, PAttr, _, Message, Suffix,ValidRoles,ProvidedInfo)

finds_closest_in_database(WordList, Result, Database,QAttr, PAttr,Suffix, Message) :-
  exists_in_database_right(WordList, Result, Database,RelevantData, ValidRoles,ProvidedInfo) &
  attributeFromList(RelevantData, Database, QAttr, PAttr, _, Message,Suffix, ValidRoles,ProvidedInfo)

finds_closest_in_database(cons(_, ShorterWordList), Result, Database,QAttr, PAttr,Suffix, Message) :-
  finds_closest_in_database(ShorterWordList, Result, Database,QAttr, PAttr,Suffix, Message)

finds_closest_in_database(WordList, Result, Database,QAttr, PAttr, Suffix,Message) :-
  drop_last_elem_from_list(WordList, ShorterWordList) &
  finds_closest_in_database(ShorterWordList, Result, Database,QAttr, PAttr,Suffix, Message)


/*
  exists_in_database_left(+WordList, -Result, +Database, -RelevantData, -ValidRole, -ProvidedInfo)
  check if a suffix of WordList exists in the database and extract information from it.
  for example, if ["James","Cameron"] exists in the database as a director, then
  calling exists_in_database_left on ["some", "other", "strings", "James","Cameron"]
  will retrieve ["James","Cameron"].
*/
exists_in_database_left(WordList, WordList, Database,RelevantData, ValidRoles,ProvidedInfo) :-
  exists_in_database(WordList, Database,RelevantData, ValidRoles,ProvidedInfo)

exists_in_database_left(cons(_, ShorterWordList), Result, Database,RelevantData, ValidRoles,ProvidedInfo) :-
  exists_in_database_left(ShorterWordList, Result, Database,RelevantData, ValidRoles,ProvidedInfo)

/*
  exists_in_database_right(+WordList, -Result, +Database, -RelevantData, -ValidRole, -ProvidedInfo)
  check if a prefix of WordList exists in the database and extract information from it.
  for example, if ["James","Cameron"] exists in the database as a director, then
  calling exists_in_database_left on ["James","Cameron", "some", "other", "strings"]
  will retrieve ["James","Cameron"].
*/
exists_in_database_right(WordList, WordList, Database,RelevantData, ValidRoles,ProvidedInfo) :-
  exists_in_database(WordList, Database,RelevantData, ValidRoles,ProvidedInfo)

exists_in_database_right(WordList, Result, Database,RelevantData, ValidRoles,ProvidedInfo) :-
  drop_last_elem_from_list(WordList, ShorterWordList) &
  exists_in_database_right(ShorterWordList, Result, Database,RelevantData, ValidRoles,ProvidedInfo)

/*
  exists_in_database(+WordList, +Database, -RelevantData, -ValidRole, -ProvidedInfo)
  check if WordList exists in the database and extract information from it.
*/
exists_in_database(WordList, Database,RelevantData, movie,ProvidedInfo) :-
  member(star(WordList, Stars, Director), Database) &
  same(RelevantData, [WordList, Stars, Director]) &
  same(ProvidedInfo, WordList)

exists_in_database(WordList, Database,RelevantData, star,ProvidedInfo) :-
  ~person_both(WordList,Database) &
  member(star(Movie, X, Director), Database) & member(WordList,X) &
  same(RelevantData, [Movie, X, Director]) &
  same(ProvidedInfo, WordList)

exists_in_database(WordList, Database,RelevantData, director,ProvidedInfo) :-
  ~person_both(WordList,Database) &
  member(star(Movie, Stars, WordList), Database) &
  same(RelevantData, [Movie,Stars, WordList]) &
  same(ProvidedInfo, WordList)

exists_in_database(WordList, Database,RelevantData, stardirector,ProvidedInfo) :-
  person_both(WordList,Database) &
  same(ProvidedInfo, WordList)

/*
  person_both(+Person, +Database)
  true if a person serves both as director and star
*/
person_both(Person,Database) :-
  member(star(_,_,Person),Database) &
  member(star(_,S2,_),Database) & member(Person,S2)

/*
  eliminate_double_negation(+InputList, -ResultList)
  remove consecutive negation words from WordList
*/
eliminate_double_negation([],[])
eliminate_double_negation(cons(Word1,cons(Word2, Rest)),Result) :-
  negToken(Word1,_) &
  negToken(Word2,_) &
  eliminate_double_negation(Rest, Result)
eliminate_double_negation(cons(Word,Rest),cons(Word,Result)) :-
  ~negToken(Word,_) &
  eliminate_double_negation(Rest, Result)
eliminate_double_negation(cons(Head,cons(Word,Rest)),cons(Head,cons(Word,Result))) :-
  ~negToken(Word,_) &
  eliminate_double_negation(Rest, Result)

/* lara*/


/* */

matchesOrNotProvided(PAttr,ValidRoles, _, NewRole) :- same(PAttr, ValidRoles) & same(NewRole, PAttr)
matchesOrNotProvided(PAttr,ValidRoles, _, NewRole):- ~same(PAttr, ValidRoles) & ~same(notspecified,PAttr) & same(stardirector,ValidRoles)
  & same(NewRole, PAttr)
matchesOrNotProvided(PAttr,ValidRoles, _, NewRole):- ~same(PAttr, ValidRoles)& same(notspecified,PAttr) & ~same(stardirector,ValidRoles) &
  same(NewRole, ValidRoles)
matchesOrNotProvided(PAttr,ValidRoles,ProvidedInfo, NewRole) :- ~same(PAttr, ValidRoles) & same(notspecified,PAttr) & same(stardirector, ValidRoles)



setRole(In,NewRole):- same("a",In) & same(star,NewRole)
setRole(In,NewRole):- same("b",In)& same(director,NewRole)
setRole(In,NewRole):- same("c",In)& same(stardirector,NewRole)
setRole(In,NewRole):- ~same("a",In) & ~same("b",In) & ~same("c",In)& same(stardirector,NewRole) & same("Didn't quite catch that. We're showing you all the results anyway.",OutputMessage1)


/* Plural movie requested */
attributeFromList(_, Database, QAttr, PAttr, _, Message,Suffix, ValidRoles, ProvidedInfo) :-
  same(movie,QAttr)& same("s",Suffix) & matchesOrNotProvided(PAttr,ValidRoles, ProvidedInfo, NewRole) &
  same("You might be looking for the movies",M) & find_all_movies_by_type(NewRole, ProvidedInfo, Movies, Database) &
  concat_string_list_of_lists(Movies,Comb) &
  concat_string_list([M,Comb], Message)

find_query_attributes(WordList, QAttr, PAttr, Suffix) :- pattern1(WordList, QAttr, PAttr, Suffix)
find_query_attributes(WordList, QAttr, PAttr, Suffix) :- pattern2(WordList, QAttr, PAttr, Suffix)
%find_question_attribute(WordList, Attr) :- pattern2(WordList, Attr)
find_query_attributes(WordList, QAttr, PAttr, Suffix) :-
  ~pattern1(WordList, QAttr, PAttr, Suffix) &
  ~pattern2(WordList, QAttr, PAttr, Suffix) &
  same("randomInfo",QAttr) %all else fails choose a random one not provided info

%Give ... attribute; e.g. Give me the movie with Bill Pullman
qWordPat1("give")
qWordPat1("who")
qWordPat1("what")
qWordPat1("show")

% lara
pattern1(WordList, ResultQ,ResultP, Suffix) :- member(QW, WordList) &
  qWordPat1(QW) & nth1(QInd, WordList, QW) & member(X, WordList) & token(X, ResultQ, Suffix)&
  nth1(AInd, WordList, X) & evaluate(min(AInd,QInd), QInd) & ~same(AInd,QInd) & member(Y, WordList)&  token(Y,ResultP,_)& nth1(PInd, WordList, Y) & evaluate(min(PInd,AInd),AInd) & ~same(PInd,AInd)

pattern1(WordList, ResultQ,ResultP, Suffix) :- member(QW, WordList) &
  qWordPat1(QW) & nth1(QInd, WordList, QW) &member(X, WordList) &token(X, ResultQ, Suffix) &
  nth1(AInd, WordList, X) & evaluate(min(QInd,AInd),QInd) & ~same(QInd,AInd) & ~negPatternHelper(_,WordList,ResultP,_,WordList,AInd)& same(ResultP,notspecified)

pattern2(WordList, ResultQ,ResultP, Suffix) :- member(QW, WordList) &
  qWordPat1(QW) &nth1(QInd, WordList, QW)& member(X, WordList) & token(X, ResultQ, Suffix)&
  nth1(AInd, WordList, X) & evaluate(min(QInd,AInd), QInd) & ~same(QInd,AInd) & member(Y, WordList) &token(Y,ResultP,_) &nth1(PInd, WordList, Y) & evaluate(min(PInd,AInd), PInd) & ~same(PInd,AInd)
pattern2(WordList, ResultQ,ResultP, Suffix) :- member(QW, WordList) &
  qWordPat1(QW)& nth1(QInd, WordList, QW)& member(X, WordList)& token(X, ResultQ, Suffix)&
  nth1(AInd, WordList, X) & evaluate(min(QInd,AInd), QInd) & ~same(QInd,AInd) & ~negPatternHelper(_,WordList,ResultP,_,WordList,AInd)& same(notspecified,ResultP)

negPatternHelper(Y,WordList,ResultP,PInd,WordList,AInd) :- member(Y, WordList) & token(Y,ResultP,_) & nth1(PInd, WordList, Y) & evaluate(min(PInd,AInd), AInd) & ~same(PInd,AInd)


token(S, A, Suffix) :- attribute_token(S, A, Suffix)
attribute_token(S, star,Suffix) :- has_prefix("star", Suffix, S)
attribute_token(S, star,Suffix) :- has_prefix("act", Suffix, S)
attribute_token(S, director,Suffix) :- has_prefix("direct", Suffix, S)
attribute_token(S, director,_) :- same("by",S)
attribute_token(S, director,Suffix) :- has_prefix("filmmaker", Suffix, S)
attribute_token(S, director,Suffix) :- has_prefix("made", Suffix, S)
attribute_token(S, director,Suffix) :- has_prefix("mak", Suffix, S) %making, make
attribute_token(S, movie, Suffix) :- has_prefix("movie", Suffix, S) %movie, movies
attribute_token(S, movie,Suffix) :-  ~same("filmmaker",S)& has_prefix("film", Suffix, S)
attribute_token(S, movie,Suffix) :- has_prefix("flick", Suffix, S)
negToken("not", neg)
negToken("no", neg)
negToken(S, neg) :- has_suffix(_,"n't",S)

/*
  did not convert:
string_lower(+String, LowerCase)
Convert String to lower case and unify the result with LowerCase.
*/
