/*  $Id$

    Part of BOOT
    Designed and implemented by Jan Wielemaker
    E-mail: jan@swi.psy.uva.nl

    Copyright (C) 1998 University of Amsterdam. All rights reserved.
*/


:- module(blackboard,
	  [ set_blackboard/2,		% -Old, +New
	    current_blackboard/1,	% ?Identifier

	    bdefault/1,			% +Module (or BlackBoard)

	    block/1,			% +Head
	    bunlock/1,			% +Head
	    bunlock/2,			% +Head, +BroadCast
	    blocked/1,			% +Head

	    bassert/1,			% +Term
	    basserta/1,			% +Term
	    bretract/1,			% +Term
	    bretractall/1,		% +Term
	    bset/2,			% +Head, +Value

	    bterm/1,			% ?Term
	    bonce/1,			% ?Term

	    bsave/1,			% +File
	    bload/1			% +File
	  ]).
:- use_module(library(broadcast)).
:- use_module(library(pretty_print)).

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
library(library(blackboard))

	Generic (simple) blackboard library based on the broadcast library
	for forwarding changes to interested clients of the blackboard. The
	package is based on the notion of a `current' blackboard.  

	The manipulation predicate all start with a `b', and otherwise
	have the conventional Prolog names and semantics.  Modifications
	raise the following broadcast messages:

		bmodified(Head, Modification)

	Where Modification is one of:

		added(Term, <end or start>)
		removed(Term).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

:- dynamic
	current_blackboard/1,
	lock/2.

current_blackboard(default_bb).

:- initialization default_bb:unknown(_, fail).

current_blackboard_(BB) :-
	current_blackboard(CBB), !,
	BB = CBB.
current_blackboard_(_) :-
	throw(bberror(no_backboard)).

%	set_blackboard(-Old, +New)
%
%	Set the current blackboard.  Unify `Old' with the old bb, or [] if
%	there is no current bb.

set_blackboard(Old, New) :-
	Old == New, !,
	current_blackboard_(Old).
set_blackboard(Old, New) :-
	current_blackboard_(Old),
	nonvar(New),
	retractall(current_blackboard(New)),
	asserta(current_blackboard(New)).

%	bdefault +Module
%
%	Assign a Prolog module as a default.  Note the default can also
%	be another blackboard!

bdefault(Module) :-
	blterm('$bb_default_to'(Module)), !.
bdefault(Module) :-
	bassert('$bb_default_to'(Module)).

%	bassert(+Term)
%	basserta(+Term)
%
%	Add a term to the blackboard.

bassert(Term) :-
	current_blackboard_(BB),
	assert(BB:Term),
	broadcast(bmodified(Term, added(end))).
basserta(Term) :-
	current_blackboard_(BB),
	asserta(BB:Term),
	broadcast(bmodified(Term, added(start))).
	

%	bset(+Head, +Value)
%
%	Assume a blackboard term consisting of `Head' with `Value' appended.
%	Remove all terms where `Value' is a variable, then assert the new
%	relation.

bset(Head, Value) :-
	current_blackboard_(BB),
	Head =.. List0,
	append(List0, [VarValue], List1),
	Term =.. List1,
	retractall(BB:Term),
	Value = VarValue,
	assert(BB:Term),
	broadcast(bmodified(Term, set)).

%	bretract(+Term)
%	bretractall(+Term)
%	

bretract(Term) :-
	current_blackboard_(BB),
	retract(BB:Term),
	broadcast(bmodified(Term, removed)).

bretractall(Term) :-
	current_blackboard_(BB),
	(   retract(BB:Term),
	    broadcast(bmodified(Term, removed)),
	    fail
	;   true
	).

%	b(un)lock(+Term)
%
%	Lock group of changes to a relation.  Will broadcast
%	bmodified(Term) after the final unlock.

block(Term) :-
	functor(Term, Name, Arity),
	functor(Head, Name, Arity),
	current_blackboard_(BB),
	asserta(lock(Head, BB)).

bunlock(Term) :-
	bunlock(Term, true).

bunlock(Term, Broadcast) :-
	functor(Term, Name, Arity),
	functor(Head, Name, Arity),
	current_blackboard_(BB),
	retract(lock(Head, BB)), !,
	(   (   lock(Term, BB)
	    ;	Broadcast == false
	    )
	->  true
	;   broadcast(bmodified(Head))
	).

blocked(Term) :-
	functor(Term, Name, Arity),
	functor(Head, Name, Arity),
	current_blackboard_(BB),
	lock(Head, BB), !.

clear_blackboard :-
	current_blackboard_(BB),
	(   current_predicate(_, BB:Head),
	    retract(BB:Head),
	    fail
	;   broadcast(bcleared(BB))
	).

%	bterm(-Term)
%

/*
bterm(Term) :-
	(   current_blackboard(BB)
	->  current_predicate(_, BB:Term),
	    BB:Term
	).
bterm(Term) :-
	blterm('$bb_default_to'(M)),
	current_predicate(_, M:Term),
	M:Term.
*/

bterm(Term) :-
	(   current_blackboard(BB)
	->  BB:Term
	).
bterm(Term) :-
	current_blackboard(BB), !,
	catch(BB:'$bb_default_to'(M), _, fail),
	catch(M:Term,
	      error(existence_error(procedure, _), _), fail).

%	bonce(-Term)
%
%	As bterm/1, but deterministic

bonce(Term) :-
	bterm(Term), !.

%	blterm(-Term)
%
%	Local version of bterm

blterm(Term) :-
	current_blackboard_(BB),
	current_predicate(_, BB:Term),
	BB:Term.

%	bsave(+File)
%
%	Save blackboard contents to file

bsave(File) :-
	open(File, write, Fd),
	current_blackboard_(BB),
	get_time(Time),
	bset(saved_at, Time),
	format(Fd, '/* Generated file: Saved blackboard.~n', []),
	format(Fd, '*/~n~n', []),
	format(Fd, 'blackboard(~q).~n~n', [BB]),
	(   blterm(Term),
	    numbervars(Term, 0, _),
%	    write_term(Fd, Term, [quoted(true), numbervars(true)]),
	    pretty_print(Fd, Term),
	    fail
	;   close(Fd)
	).


%	bload(+File)
%
%	Load blackboard contents from file

bload(File) :-
	open(File, read, Fd),
	(   read(Fd, blackboard(BB))
	->  set_blackboard(_, BB),
	    clear_blackboard,
	    read(Fd, Term),
	    retractall(current_relation(_)),
	    bload(Term, Fd)
	;   close(Fd),
	    throw(berror(not_a_blackboard_file(File)))
	),
	close(Fd).

:- dynamic
	current_relation/1.

make_current(Term) :-
	functor(Term, Name, Arity),
	functor(Head, Name, Arity),
	(   current_relation(Head)
	->  true
	;   forall(retract(current_relation(Old)),
		   bunlock(Old)),
	    asserta(current_relation(Head)),
	    block(Term)
	).

bload(end_of_file, _) :- !,
	make_current([]).
bload(Term, Fd) :-
	make_current(Term),
	bassert(Term),
	repeat,
	read(Fd, Term2), !,
	bload(Term2, Fd).