/*  $Id: autoload.pl,v 1.7 1997/10/28 13:40:26 jan Exp $

    Copyright (c) 1990 Jan Wielemaker. All rights reserved.
    jan@swi.psy.uva.nl

    Purpose: Automatic library loading
*/

:- module($autoload,
	[ $find_library/5
	, $in_library/2
	, $define_predicate/1
	, $update_library_index/0
	, make_library_index/1
	, make_library_index/2
	, autoload/0
	, autoload/1
	]).

:- dynamic
	library_index/3.			% Head x Module x Path
:- volatile
	library_index/3.

%	$find_library(+Module, +Name, +Arity, -LoadModule, -Library)
%
%	Locate a predicate in the library.  Name and arity are the name
%	and arity of the predicate searched for.  `Module' is the
%	preferred target module.  The return values are the full path names
%	of the library and module declared in that file.

$find_library(Module, Name, Arity, LoadModule, Library) :-
	load_library_index,
	functor(Head, Name, Arity),
	(   library_index(Head, Module, Library),
	    LoadModule = Module
	;   library_index(Head, LoadModule, Library)
	), !.

%	$in_library(?Name, ?Arity)
%	Is true if Name/Arity is in the autoload libraries.

$in_library(Name, Arity) :-
	load_library_index,
	library_index(Head, _, _),
	functor(Head, Name, Arity).

%	$define_predicate(+Head)
%	Make sure pred can be called.  First test if the predicate is
%	defined.  If not, invoke the autoloader.

:- module_transparent
	$define_predicate/1.

$define_predicate(Head) :-
	$defined_predicate(Head), !.
$define_predicate(Term) :-
	$strip_module(Term, Module, Head),
	functor(Head, Name, Arity),
	flag($enable_autoload, on, on),
	$find_library(Module, Name, Arity, LoadModule, Library),
	flag($autoloading, Old, Old+1),
	(   Module == LoadModule
	->  ignore(ensure_loaded(Library))
	;   ignore(Module:use_module(Library, [Name/Arity]))
	),
	flag($autoloading, _, Old),
	$define_predicate(Term).


		/********************************
		*          UPDATE INDEX		*
		********************************/

$update_library_index :-
	$check_file(library('INDEX.pl'), IndexFile),
	file_directory_name(IndexFile, Dir),
	update_library_index(Dir),
	fail.
$update_library_index.

update_library_index(Dir) :-
	concat_atom([Dir, '/INDEX.pl'], IndexFile),
	access_file(IndexFile, write),
	make_library_index(Dir).

clear_library_index :-
	retractall(library_index(_, _, _)).

		/********************************
		*           LOAD INDEX		*
		********************************/

load_library_index :-
	library_index(_, _, _), !.		% loaded
load_library_index :-
	$check_file(library('INDEX'), Index),
	    file_directory_name(Index, Dir),
	    read_index(Index, Dir),
	fail.
load_library_index.
	
read_index(Index, Dir) :-
	seeing(Old), see(Index),
	repeat,
	    read(Term),
	    (   Term == end_of_file
	    ->  !
	    ;   assert_index(Term, Dir),
	        fail
	    ),
	seen, see(Old).

assert_index(index(Name, Arity, Module, File), Dir) :- !,
	functor(Head, Name, Arity),
	concat_atom([Dir, '/', File], Path),
	assertz(library_index(Head, Module, Path)).
assert_index(Term, Dir) :-
	$warning('Illegal term in INDEX.pl of directory ~w: ~w', [Dir, Term]).
	

		/********************************
		*       CREATE INDEX.pl		*
		********************************/

make_library_index(Dir) :-
	make_library_index(Dir, ['*.pl']).
	
make_library_index(Dir, Patterns) :-
	Index = 'INDEX.pl',
	concat_atom([Dir, '/', Index], AbsIndex),
	access_file(AbsIndex, write), !,
	absolute_file_name('', OldDir),
	chdir(Dir),
	expand_index_file_patterns(Patterns, Files),
	(   library_index_out_of_date(Index, Files)
	->  format('Making library index for ~w ... ', Dir), flush,
	    do_make_library_index(Index, Files),
	    format('ok~n')
	;   true
	),
	chdir(OldDir).
make_library_index(Dir, _) :-
	$warning('make_library_index/1: Cannot write ~w', [Dir]).


expand_index_file_patterns(Patterns, Files) :-
	maplist(expand_file_name, Patterns, NestedFiles),
	flatten(NestedFiles, F0),
	subtract(F0, ['INDEX.pl', 'index.pl', 'Make.pl', 'make.pl'], Files).


library_index_out_of_date(Index, _Files) :-
	\+ exists_file(Index), !.
library_index_out_of_date(Index, Files) :-
	time_file(Index, IndexTime),
	(   time_file('.', DotTime),
	    DotTime @> IndexTime
	;   member(File, Files),
	    time_file(File, FileTime),
	    FileTime @> IndexTime
	), !.


do_make_library_index(Index, Files) :-
	open(Index, write, Fd),
	index_header(Fd),
	checklist(index_file(Fd), Files),
	close(Fd).

index_file(Fd, File) :-
	open(File, read, In),
	read(In, Term),
	close(In),
	Term = (:- module(Module, Public)), !,
	delete_suffix(File, '.pl', Base),
	forall( member(Name/Arity, Public),
		format(Fd, 'index((~k), ~k, ~k, ~k).~n',
		       [Name, Arity, Module, Base])).
index_file(_, _).

delete_suffix(File, Suffix, Base) :-
	concat(Base, Suffix, File), !.
delete_suffix(File, _, File).

index_header(Fd):-
	format(Fd, '/*  $Id: autoload.pl,v 1.7 1997/10/28 13:40:26 jan Exp $~n~n', []),
	format(Fd, '    Creator: make/0~n~n', []),
	format(Fd, '    Purpose: Provide index for autoload~n', []),
	format(Fd, '*/~n~n', []).

		 /*******************************
		 *	   DO AUTOLOAD		*
		 *******************************/

%	autoload([options ...])
%
%	Force all necessary autoloading to be done now.

autoload :-
	autoload([]).

autoload(Options) :-
	option(Options, verbose/on, Verbose),
	$style_check(Old, Old), 
	style_check(+dollar), 
	please(autoload, OldAutoLoad, off),
	findall(Pred, needs_autoloading(Pred), Preds),
	please(autoload, _, OldAutoLoad),
	$style_check(_, Old),
	(   Preds == []
	->  true
	;   please(verbose_autoload, OldVerbose, Verbose),
	    please(autoload, OldAutoLoad2, on),
	    checklist($define_predicate, Preds),
	    please(autoload, _, OldAutoLoad2),
	    please(verbose_autoload, _, OldVerbose),
	    autoload(Verbose)
	).
	
needs_autoloading(Module:Head) :-
	predicate_property(Module:Head, undefined), 
	\+ predicate_property(Module:Head, imported_from(_)), 
	functor(Head, Functor, Arity), 
	$in_library(Functor, Arity).

option(Options, Name/Default, Value) :-
	(   memberchk(Name = Value, Options)
	->  true
	;   Value = Default
	).

