:- module(freeze, [freeze/2, frozen/2]).

/** Provides the constraint `freeze/2`.
*/

:- use_module(library(atts)).
:- use_module(library(dcgs)).

:- meta_predicate(freeze(-, 0)).

:- attribute(frozen/1).

verify_attributes(Var, Other, Goals) :-
        get_atts(Var, frozen(Fa)), !,       % are we involved?
        (   var(Other) ->                   % must be attributed then
            (   get_atts(Other,  frozen(Fb)) % has a pending goal?
            ->  put_atts(Other,  frozen((Fb,Fa))) % rescue conjunction
            ;   put_atts(Other,  frozen(Fa)) % rescue the pending goal
            ),
            Goals = []
        ;   Goals = [Fa]
        ).
verify_attributes(_, _, []).

%% freeze(Var, Goal)
%
%  Schedules Goal to be executed when Var is instantiated. This can
%  be useful to observe the exact moment a variable becomes bound to a
%  more concrete term, for example when creating animations of search
%  processes. Higher-level constructs such as `phrase_from_file/2` can
%  also be implemented with `freeze/2`, by scheduling a goal that
%  reads additional data from a file as soon as it is needed.

freeze(X, Goal) :-
    put_atts(Fresh, frozen(Goal)),
    Fresh = X.

attribute_goals(Var) -->
    { get_atts(Var, frozen(Goals)),
      put_atts(Var, -frozen(_)) },
    [freeze:freeze(Var, Goals)].

%% frozen(Var, Goal)

:- use_module(library(lists)).

:- help(freeze(-var,+goal), [iso(false)]).
:- help(frozen(@term,-goal), [iso(false)]).

frozen(Term, Goal) :-
	term_attributed_variables_(Term, Vs),
	collect_atts_(Vs, [], AttsList),
	collect_goals_(Vs, [], Gs),
	reapply_atts_(Vs, AttsList),
	( Gs = [] ->
		Goal = true
	;
		flatten(Gs, Gs2),
		list_to_conjunction(Gs2, Fresh),
		Fresh = Goal
	).

collect_atts_([], AttsList, AttsList).
collect_atts_([X|Tail], AttsList0, AttsList) :-
	get_atts(X, Atts),
	collect_atts_(Tail, [Atts|AttsList0], AttsList).

reapply_atts_([], _).
reapply_atts_([X|Tail], [Atts|Tail2]) :-
	put_atts(X, Atts),
	reapply_atts_(Tail, Tail2).
