%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%  Constraint Handling Rules			      version 2.2 %
%								  %
%  (c) Copyright 1996-98					  %
%  LMU, Muenchen						  %
%								  %
%  File:   chr.pl						  %
%  Author: Christian Holzbaur		christian@ai.univie.ac.at %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%
% The CHR runtime system,
% the constraint store.
%
% Two functions: a) storage b) reactivation triggered by bindings
%
% Reactivation is symmetric: if two variables with suspensions
% are unified, both suspensions run. (Both variables got more
% constrained)
%
% *** Sequence of wakeups determines termination of handler leq ***
%
% Another sequence that could matter is the one
% generated by the iterators
%
% Layout:
%
%	   suspension(Id,State,Closure,Generation,PropagationHistory,F|Args)
%
%   Id is 1st to allow for direct comparisons (sort) and avoids
%   unifiability if the Id is nonvar.
%   F is the constraint functor
%
%

:- module( chr,
	[
	  find_constraint/2,
	  find_constraint/3,
	  findall_constraints/2,
	  findall_constraints/3,
	  remove_constraint/1,
	  current_handler/2,
	  current_constraint/2,
	  unconstrained/1,
	  notify_constrained/1,

	  chr_trace/0, chr_notrace/0,
	  chr_debug/0, chr_nodebug/0, chr_debugging/0,
	  chr_leash/1, chr_spy/1, chr_nospy/1
      ]).

:- use_module( library('chr/getval')).

:- use_module( library(lists),
	[
	  append/3,
	  member/2,
	  is_list/1,
	  nth/3,
	  select/3
	]).

:- use_module( library(terms),
	[
	  term_variables/2,
	  subsumes_chk/2,
	  subsumes/2
	]).

:- use_module( library(assoc),			% propagation history
	[
	  empty_assoc/1,
	  put_assoc/4,
	  get_assoc/3,
	  assoc_to_list/2
      ]).

:- use_module(library('chr/sbag')).   % link to sbag_l.pl or sbag_a.pl
:- use_module(library('chr/chrcmp')).
:- use_module(library('chr/trace')).

:- use_module(library(atts)).

:- attribute locked/0, exposed/1, dbg_state/1.

%
% Problem with cyclic structures:
%   error reporters seem to use write ...
%
:- multifile
	user:portray/1,
	user:portray_message/2,
	user:goal_expansion/3.

:- dynamic
	user:portray/1,
	user:portray_message/2,
	user:goal_expansion/3.

%
user:portray( Susp) :-
	Susp =.. [suspension,Id,Mref,_,_,_,_|_],
	nonvar( Mref),
	!,
	write('<c'), write(Id), write('>').	% (c)onstraint
%
user:portray( '$want_duplicates'(_,Term)) :- !, % cf. attribute_goal/2
	prolog_flag( toplevel_print_options, Options),
	write_term( Term, Options).

:- initialization
	setval( id, 0).		% counter for portray/debugger

%
user:portray_message( error, chr(multiple_handlers(Old,New,Module))) :- !,
	format( user_error, '{CHR ERROR: registering ~p, module ~p already hosts ~p}~n',
	  [New,Module,Old]).

% -----------------------------------------------------------------

%
% *** MACROS ***
%
%
user:goal_expansion( lock_some(L),		 chr, Exp) :- is_list(L),
	unravel( L, lock, Exp).
user:goal_expansion( unlock_some(L),		 chr, Exp) :- is_list(L),
	unravel( L, unlock, Exp).
user:goal_expansion( via([],V), 		 chr, global_term_ref_1(V)).
user:goal_expansion( via([X],V),		 chr, via_1(X,V)).
user:goal_expansion( via([X,Y],V),		 chr, via_2(X,Y,V)).
user:goal_expansion( via([X,Y,Z],V),		 chr, via_3(X,Y,Z,V)).
user:goal_expansion( load_args(S,State,Args),	 chr, Exp) :-
	is_list( Args),
	Susp =.. [suspension,_,Mref,_,_,_,_|Args],
	Exp = ( S=Susp, get_mutable( State, Mref) ).
%
%
%
user:goal_expansion( nd_init_iteration(V,_,_,Att,S), _, Exp) :-
	arg( 1, Att, Stack),
	Exp = ( get_atts(V,Att), chr:sbag_member(S,Stack) ).
%
user:goal_expansion( init_iteration(V,_,_,Att,L), _, Exp) :-
	arg( 1, Att, Stack),
	Exp = ( get_atts(V,Att), chr:iter_init(Stack,L) ).

unravel( [],	 _, true).
unravel( [X|Xs], F, (G,Gs)) :-
	G =.. [F,X],
	unravel( Xs, F, Gs).

% ----------------------- runtime user predicates -----------------

remove_constraint( Susp) :-
	nonvar( Susp),
	functor( Susp, suspension, N),
	N >= 6,
	!,
	debug_event( remove(Susp)),
	remove_constraint_internal( Susp, Vars),
	arg( 3, Susp, Module:_),
	arg( 6, Susp, F),
	A is N-6,
	Module:detach( F/A, Susp, Vars). 
remove_constraint( S) :-
	raise_exception( type_error(remove_constraint(S),1,'a constraint object',S)).

find_constraint( Term, Susp) :-
	global_term_ref_1( Global),
	find_constraint( Global, Term, Susp).

find_constraint( V, Term, Susp) :- var( V), !,
	find_constraint_internal( V, Term, Susp, active, _).
find_constraint( A, B, C) :-
	raise_exception( instantiation_error( find_constraint(A,B,C), 1)).

find_constraint_internal( V, Term, Susp, State, Module) :-
	constraint( Handler, F/A, Att),
	functor( Term, F, A),			% prune some
	arg( 1, Att, Stack),
	current_handler( Handler, Module),
	Module:get_atts( V, Att),
	length( Args, A),
	Try =.. [F|Args],
	sbag_member( Susp, Stack),
	Susp =.. [suspension,_,Mref,_,_,_,_|Args],
	get_mutable( State, Mref),
	subsumes( Term, Try).

%
% Test for unconstrained var
% Used by some math solvers
%
unconstrained( X) :-
	% var(X), prolog:'$get_cva'(X,[],_).
	find_constraint( X, _, _), !, fail.
unconstrained( _).

findall_constraints( C, L) :-
	global_term_ref_1( Global),
	findall_constraints( Global, C, L).

findall_constraints( V, C, L) :- var( V), !,
	findall( M:Att, (
			    constraint( H, F/A, Att),
			    functor( C, F, A),
			    current_handler( H, M)
			),
			Agenda),
	findall_constraints( Agenda, C, V, L, []).
findall_constraints( V, C, L) :-
	raise_exception( instantiation_error( findall_constraints(V,C,L), 1)).

findall_constraints( [],		  _, _) --> [].
findall_constraints( [Module:Att|Agenda], C, V) -->
	( {
	    arg( 1, Att, Stack),
	    Module:get_atts( V, Att),
	    iter_init( Stack, State)
	  } ->
	    findall_constraints_( State, C, Module)
	;
	    []
	),
	findall_constraints( Agenda, C, V).

findall_constraints_( State, _,       _) --> {iter_last(State)}.
findall_constraints_( State, General, Module) -->
	{
	    iter_next( State, S, Next)
	},
	( {
	    S =.. [suspension,_,Mref,_,_,_,F|Args],
	    get_mutable( active, Mref),
	    Term =.. [F|Args],
	    subsumes_chk( General, Term)
	  } ->
		[ Term#S ]
	;
		[]
	),
	findall_constraints_( Next, General, Module).

%
% Decorate a constraint Term from Module
% with a module prefix if needed.
%
module_wrap( Term, Module, Wrapped) :-
	prolog_flag( typein_module, Typein),
	( Module == Typein ->
	    Wrapped = Term
	; predicate_property( Typein:Term, imported_from(_)) ->
	    Wrapped = Term
	;
	    Wrapped = Module:Term
	).

% -----------------------------------------------------------------
/*

   Two namespaces handler/module actually only justified if there
   can be more than one handler per module ...

*/

:- dynamic handler/2.
:- dynamic constraint/3.

current_handler( Handler, Module) :-
	handler( Handler, Module).

current_constraint( Handler, C) :-
	constraint( Handler, C, _).

register_handler( Handler, Cs, Slots) :-
	prolog_load_context( module, Module),
	( handler(Other,Module),
	  Other \== Handler ->
	    raise_exception( chr(multiple_handlers(Other,Handler,Module)))
	; handler( Handler, Module) ->
	    true				% simple reload
	;
	    assert( handler(Handler,Module))
	),
	retractall( constraint(Handler,_,_)),
	reg_handler( Cs, Slots, Handler).

reg_handler( [],     [],     _).
reg_handler( [C|Cs], [S|Ss], Handler) :-
	assert( constraint(Handler,C,S)),
	reg_handler( Cs, Ss, Handler).

% ----------------------------------------------------------------

notify_constrained( X) :- var( X),
	findall( M, handler(_,M), Modules),
	notify_constrained( Modules, X).
notify_constrained( X) :- nonvar( X),
	raise_exception( instantitation_error( notify_constrained(X),1)).

notify_constrained( [], _).
notify_constrained( [M|Ms], X) :-
	M:get_suspensions( X, S),
	run_suspensions( S),
	notify_constrained( Ms, X).

%
% support for verify_attributes/3, notify_constrained/1
%
% Approximation because debug state might change between calls ...
%
run_suspensions( Slots) :-
	getval( debug, State),
	( State == off ->
	    run_suspensions_loop( Slots)
	;
	    run_suspensions_loop_d( Slots)
	),
	true.

run_suspensions_loop( []).
run_suspensions_loop( [A|As]) :-
	arg( 1, A, Stack),
	iter_init( Stack, State),
	run_suspensions_( State),
	run_suspensions_loop( As).

run_suspensions_loop_d( []).
run_suspensions_loop_d( [A|As]) :-
	arg( 1, A, Stack),
	iter_init( Stack, State),
	run_suspensions_d( State),
	run_suspensions_loop_d( As).

%
% Transition active->triggered->removed instead of
% active->removed is to avoid early gc of suspensions.
% The suspension's generation is incremented to signal
% to the revive scheme that the constraint has been
% processed already.
%
run_suspensions_( State) :- iter_last( State).
run_suspensions_( State) :-
	iter_next( State, S, Next),
	arg( 2, S, Mref),
	get_mutable( Status, Mref),
	( Status==active ->
	    update_mutable( triggered, Mref),
	    arg( 4, S, Gref),
	    get_mutable( Gen, Gref),
	    Generation is Gen+1,
	    update_mutable( Generation, Gref),
	    arg( 3, S, Goal),
	    call( Goal),
	    get_mutable( Post, Mref),
	    ( Post==triggered ->
		update_mutable( removed, Mref)
	    ;
		true
	    )
	;
	    true
	),
	run_suspensions_( Next).

run_suspensions_d( State) :- iter_last( State).
run_suspensions_d( State) :-
	iter_next( State, S, Next),
	arg( 2, S, Mref),
	get_mutable( Status, Mref),
	( Status==active ->
	    update_mutable( triggered, Mref),
	    arg( 4, S, Gref),
	    get_mutable( Gen, Gref),
	    Generation is Gen+1,
	    update_mutable( Generation, Gref),
	    arg( 3, S, Goal),
	    byrd( S, Goal),
	    get_mutable( Post, Mref),
	    ( Post==triggered ->
		update_mutable( removed, Mref)
	    ;
		true
	    )
	;
	    true
	),
	run_suspensions_d( Next).

byrd( Self, Goal) :-
	(   debug_event( wake(Self)), call( Goal)
	;   debug_event( fail(Self)), !, fail
	),
	(   debug_event( exit(Self))
	;   debug_event( redo(Self)), fail
	).

%
% Merge 2 sorted lists of Name/1 terms.
% The argument of each term is a sbag.
%
merge_attributes( [],	  Bs, Bs).
merge_attributes( [A|As], Bs, Cs) :-
	merge_attributes( Bs, Cs, A, As).

merge_attributes( [],	  [A|As], A, As).
merge_attributes( [B|Bs], Cs,	  A, As) :-
	functor( A, NameA, 1),
	functor( B, NameB, 1),
	compare( R, NameA, NameB),
	( R == < -> Cs = [A|Css], merge_attributes( As, Css, B, Bs)
	; R == > -> Cs = [B|Css], merge_attributes( Bs, Css, A, As)
	;
	    Cs = [C|Css],
	    functor( C, NameA, 1),
	    arg( 1, A, StackA),
	    arg( 1, B, StackB),
	    arg( 1, C, StackC),
	    sbag_union( StackA, StackB, StackC),
	    merge_attributes( As, Bs, Css)
	).

show_bag( Bag) :-
	iter_init( Bag, State),
	show_bag_( State),
	nl.

show_bag_( State) :- iter_last( State).
show_bag_( State) :-
	iter_next( State, S, Next),
	arg( 2, S, Ref),
	get_mutable( St, Ref),
	format( ' ~p:~p', [S,St]),
	show_bag_( Next).

%
% Support for attribute_goal/2.
%
% Complication: the Sicstus kernel removes duplicates
% via call_residue/2 - that includes the toplevel.
% We may want to see them ->
%   tag Term with Suspension, 'untag' via portray/1
%
% Called with a list of slots once per module
%
attribute_goals( L, Goal, Module) :-
	attribute_goal_loop( L, Module, GL, []),
	l2c( GL, Goal).

attribute_goal_loop( [],     _) --> [].
attribute_goal_loop( [A|As], Mod) -->
	{
	    arg( 1, A, Stack),
	    iter_init( Stack, State)
	},
	attgs_( State, Mod),
	attribute_goal_loop( As, Mod).

attgs_( State, _) --> {iter_last( State)}.
attgs_( State, Module) -->
	{
	    iter_next( State, S, Next),
	    S =.. [suspension,_,Mref,_,_,_,F|Args]
	},
	( {get_mutable(active,Mref)} ->
	     {
	       Term =.. [F|Args],
	       module_wrap( Term, Module, Wrapped)
	     },
	     [ '$want_duplicates'(S,Wrapped) ]
	;
	     []
	),
	attgs_( Next, Module).

%
% fail for empty list
%
l2c( [C], C) :- !.
l2c( [C|Cs], (C,Cj)) :-
	l2c( Cs, Cj).

%
% Unlink removed constraints cleanly from all chains
% Still need gc state because of wake,
% but re-insertion = insert because of complete removal.
%
chr_gc :-
	global_term_ref_1( Global),
	findall( M, handler(_,M), Modules),
	chr_gcm( Modules, Global).

chr_gcm( [],	 _).
chr_gcm( [M|Ms], Global) :-
	M:get_suspensions( Global, AllS),
	term_variables( [Global|AllS], Vars),	% AllS may be ground
	chr_gcv( Vars, M),
	chr_gcm( Ms, Global).

%
% Have compiler generated support?
%
chr_gcv( [],	 _).
chr_gcv( [V|Vs], M) :-
	M:get_suspensions( V, Old),
	chr_gcb( Old, New),
	M:put_suspensions( V, New),
	chr_gcv( Vs, M).

chr_gcb( [],	 []).
chr_gcb( [S|Ss], [Sgc|Ts]) :-
	arg( 1, S, Bag),
	iter_init( Bag, State),
	functor( S, N, 1),
	functor( T, N, 1),
	gc_bag( State, Lgc),
	( Lgc==[] ->
	    Sgc = -T
	;
	    Sgc = T,
	    list_to_sbag( Lgc, BagGc),
	    arg( 1, T, BagGc)
	),
	chr_gcb( Ss, Ts).

gc_bag( State, []) :- iter_last( State).
gc_bag( State, L) :-
	iter_next( State, Susp, Next),
	arg( 2, Susp, Mref),
	get_mutable( SuspState, Mref),
	( SuspState==removed ->
	    L = Tail,
	    update_mutable( gc, Mref)
	; SuspState==gc ->
	    L = Tail
	;
	    L = [Susp|Tail]
	),
	gc_bag( Next, Tail).

% --------------------------------------------------------------------
%
% Incremental allocation & activation of constraints.
% Attachment code of closures to variables is generated
% by the compiler.
%
% States {passive(Term),inactive,triggered,active,removed,gc}
%
%

:- meta_predicate allocate_constraint(:,-,+,+).
%
allocate_constraint( Closure, Self, F, Args) :-
	empty_history( History),
	create_mutable( passive(Args), Mref),
	create_mutable( 0, Gref),
	create_mutable( History, Href),
	gen_id( Id),
	Self =.. [suspension,Id,Mref,Closure,Gref,Href,F|Args].

%
% activate_constraint( -, +, -).
%
% The transition gc->active should be rare
%
activate_constraint( Vars, Susp, Generation) :-
	arg( 2, Susp, Mref),
	get_mutable( State, Mref),
	update_mutable( active, Mref),
	( nonvar(Generation) ->			% aih
	    true
	;
	    arg( 4, Susp, Gref),
	    get_mutable( Gen, Gref),
	    Generation is Gen+1,
	    update_mutable( Generation, Gref)
	),
	( compound(State) ->			% passive/1
	    term_variables( State, Vs),
	    none_locked( Vs),
	    global_term_ref_1( Global),
	    Vars = [Global|Vs]
	; State==gc ->				% removed from all chains
	    Susp =.. [_,_,_,_,_,_,_|Args],
	    term_variables( Args, Vs),
	    global_term_ref_1( Global),
	    Vars = [Global|Vs]
	; State==removed ->			% the price for eager removal ...
	    Susp =.. [_,_,_,_,_,_,_|Args],
	    term_variables( Args, Vs),
	    global_term_ref_1( Global),
	    Vars = [Global|Vs]
	;
	    Vars = []
	).

%
% Combination of the prev. two
%
:- meta_predicate insert_constraint_internal(-,-,:,+,+).
%
insert_constraint_internal( [Global|Vars], Self, Closure, F, Args) :-
	term_variables( Args, Vars),
	none_locked( Vars),
	global_term_ref_1( Global),
	empty_history( History),
	create_mutable( active, Mref),
	create_mutable( 0, Gref),
	create_mutable( History, Href),
	gen_id( Id),
	Self =.. [suspension,Id,Mref,Closure,Gref,Href,F|Args].

:- meta_predicate insert_constraint_internal(-,-,?,:,+,+).
%
insert_constraint_internal( [Global|Vars], Self, Term, Closure, F, Args) :-
	term_variables( Term, Vars),
	none_locked( Vars),
	global_term_ref_1( Global),
	empty_history( History),
	create_mutable( active, Mref),
	create_mutable( 0, Gref),
	create_mutable( History, Href),
	gen_id( Id),
	Self =.. [suspension,Id,Mref,Closure,Gref,Href,F|Args].

gen_id( Id) :-
	incval( id, Id).
        /* no undo/1 in sicstus3.7
	( Id =:= 1 ->				% first time called
	    undo( setval(id,0))
	;
	    true
	).
	*/

%
% Eager removal from all chains.
%
remove_constraint_internal( Susp, Agenda) :-
	arg( 2, Susp, Mref),
	get_mutable( State, Mref),
	update_mutable( removed, Mref),		% mark in any case
	( compound(State) ->			% passive/1
	    Agenda = []
	; State==removed ->
	    Agenda = []
	; State==triggered ->
	    Agenda = []
	;
            Susp =.. [_,_,_,_,_,_,_|Args],
	    term_variables( Args, Vars),
	    global_term_ref_1( Global),
	    Agenda = [Global|Vars]
	).

%
% Protect the goal against any binding
% or attachment of constraints. The latter is
% via the notify_constrained/1 convention.
%
lock( T) :- var(T), put_atts( T, locked).
lock( T) :- nonvar( T),
	functor( T, _, N),
	lock_arg( N, T).

lock_arg( 0, _) :- !.
lock_arg( 1, T) :- !, arg( 1, T, A), lock( A).
lock_arg( 2, T) :- !, arg( 1, T, A), lock( A), arg( 2, T, B), lock( B).
lock_arg( N, T) :-
	arg( N, T, A),
	lock( A),
	M is N-1,
	lock_arg( M, T).

unlock( T) :- var(T), put_atts( T, -locked).
unlock( T) :- nonvar( T),
	functor( T, _, N),
	unlock_arg( N, T).

unlock_arg( 0, _) :- !.
unlock_arg( 1, T) :- !, arg( 1, T, A), unlock( A).
unlock_arg( 2, T) :- !, arg( 1, T, A), unlock( A), arg( 2, T, B), unlock( B).
unlock_arg( N, T) :-
	arg( N, T, A),
	unlock( A),
	M is N-1,
	unlock_arg( M, T).

verify_attributes( X, Y, []) :-
	get_atts( X, locked),
	!,
	var(Y),
	get_atts( Y, -locked),
	put_atts( Y,  locked).
verify_attributes( _, _, []).

none_locked( []).
none_locked( [V|Vs]) :-
	not_locked( V),
	none_locked( Vs).

not_locked( V) :- var( V), get_atts( V, -locked).
not_locked( V) :- nonvar( V).

% -------------------------- access to constraints ------------------

%
% Try a list of candidates. V may be nonvar but
% bound to a term with variables in it.
%
via( L, V) :-
	member( X, L),
	var( X),
	!,
	V = X.
via( L, V) :-
	compound( L),
	nonground( L, V),
	!.
via( _, V) :-
	global_term_ref_1( V).

%
% specialization(s)
%
via_1( X, V) :- var(X), !, X=V.
via_1( T, V) :- compound(T), nonground( T, V), !.
via_1( _, V) :- global_term_ref_1( V).

via_2( X, _, V) :- var(X), !, X=V.
via_2( _, Y, V) :- var(Y), !, Y=V.
via_2( T, _, V) :- compound(T), nonground( T, V), !.
via_2( _, T, V) :- compound(T), nonground( T, V), !.
via_2( _, _, V) :- global_term_ref_1( V).

via_3( X, _, _, V) :- var(X), !, X=V.
via_3( _, Y, _, V) :- var(Y), !, Y=V.
via_3( _, _, Z, V) :- var(Z), !, Z=V.
via_3( T, _, _, V) :- compound(T), nonground( T, V), !.
via_3( _, T, _, V) :- compound(T), nonground( T, V), !.
via_3( _, _, T, V) :- compound(T), nonground( T, V), !.
via_3( _, _, _, V) :- global_term_ref_1( V).


%
% The second arg is a witness.
% The formulation with term_variables/2 is
% cycle safe, but it finds a list of all vars.
% We need only one, and no list in particular.
%
nonground( Term, V) :-
	term_variables( Term, Vs),
	Vs = [V|_].

/*
nonground( Term, V) :- var( Term), V=Term.
nonground( Term, V) :- compound( Term),
	functor( Term, _, N),
	nonground( N, Term, V).

%
% assert: N > 0
%
nonground( 1, Term, V) :- !,
	arg( 1, Term, Arg),
	nonground( Arg, V).
nonground( 2, Term, V) :- !,
	arg( 2, Term, Arg2),
	( nonground( Arg2, V) ->
	    true
	;
	    arg( 1, Term, Arg1),
	    nonground( Arg1, V)
	).
nonground( N, Term, V) :-
	arg( N, Term, Arg),
	( nonground( Arg, V) ->
	    true
	;
	    M is N-1,
	    nonground( M, Term, V)
	).
*/

constraint_generation( Susp, State, Generation) :-
	arg( 2, Susp, Mref),
	get_mutable( State, Mref),
	arg( 4, Susp, Gref),
	get_mutable( Generation, Gref). 	% not incremented meanwhile

change_state( Susp, State) :-
	arg( 2, Susp, Mref),
	update_mutable( State, Mref).

:- meta_predicate expose(-,+,+,+,:).
%
expose_active( Ref, Head, Tid, Heads, Continuation) :-
	get_exposed( Ref),
	get_mutable( Exposed, Ref),
	update_mutable( [active(Head,Tid,Heads,Continuation)|Exposed], Ref).

expose_passive( Ref, Heads) :-
	get_exposed( Ref),
	get_mutable( Exposed, Ref),
	update_mutable( [passive(Heads)|Exposed], Ref).

de_expose( Ref) :-
	get_mutable( [_|Exposed], Ref),
	update_mutable( Exposed, Ref).

%
% Prefer passive over active (cheaper to deal with).
%
is_exposed( Constraint, Suspension, Continuation) :-
	get_exposed( Ref),
	get_mutable( Exposed, Ref),
	is_exposed( Exposed, Constraint, Suspension, Continuation).

is_exposed( [E|Es], Constraint, Suspension, Continuation) :-
	is_exposed( E, Constraint, Suspension, Continuation, Es).

is_exposed( active(Head,Susp,Heads,Cont), Constraint, Suspension, Continuation, Es) :-
	( member( C#Suspension, Heads),
	  Constraint == C ->
	    Continuation = true
	; Constraint == Head ->
	    ( is_exposed( Es, Constraint, Suspension, true) -> % prefer
		Continuation = true
	    ;
		Continuation = Cont,
		Suspension = Susp
	    )
	;
	    is_exposed( Es, Constraint, Suspension, Continuation)
	).
is_exposed( passive(Heads), Constraint, Suspension, Continuation, Es) :-
	( member( C#Suspension, Heads),
	  Constraint == C ->
	    Continuation = true
	;
	    is_exposed( Es, Constraint, Suspension, Continuation)
	).

get_exposed( Ref) :-
	global_term_ref_1( Global),
	( get_atts( Global, exposed(Ref)) ->
	    true
	;
	    create_mutable( [], Ref),
	    put_atts( Global, exposed(Ref))
	).

get_dbg_state( Ref) :-
	global_term_ref_1( Global),
	( get_atts( Global, dbg_state(Ref)) ->
	    true
	;
	    create_mutable( [], Ref),
	    put_atts( Global, dbg_state(Ref))
	).

% ------------------- abstract data type for propagation rules -------------

empty_history( E) :- empty_assoc( E).

%
% assert: constraints/tuples are comparable directly
%
novel_production( Self, Tuple) :-
	arg( 5, Self, Ref),
	get_mutable( History, Ref),
	( get_assoc( Tuple, History, _) ->
	    fail
	;
	    true
	).

%
% Not folded with novel_production/2 because guard checking
% goes in between the two calls.
%
extend_history( Self, Tuple) :-
	arg( 5, Self, Ref),
	get_mutable( History, Ref),
	put_assoc( Tuple, History, x, NewHistory),
	update_mutable( NewHistory, Ref).

:- load_foreign_resource(library(system(chr))).

end_of_file.