/*  Part of SWISH

    Author:        Jan Wielemaker
    E-mail:        J.Wielemaker@vu.nl
    WWW:           http://www.swi-prolog.org
    Copyright (c)  2014-2018, VU University Amsterdam
			      CWI, Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

:- module(swish_config,
	  [ swish_reply_config/2,	% +Request, +Options
	    swish_config/2,		% ?Type, ?Config
	    swish_config_hash/2		% -HASH, +Options
	  ]).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/http_path)).
:- use_module(library(http/http_json)).
:- use_module(library(option)).
:- use_module(library(apply)).

:- multifile
	config/2,			% ?Key, ?Value
	config/3,			% ?Key, ?Value, +Options
	web_plugin/1,			% ?Dict
	source_alias/2,			% ?Alias, ?Options
	authenticate/2,			% +Request, -User
        login_item/2,                   % -Server, -HTML_DOM
        login/2,                        % +Server, +Request
        user_info/3.                    % +Request, -Server, -Info

/** <module> Make HTTP locations known to JSON code
*/

		 /*******************************
		 *	       CONFIG		*
		 *******************************/

%%	swish_reply_config(+Request, +Options) is semidet.
%
%	Emit a configuration object to the client if the client requests
%	for '.../swish_config.json', regardless  of   the  path  prefix.

swish_reply_config(Request, Options) :-
	option(path(Path), Request),
	file_base_name(Path, 'swish_config.json'),
	json_config(JSON, Options),
	reply_json(JSON).

%%	swish_config_hash(-Hash, +Options) is det.
%
%	True if Hash is the SHA1 of the SWISH config.

swish_config_hash(Hash, Options) :-
	json_config(Config, Options),
	variant_sha1(Config, Hash).

json_config(json{ http: json{ locations:JSON
			    },
		  swish: SWISHConfig,
		  plugins : Plugins
		}, Options) :-
	http_locations(JSON),
	swish_config_dict(SWISHConfig, Options),
	web_plugins(Plugins, Options).

http_locations(JSON) :-
	findall(ID-Path,
		( http_current_handler(Path, _:_, Options),
		  memberchk(id(ID), Options)
		), Pairs),
	keysort(Pairs, Sorted),
	remove_duplicate_ids(Sorted, Cleaned),
	dict_pairs(JSON, json, Cleaned).

remove_duplicate_ids([], []).
remove_duplicate_ids([Id-Path1,Id-Path2|T], [Id-Path1|Cleaned]) :- !,
	same_ids(T, Id, T1, Paths0),
	sort([Path1,Path2|Paths0], Unique),
	(   Unique = [_]
	->  true
	;   print_message(warning, http(duplicate_handlers(Id, Unique)))
	),
	remove_duplicate_ids(T1, Cleaned).
remove_duplicate_ids([H|T0], [H|T]) :-
	remove_duplicate_ids(T0, T).

same_ids([], _, [], []).
same_ids([Id-Path|T0], Id, T, [Path|TP]) :- !,
	same_ids(T0, Id, T, TP).
same_ids(T, _, T, []).


%%	swish_config_dict(-Config:dict, +Options) is det.
%
%	Obtain name-value pairs from swish_config:config/2

swish_config_dict(Config, Options) :-
	findall(Key-Value, swish_config(Key, Value, Options), Pairs),
	keysort(Pairs, Sorted),
	warn_duplicate_config(Sorted, Unique),
	dict_pairs(Config, json, Unique).

:- dynamic  warned_duplicate/1.
:- volatile warned_duplicate/1.

warn_duplicate_config([], []).
warn_duplicate_config([K-V1,K-V2|T0], [K-V1|T]) :- !,
	collect_same(K, T0, VL, T1),
	(   warned_duplicate(K)
	->  true
	;   sort([V1,V2|VL], [_])
	->  true
	;   print_message(warning, swish(duplicate_config(K, [V1,V2|VL]))),
	    assertz(warned_duplicate(K))
	),
	warn_duplicate_config(T1, T).
warn_duplicate_config([KV|T0], [KV|T]) :- !,
	warn_duplicate_config(T0, T).

collect_same(K, [K-V|T0], [V|VT], T) :- !,
	collect_same(K, T0, VT, T).
collect_same(_, List, [], List).

%!	web_plugins(-Plugins, +Options) is det.
%
%	Obtain a list of JSON dicts for additional web plugins.

web_plugins(Plugins, _Options) :-
	findall(Plugin, web_plugin_ex(Plugin), Plugins).

web_plugin_ex(Plugin) :-
	web_plugin(Plugin0),
	dict_pairs(Plugin0, Tag, Pairs0),
	maplist(expand_paths, Pairs0, Pairs),
	dict_pairs(Plugin, Tag, Pairs).

:- multifile http:location/3.
:- dynamic   http:location/3.

expand_paths(Name-Spec, Name-Path) :-
	compound(Spec),
	compound_name_arity(Spec, Alias, 1),
	http:location(Alias, _, _),
	!,
	http_absolute_location(Spec, Path, []).
expand_paths(Pair, Pair).


%%	config(-Key, -Value) is nondet.
%%	swish_config(-Key, -Value) is nondet.
%
%	Define a name/value pair that will end   up  in the SWISH config
%	object (see =web/js/config.js=)

swish_config(Key, Value) :-
	swish_config(Key, Value, []).

swish_config(Key, Value, Options) :-
	config(Key, Value, Options).
swish_config(Key, Value, _) :-
	config(Key, Value).

% We need to use '$swish wrapper' with a variable _residuals in
% versions that support the `var_prefix` option.
:- if(current_prolog_flag(var_prefix, _)).
config(residuals_var, '_residuals').
:- endif.

		 /*******************************
		 *             LOGIN		*
		 *******************************/

%!	login_item(-Server, -Item) is nondet.
%
%	This hook is called  to  find   all  possible  login options. It
%	should bind Item to an HTML description for html//1 that must be
%	clicked to login  with  this  option.   The  item  may  have the
%	following HTML attributes:
%
%	  - 'data-server'(+Server)
%	  This must be present and provides the first argument for the
%	  login/2 hook.
%
%	  - 'data-frame'(+Style)
%	  The login is realised in a popup to avoid reloading the
%	  current swish page.  If Style is `popup`, a browser popup window
%	  is used. This is necessary for identity providers that refuse to
%	  open inside a frame. The default is `iframe`, which handles
%	  the login inside an =iframe= element in a modal popup.
%
%	The Item is often  an  image.  The   image  must  have  a  class
%	=login-with=. Below is an example to login with Google:
%
%	```
%	swish_config:login_item(Item) :-
%	    http_absolute_location(icons('social_google_box.png'), Img, []),
%	    Item = img([ src(Img),
%	                 class('login-with'),
%	                 'data-server'(google),
%	                 title('Login with Google')
%	               ]).
%	```
%
%	@arg Item may be of the form  `Tag-Item`. In this case the items
%	are ordered by Tag. The default tag is `0`.

%!	login(+Server, +Request) is det.
%
%	If a login item with   `'data-server'(+Server)`  is clicked, the
%	HTTP handler with id `login` is called. This handler figures the
%	selected login _server_ and calls this hook.

%!	user_info(+Request, -Server, -UserInfo:dict) is semidet.
%
%	Each login facility must provide  this   hook.  The  hook should
%	succeed if-and-only-if the user is logged in using this facility
%	and the hook must bind UserInfo with   a  dict that contains the
%	following fields:
%
%	  - user: User
%	  User name (id) if the logged in user.
%	  - name: Name
%	  Common name of the logged in user.
%	  - email: Email
%	  Email address of the logged in user.
%	  - picture: URL
%	  If present, URL is used to indicate the currently logged in
%	  user.
%	  - auth_method: Method
%	  Authentication method used. Currently one of `basic`, `digest`
%	  or `oauth2`.
%	  - logout_url: URL
%	  URL that must be used to logout.  Needed if `auth_method` is
%	  not one of the HTTP authentication methods (`basic` or
%	  `digest`).
%
%	If this hook fails the user is not logged in.


		 /*******************************
		 *          OTHER HOOKS		*
		 *******************************/

%%	source_alias(?Alias, ?Options) is nondet.
%
%	Multifile hook that  defines   properties  of file_search_path/2
%	aliases wrt. storage handling.  Defined options are:
%
%	  - access(Access)
%	  One of `read` or `both`.
%	  - search(Pattern)
%	  The _New Tab_ search form searches in files that satisfy the
%	  given pattern in the matching directories.  Pattern is handed
%	  to expand_file_name/2.


		 /*******************************
		 *	      MESSAGES		*
		 *******************************/

:- multifile
	prolog:message//1.

prolog:message(http(duplicate_handlers(Id, Paths))) -->
	[ 'Duplicate HTTP handler IDs: "~w"'-[Id] ],
	paths(Paths).
prolog:message(swish(duplicate_config(K, [V0|List]))) -->
	[ 'Duplicate SWISH config values for "~w": ~p.  Using ~q'-
	  [K, [V0|List], V0]
	].

paths([]) --> [].
paths([H|T]) --> [ '\t~q'-[H], nl ], paths(T).