:-module(parser_utils,
	 [read_file_to_string/2,
	 atomic_constant_list/3,
	 atomic_constant/3,
	 elementList/3, %PAY ATTENTION: HERE THE ORIGINAL PRED IS /1, BUT SINCE IT'S DCG, IT BECOMES /3 !!!
	 drop_whites/2,
	 drop_comments/2,
	 opening_parenthesis/2,
	 term/3,
	 term_list/3,
	 comma/2,
	 time/3,
	 closing_parenthesis/2,
	 funct/3,
	 number/3,
	 variable/3,
	 full_stop/2,
     integer/3,
     signed_int/3,
     alphanumeric_string/3]).


:-use_module(library(lists)).
%	     [append/3,
%	      member/2, memberchk/2]).

% If the path is a URL, then download the file
read_file_to_string(Path,String):-
    atom_concat('http://',_,Path),!,
    ensure_loaded(library(pillow)), % load the library only if needed
    url_info(Path,Info),
    fetch_url(Info,[],Response),
    
    memberchk(status(Type,Code,Message),Response),
    (Type = success
        -> memberchk(content(String),Response)
        ;  write(Type), write(' '), write(Code), write(': '), print_resp(Message), nl, fail
    ).
% Otherwise, if the file is local, open it
read_file_to_string(File,String):-
	open(File,read,Stream),
	%write('Open OK'),nl,
	read_stream_to_string(Stream,String),
	close(Stream).

print_resp([]):-!.
print_resp([C|S]):- !,format('~s',[[C]]), print_resp(S).



read_stream_to_string(Stream,[]):-
	at_end_of_stream(Stream),
	!.
read_stream_to_string(Stream,[C|T]):-
	get_code(Stream,C),
	read_stream_to_string(Stream,T).

drop_whites([],[]) :- !.
drop_whites(Codes,NoWhiteCodes):-
	whites(Codes,MoreCodes),
	!,
	drop_whites(MoreCodes,NoWhiteCodes).
drop_whites([Code|MoreCodes],[Code|MoreNoWhiteCodes]):-
	drop_whites(MoreCodes,MoreNoWhiteCodes).


full_stop -->
	".".
comma -->
	",".
opening_parenthesis -->
	"(".
closing_parenthesis -->
	")".
pipe -->
	"|".

whites(Codes,MoreCodes):-
	white_codes(WhiteCodes),
	append(WhiteCodes,MoreCodes,Codes).

white_codes(WhiteCodes):-
	white_atom(Atom),
	atom_codes(Atom,WhiteCodes).
white_codes([13]).
white_codes([-1]). % Sometimes in Linux the end-of-file has code -1
	
white_atom(' ').
white_atom('\t').
white_atom('\n').

funct(Functor) -->
	atomic_constant(Functor),
	!.
funct(',')-->
	"".

atomic_constant(Const) -->
	lowercase(LowerCase),
	alphanumeric_string_tail(Alpha),
	{atom_codes(Const,[LowerCase|Alpha])}.
atomic_constant(Const) -->
	"'",
	any_char_tail(Alpha),
	"'",
	{
		char_code('\'', A),
		append(Alpha, [A], L1),
		append([A], L1, L2),
		atom_codes(Const,L2)
	}.
	
any_char_tail(String) -->
	any_char(String),
	!.
any_char_tail([]) -->
	[].
any_char([Code|MoreCodes]) -->
	any_char_excluded_apice(Code),
	!,
	any_char_tail(MoreCodes).

any_char_excluded_apice(Code) -->
	[Code],
	{
		char_code('\'', A),
		Code =\= A
	}.



lowercase(C) -->
	[C],
	{char_code('a',A),
	 char_code('z',Z),
	 A =< C,
	 C =< Z}.

uppercase(C) -->
	[C],
	{char_code('A',A),
	 char_code('Z',Z),
	 A =< C,
	 C =< Z}.
underscore(C) -->
	[C],
	{char_code('_',C)}.

uppercase_or_underscore(C)-->
	uppercase(C),
	!.
uppercase_or_underscore(C)-->
	underscore(C).

alphanumeric_string([Code|MoreCodes]) -->
	alphanumeric(Code),
	!,
	alphanumeric_string_tail(MoreCodes).

alphanumeric_string_tail(String) -->
	alphanumeric_string(String),
	!.
alphanumeric_string_tail([]) -->
	[].

alphanumeric(Code) -->
	lowercase(Code).
alphanumeric(Code) -->
	uppercase(Code).
alphanumeric(Code) -->
	digit(Code).
alphanumeric(Code) -->
	[Code],
	{char_code('_',Code)}.

integer(Integer) -->
	digit_string(DigitString),
	{number_codes(Integer,DigitString)}.
signed_int(Integer) -->
    "+", integer(Integer).
signed_int(Integer) -->
    "-", integer(Neg),
    {Integer is 0-Neg}.

digit_string([Digit|MoreDigits]) -->
	digit(Digit),
	!,
	digit_string_tail(MoreDigits).

digit_string_tail(DigitStringTail) -->
	digit_string(DigitStringTail),
	!.
digit_string_tail([]) -->
	[].

digit(Digit) -->
	[Digit],
	{char_code('0',Zero),
	 char_code('9',Nine),
	 Zero =< Digit,
	 Digit =< Nine}.

float(Float) -->
	digit_string(DigitString1),
	".",
	digit_string(DigitString2),
	{char_code('.',FullStopCode),
	 append(DigitString1,[FullStopCode|DigitString2],DigitString),
	 number_codes(Float,DigitString)}.

number(Number) -->
	float(Number),
	!.
number(Number) -->
	integer(Number).


term(Term) -->
	funct(Functor),
	opening_parenthesis,
	!,
	term_list(Arguments),
	closing_parenthesis,
	{Term=..[Functor|Arguments]}.
% term(TermList) -->
	% "[",
	% !,
	% term_list(TermList),
	% "]".
term(List) -->
	dcgList(List).



	
term(Term) -->
	constant(Term),
	!.
term(Term) -->
	variable(Term),
	!.

term1(Term) -->
	term(Term).
term1(Term) -->
	unary_operator_term(Term).
term1(Term) -->
	binary_operator_term(Term).

unary_operator_term(Term) -->
	oper(Operator),
	term(Term1),
	{concat_atoms(Operator,Term1,Term)}.

binary_operator_term(Term) -->
	term(Term1),
	oper(Operator),
	term(Term2),
	{concat_atoms(Term1,Operator,Term3),
	concat_atoms(Term3,Term2,Term)}.

term_list([Term|MoreTerms]) -->
	term1(Term),
	term_list_tail(MoreTerms).
term_list([]) -->
	[].
	
	
term_list_tail(TermListTail) -->
	comma,
	!,
	term_list(TermListTail).
term_list_tail([]) -->
	[].


dcgList([Head|Tail]) -->
	"[",
	term(Head),
	dcgList_tail(Tail),
	"]".
dcgList([]) -->
	"[",
	"]".
dcgList_tail([Head|Tail]) -->
	comma,
	!,
	term(Head),
	dcgList_tail(Tail).
dcgList_tail(Tail) -->
	pipe,
	dcgList(Tail), !.
dcgList_tail(Tail) -->
	pipe,
	variable(Tail), !.
dcgList_tail([]) -->
	[].

	
	
atomic_constant_list([Const|MoreConsts]) -->
	atomic_constant(Const),
	!,
	atomic_constant_list(MoreConsts).
atomic_constant_list([]) -->
	[].

	


constant(Const) -->
	atomic_constant(Const).
constant(Const) -->
	number(Const).

oper(Operator) -->
	{member(Operator,[+,-,*,/]),
	 atom_codes(Operator,Codes)},
	Codes.
	 

time(Time) -->
	variable(Time).
time(Time) -->
	number(Time).


variable(Variable) -->
	uppercase_or_underscore(UpperCase),
	alphanumeric_string_tail(Alpha),
	{atom_codes(Variable,[UpperCase|Alpha])}.

concat_atoms(A1,A2,A3) :-
	atomo_codes(A1,S1),
	atomo_codes(A2,S2),
	append(S1,S2,S3),
	atomo_codes(A3,S3).

atomo_codes(A,S):-
	number(A),
	!,
	number_codes(A,S).
atomo_codes(A,S):-
	atom_codes(A,S).



%----------------------------------------------------------
% COMMENTS DCG
%----------------------------------------------------------
drop_comments(InputList, OutputList) :-
	phrase(elementList(OutputList),InputList).


elementList(MoreElements) -->
	comment,
	!,
	elementList(MoreElements).
elementList([Element|MoreElements]) --> 
	anElement(Element),
	!,
	elementList(MoreElements).
elementList([]) -->
	[].

anElement(Element) -->
	[Element].

comment -->
	commentStarter_sl, !, commentContent_sl.
comment -->
	commentStarter_ml, commentContent_ml.


%%% Comments on single-line

commentStarter_sl -->
	"//", !.
commentStarter_sl -->
	"%".

commentContent_sl -->
	commentEnder_sl, !.
commentContent_sl -->
	[_], commentContent_sl.

commentEnder_sl -->
	{atom_codes('\n', X)}, X.


%%% Comments on multi-lines...

commentStarter_ml -->
	"/*".

commentContent_ml -->
	commentEnder_ml, !.
commentContent_ml -->
	[_], commentContent_ml.

commentEnder_ml -->
	"*/".

