/*

  Turn HTTP Header, and the extra info we picked up along the way,
  into RDF data.   Also do some logic/processing on it, to make
  it easier for use to use later.

*/

:- use_module(library('http/http_header')).
% :- debug(retmeta).

add_header_metadata(Ret, Headers) :-
	% turn Cache-Control directives into just more headers
	(   memberchk(cache_control(CacheControl), Headers),
	    debug(retmeta, 'parsing cache-control: ~q', [CacheControl]),
	    (  parse_cache_control(CacheControl, CCHeaders)
	    -> true
	    ;  print_message(warning, parse_failed_cache_control_header(Ret, CacheControl)),
		fail
	    )
        ->  append(Headers, CCHeaders, AllHeaders)
	;   AllHeaders = Headers
	),
	debug(retmeta, 'all headers: ~q', [AllHeaders]),
	forall(  member(Header, AllHeaders),
		 assert_header_rdf(Ret, Header)
	      ).


/*
changes_in_cache_status(Ret, Dir, XHeaders, Update, NotModified, Previous) :-
	parse_headers(Dir, HH),
	append(HH, XHeaders, Headers),
	headers_to_rdf(Headers, Ret, HeaderRDF),
	set_max_age(Ret, Headers, MaxAgeTriples), !,
	Update1 = (anchor((
		   delete(Ret, retr:status, _),
		   insert(Ret, retr:status, retr:'DONE'),
		   insert(Ret, retr:dir, literal(Dir)),
		   HeaderRDF,
		   MaxAgeTriples
		  ),  retdata)),
	(   memberchk(curl_http_code('304'), XHeaders)
	    % OR 	      ( memberchk(curl_http_code('404'), XHeaders)
	      %    @@@ AND OLD WAS 404, too!
	->  debug(retrieve, 'not modified since ~q', [Previous]),
	    Update = (Update1,
			 (insert(Previous, retr:cacheValidation, Ret, retdata),
			     delete(Ret, retr:status, _, retdata),
			     insert(Ret, retr:status, retr:'BECAMEVALIDATION', retdata)
			 )),
	    NotModified = true
	;   Update = Update1,
	    NotModified = fail
	).

*/

parse_cache_control(CCText, Parts) :-
	atom_codes(CCText, Codes),
	cache_control_header(Parts, Codes, []), !.

:- use_module(library('http/dcg_basics')).
%  http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
cache_control_header([Part|Parts]) -->
	cache_response_directive(Part),
	", ",
	cache_control_header(Parts).
cache_control_header([Part]) --> cache_response_directive(Part).
cache_control_header([]) -->  []. 

cache_response_directive(public(true)) --> "public", !.
cache_response_directive(private(Field)) --> "private=", field(Field). 
cache_response_directive(private(true)) --> "private", !.
cache_response_directive(no_cache(Field)) --> "no-cache=", !, field(Field).
cache_response_directive(no_cache(true)) --> "no-cache", !.
cache_response_directive(no_store) --> "no-store", !.
cache_response_directive(no_transform) --> "no_transform", !.
cache_response_directive(must_revalidate) --> "must_revalidate", !.
cache_response_directive(proxy_revalidate) --> "proxy_revalidate", !.
cache_response_directive(max_age(T)) --> "max-age=",!,dcg_basics:integer(T),!.
cache_response_directive(s_max_age(T)) --> "s-maxage=",!,dcg_basics:integer(T),!.
% post-check and pre-check are Microsoft IE 5 extensions
cache_response_directive(post_check(T)) --> "post-check=",!,dcg_basics:integer(T),!.
cache_response_directive(pre_check(T)) --> "pre-check=",!,dcg_basics:integer(T),!.
cache_response_directive(cache_extension) --> token, "=", token, !.
cache_response_directive(cache_extension) --> token, "=", quoted, !.
cache_response_directive(cache_extension) --> token, !.

token --> "".   % frr now!   @@@@@@@@@
field(_) --> "".
quoted --> "".

unit_test(( parse_cache_control('no-cache, max-age=60', L),
	    L = [no_cache(true), max_age(60)]
	 )).

	
rdf_header_map(accept_ranges, retr:accept_ranges, skip).
rdf_header_map(cache_control, retr:cache_control, literal).       %  @@@ USE in maxAgeFromServer
rdf_header_map(content_length, retr:content_length, skip).
rdf_header_map(content_type, retr:contentType, literal).       %  @@@ use for parser
rdf_header_map(curl_http_code, retr:curl_http_code, skip).
rdf_header_map(curl_size_download, retr:curl_size_download, skip).
rdf_header_map(curl_size_header, retr:curl_size_header, skip).
rdf_header_map(curl_size_request, retr:curl_size_request, skip).
rdf_header_map(curl_size_upload, retr:curl_size_upload, skip).
rdf_header_map(curl_speed_download, retr:curl_speed_download, skip).
rdf_header_map(curl_speed_upload, retr:curl_speed_upload, skip).
rdf_header_map(curl_time_connect, retr:curl_time_connect, skip).
rdf_header_map(curl_time_namelookup, retr:curl_time_namelookup, skip).
rdf_header_map(curl_time_pretransfer, retr:curl_time_pretransfer, skip).
rdf_header_map(curl_time_starttransfer, retr:curl_time_starttransfer, skip).
rdf_header_map(curl_time_total, retr:curl_time_total, skip).
rdf_header_map(curl_url_effective, retr:curl_url_effective, skip).        %  @@@ use for parser base
rdf_header_map(date, retr:dateServed, datetime).
rdf_header_map(etag, retr:etag, skip).               % @@@ maybe use for conditional get
rdf_header_map(expires, retr:expires, datetime).         % @@@ use in maxAgeFromServer
rdf_header_map(last_modified, retr:lastModifiedText, literal).   % used, yeah...
rdf_header_map(last_modified, retr:lastModified, datetime).   % used, yeah...
rdf_header_map(p3p, retr:p3p, literal).
rdf_header_map(proxy_connection, retr:proxy_connection, skip).
rdf_header_map(server, retr:server, literal).
rdf_header_map(sh_accept, retr:sh_accept, skip).
rdf_header_map(sh_if_modified_since, retr:sh_if_modified_since, skip).
rdf_header_map(sh_start_time, retr:sh_start_time, number).
rdf_header_map(sh_stop_time, retr:sh_stop_time, number).
rdf_header_map(sh_uri, retr:sh_uri, skip).
rdf_header_map(www_authenticate, retr:www_authenticate, skip).
rdf_header_map(x_cache, retr:x_cache, skip).
rdf_header_map(connection, retr:connection, skip).
rdf_header_map(set_cookie, retr:set_cookie, skip).   %% we're given the cookie already parsed, so we can't just dump it in the RDF store until we tell RDF about the set_cookie functor
rdf_header_map(pragma, retr:pragma, skip).
rdf_header_map(transfer_encoding, retr:transfer_encoding, skip).
rdf_header_map(content_location, retr:content_location, skip).
rdf_header_map(vary, retr:vary, skip).
rdf_header_map(x_powered_by, retr:x_powered_by, skip).
rdf_header_map(alternates, retr:alternates, skip).
rdf_header_map(tcn, retr:tcn, skip).
rdf_header_map(mime_version, retr:mime_version, skip).
rdf_header_map(max_age, retr:max_age, already_number).
rdf_header_map(input, ignore, skip).
rdf_header_map(x_pad, ignore, skip).
rdf_header_map(http_version, retr:httpVersion, http_version).
rdf_header_map(status, retr:httpStatus, literal).


% SET retr:maxAgeFromServer and retr:estimatedMaxAge
%
%     This could be better done just looking at the RDF KB, I think.
%     Oh well, next time.
%
set_max_age(Ret, Headers, MaxAgeTriples) :-
	memberchk(cache_control(CacheControl), Headers),
	parse_cache_control(CacheControl, CC),
	memberchk(max_age(MaxAge), CC),
	debug(retrieve, 'max-age from server is ~a', MaxAge),
	MaxAgeTriples = insert(Ret, retr:maxAgeFromServer, MaxAge).
% -- fall back to using Expires
set_max_age(Ret, Headers, MaxAgeTriples) :-
	memberchk(expires(ExpiresText), Headers),
	memberchk(date(BornText), Headers),
	parse_http_datetime(ExpiresText, Expires),
	parse_http_datetime(BornText, Born),
	MaxAge is Expires - Born,
	debug(retrieve, 'expires from server give max-age: ~a', MaxAge),
	MaxAgeTriples = insert(Ret, retr:maxAgeFromServer, MaxAge).
% -- fall back to using a 0.001 times since-last-mod
%       nah.
% -- fall back to using 1 minute
set_max_age(Ret, _Headers, insert(Ret, retr:estimatedMaxAge, 60)) :-
	debug(retrieve, 'assigning max-age 60', []).

