/* Knowledge bus basic (JSON-only) implementation.
 * Paul Martin, 2/2/17.
*/

:- use_module(library(http/http_unix_daemon)).
:- use_module(library('http/thread_httpd')).
:- use_module(library('http/http_dispatch')).
:- use_module(library('http/http_json')).
:- use_module(library('http/http_header')).
:- use_module(library('http/http_client')).
:- use_module(library('http/html_write')).

:- initialization http_daemon.

/* Servlet structure:
    / : landing page for the knowledge base.
    /profiles : landing page for the information profile collection.
          GET : retrieve a JSON catalogue of all information profiles.
         POST : upload a JSON information profile; returns the identifier in JSON format.
       DELETE : purge the knowledge base.
    /profiles/{id} : landing page for a specific information profile.
               GET : retrieve the information profile in JSON format.
               PUT : replace the information profile with another JSON description.
            DELETE : remove the information profile from the knowledge base. */

% The handler predicates for the different facets of the servlet.
:- http_handler(root(.), drip_handler, []).
:- http_handler(root('profiles'), profiles_handler, []).

% info_profile(?Key, ?Type, ?Dict)---describes an information profile as a dictionary.
%   ?Key---a unique UUID.
%   ?Type---the kind of profile: adaptation, application, cloud, component, control, infrastructure, monitoring.
%   ?Dict---a dictionary generated by json_write_dict/2.
dynamic info_profile/3.

% server(+Port)---initialises the HTTP server.
%   +Port---the port via which the server can be accessed.
server(Port) :-
    http_server(http_dispatch, [port(Port)]).

% drip_handler(+Request)---handles requests to the root of the servlet.
%   +Request---a HTTP request.
drip_handler(_) :-
    reply_html_page( title('DRIP knowledge base'),
                     [ h1('DRIP knowledge base'),
                       p('A simple knowledge base for storing information objects needed by the DRIP system.') ] ).

% profile_list_handler(+Request)---handles requests to the profiles collection facet of the servlet.
%   +Request---a HTTP request.
profiles_handler(Request) :-
    % If a POST request is received...
    memberchk(method(post), Request), !,
    ( http_read_json_dict(Request, Dict)
      ;
      throw( http_reply(bad_request("No content type info: should be 'application/json'.")) )
    ),
    ( info_type(Type, Dict),
      uuid(Key),
      assert( info_profile(Key, Type, Dict) )
      ;
      throw( http_reply(server_error("Unable to modify knowledge base!")) )
    ),
    ( atom_concat('profiles/', Key, Location),
      http_handler(root(Location), profile_handler(Key), [])
      ;
      throw( http_reply(server_error("Unable to create resource page for profile.")) )
    ),
    dict_create(Reply, _, [identifier(Key)]),
    reply_json_dict(Reply, [status(201)]).

profiles_handler(Request) :-
    % If a DELETE request is received...
    memberchk(method(delete), Request), !,
    retractall(info_profile(_, _, _)),
    format('Content-type: text/plain~n~n'),
    format('All information profiles deleted.').

profiles_handler(Request) :-
    % If a GET request is received...
    memberchk(method(get), Request), !,
    catch( findall(Key, info_profile(Key, _, _), Keys), _, Keys = [] ),
    dict_create(Reply, _, [identifiers(Keys)]),
    reply_json_dict(Reply).

profiles_handler(_) :-
    % Otherwise report an error.
    format('Status: 403~n'),
    format('Content-type: text/plain~n~n'),
    format('This request is not permitted for the URL given.').

% profile_handler(+Key, +Request)---handles requests regarding individual profiles.
%   +Key---a UUID for a profile.
%   +Request---a HTTP request.
profile_handler(Key, Request) :-
    % If a PUT request is received...
    memberchk(method(put), Request), !,
    catch( info_profile(Key, Type, Dict), _, throw( http_reply(server_error("The information associated with this UUID appears to be missing.")) ) ),
    ( http_read_json_dict(Request, NewDict)
      ;
      throw( http_reply(bad_request("No content type info: should be 'application/json'.")) )
    ),
    ( retract( info_profile(Key, Type, Dict) ),
      assert( info_profile(Key, Type, NewDict) )
      ;
      throw( http_reply(server_error("Unable to modify knowledge base!")) )
    ),
    dict_create(Reply, _, [identifier(Key)]),
    reply_json_dict(Reply).

profile_handler(Key, Request) :-
    % If a GET request is received...
    memberchk(method(get), Request), !,
    catch( info_profile(Key, _, Dict), _, throw( http_reply(server_error("The information associated with this UUID appears to be missing.")) ) ),
    reply_json_dict(Dict).

profile_handler(Key, Request) :-
    % If a DELETE request is received...
    memberchk(method(delete), Request), !,
    catch( info_profile(Key, Type, Dict), _, throw( http_reply(server_error("The information associated with this UUID appears to be missing.")) ) ),
    ( retract( info_profile(Key, Type, Dict) )
      ;
      throw( http_reply(not_modified("Unable to modify knowledge base!")) )
    ),
    atom_concat('profiles/', Key, Location),
    http_delete_handler(root(Location)),
    format('Content-type: text/plain~n~n'),
    format('The information profile with UUID ~q has been deleted.', [Key]).

profile_handler(Key, Request) :-
    % If a POST request is received...
    member(method(post), Request), !,
    format('Status: 409~n'),
    format('Content-type: text/plain~n~n'),
    format('Profile with UUID ~q already exists (use PUT to update an existing profile, or POST to ./profiles to generate a new profile with a new UUID).', [Key]).

profile_handler(_, _) :-
    % Otherwise report an error.
    format('Status: 403~n'),
    format('Content-type: text/plain~n~n'),
    format('This request is not permitted for the URL given.').

% info_type(?Type, +Dict)---determines if a given profile is of a given type.
%   ?Type---the type of the profile.
%   +Dict---the dictionary describing the profile.
info_type(adaptation, Dict) :- _ = Dict.get('adaptation profile').
info_type(application, Dict) :- _ = Dict.get('application profile').
info_type(cloud, Dict) :- _ = Dict.get('cloud profile').
info_type(component, Dict) :- _ = Dict.get('component profile').
info_type(control, Dict) :- _ = Dict.get('control profile').
info_type(infrastructure, Dict) :- _ = Dict.get('infrastructure profile').
info_type(monitoring, Dict) :- _ = Dict.get('monitoring profile').
info_type(misc, _).

%:- server(8080).
