% ----------------------------------------------------------------------
% BEGIN LICENSE BLOCK
% Version: CMPL 1.1
%
% The contents of this file are subject to the Cisco-style Mozilla Public
% License Version 1.1 (the "License"); you may not use this file except
% in compliance with the License.  You may obtain a copy of the License
% at www.eclipse-clp.org/license.
% 
% Software distributed under the License is distributed on an "AS IS"
% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
% the License for the specific language governing rights and limitations
% under the License. 
% 
% The Original Code is  The ECLiPSe Constraint Logic Programming System. 
% The Initial Developer of the Original Code is  Cisco Systems, Inc. 
% Portions created by the Initial Developer are
% Copyright (C) 1992-2006 Cisco Systems, Inc.  All Rights Reserved.
% 
% Contributor(s): ECRC GmbH
% Contributor(s): IC-Parc, Imperal College London
% 
% END LICENSE BLOCK
%
% System:	ECLiPSe Constraint Logic Programming System
% Version:	$Id: meta.pl,v 1.10 2016/07/28 03:34:35 jschimpf Exp $
% ----------------------------------------------------------------------

%
% SEPIA PROLOG KERNEL MODULE
%
% IDENTIFICATION:	meta.pl, part of module(sepia_kernel)
%
% AUTHOR:		Micha Meier
%
% CONTENTS:		Basic metaterm handling
%

:- pragma(nodebug).
:- pragma(noskip).

:- export
	copy_term/2,
	copy_term_vars/3,
	delayed_goals/2,
	suspensions/2,
	delayed_goals_number/2,
	instance/2,
	compare_instances/3,
	meta_attribute/2,
	get_var_bounds/3,
	set_var_bounds/3,
	not_unify/2,
	variant/2.

:- export			% export tool bodies and handlers
	unify_attributes/2,
	test_unify_handler/1.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Generic metaterm stuff, meta transformations, multiple extensions
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%		MULTIPLE EXTENSIONS
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Declaring a new extension
%

:- tool(meta_attribute/2, meta_attribute_body/3).

% The name of the following records is the handler type name.
% The record entries are structures of the form
%	t(AttributeIndex, AttributeName, HandlerType, HandlerPred, Module)
% where HandlerPred is Name/Arity.

:- local_record(pre_unify).
:- local_record(unify).
:- local_record(test_unify).
:- local_record(compare_instances).
:- local_record(copy_term).
:- local_record(print).
:- local_record(get_bounds).
:- local_record(set_bounds).
:- local_record(suspensions).
:- local_record(delayed_goals).
:- local_record(delayed_goals_number).
:- local_record(suspension_lists).


meta_attribute_body(Name, List, Module) :-
    check_atom(Name),
    meta_index(+Name, Index),
    ( Name == suspend, Index == 1 ->
	% The suspend handlers are handcoded below to avoid use of the
	% compiler during initial booting
	check_handlers(List, List1, Name, Module),
	record_handlers(Index, Name, List1, Module)
    ;
	check_handlers(List, List1, Name, Module),
	record_handlers(Index, Name, List1, Module),
	recompile_system_handlers
    ),
    !.
meta_attribute_body(Name, List, Module) :-
    bip_error(meta_attribute(Name, List), Module).


% can fail with bip_error
:- mode check_handlers(?,-,+,+).
check_handlers(L, _, _, _) :- var(L), !,
    set_bip_error(4).
check_handlers([], [], _, _) :- !.
check_handlers([Decl|List], Decls, AttrName, Module) :- !,
    check_functor(Decl, (:), 2),
    Decl = H:P,
    ( is_meta_event(H, _) ->
	check_predspec(P),
	( P == true/0 ->
	    true
	;
	    P = _/Arity,
	    once is_meta_event(H, Arity),
	    ( get_flag(P, defined, on)@Module ->
		get_flag(P, visibility, Vis)@Module,
		( Vis == local ->
		    (export P)@Module
		; Vis == imported ->
		    get_flag(P, definition_module, DM)@Module,
		    (reexport P from DM)@Module
		;
		    true
		)
	    ;
		% require handler to be defined already
		set_bip_error(60)
	    )
	),
	Decls = [Decl|Decls1],
	check_handlers(List, Decls1, AttrName, Module)

    ; H == suspension_lists ->
	check_proper_list(P),
	( foreach(Spec,P), foreach(OutSpec,OutDecl), param(AttrName,Module) do
	    normalise_susp_list_spec(Spec, OutSpec, AttrName, Module)
	),
	Decls = [H:OutDecl|Decls1],
	check_handlers(List, Decls1, AttrName, Module)
    ;
	set_bip_error(6)
    ).
check_handlers(_, _, _, _) :-
    set_bip_error(5).


record_handlers(_, _, [], _).
record_handlers(Index, Name, [H:P|List], Module) :-
    (recordedchk(H, t(Index, _, _, _, _), Ref) ->
	erase(Ref)
    ;
	true
    ),
    ( P == true/0 ->
	true	% remove the handler
    ;
	recordz(H, t(Index, Name, H, P, Module))
    ),
    record_handlers(Index, Name, List, Module).


% Check and normalise a single suspension_lists declaration:
% ( atom | atom:(atom|posint|list(atom|posint)) )  ==>  atom:list(posint)
% can fail with bip_error
:- mode normalise_susp_list_spec(?,-,+,+).
normalise_susp_list_spec(Spec, _, _, _) :- var(Spec), !,
	set_bip_error(4).
normalise_susp_list_spec(Name, Name:[Slot], AttrName, Module) :- atom(Name), !,
	lookup_slot_number(Name, Slot, AttrName, Module).
normalise_susp_list_spec(Name:SlotSpecs, NameSlots, AttrName, Module) ?- !,
	check_atom(Name),
	NameSlots = Name:Slots,
	( atom(SlotSpecs) ->
	    Slots = [Slot], lookup_slot_number(SlotSpecs, Slot, AttrName, Module)
	; integer(SlotSpecs) ->
	    check_integer_ge(SlotSpecs, 1),
	    Slots = [SlotSpecs]
	;
	    check_proper_list(SlotSpecs),
	    ( foreach(SlotSpec,SlotSpecs), foreach(Slot1,Slots), param(AttrName,Module) do
		( atom(SlotSpec) ->
		    lookup_slot_number(SlotSpec, Slot1, AttrName, Module)
		;
		    check_integer_ge(SlotSpec, 1), Slot1=SlotSpec
		)
	    )
	).
normalise_susp_list_spec(_, _, _, _) :-
	set_bip_error(5).

    % can fail with bip_error
    lookup_slot_number(Name, Slot, AttrName, Module) :-
	( tr_of(no_macro_expansion(Name of AttrName), Slot, Module), integer(Slot) ->
	    true
	;
	    set_bip_error(6)
	).


% remove all calls to handlers in the erased module
erase_module_attribute_handlers(suspend) :- !.
erase_module_attribute_handlers(Module) :-
    findall(H, (
	    meta_event(H, _),
	    recorded(H, t(_, _, _, _, Module), Ref),
	    erase(Ref)
	), Erased),
    ( Erased = [_|_] ->
	recompile_system_handlers
    ;
	true
    ).


is_meta_event(Var, _) :-
    var(Var),
    !,
    set_bip_error(4).
is_meta_event(Var, _) :-
    not atom(Var),
    !,
    set_bip_error(5).
is_meta_event(H, A) :-
    meta_event(H, A), !.
is_meta_event(_, _) :-
    set_bip_error(6).

meta_event(pre_unify, 2).
meta_event(pre_unify, 3).
meta_event(unify, 2).
meta_event(unify, 3).
meta_event(test_unify, 2).
meta_event(compare_instances, 3).
meta_event(copy_term, 2).
meta_event(delayed_goals, 3).
meta_event(suspensions, 3).
meta_event(delayed_goals_number, 2).
meta_event(get_bounds, 3).
meta_event(set_bounds, 3).
meta_event(print, 2).


% lookup_suspension_list(?AttrName, +SuspName, -Slots, +Module) is semidet
lookup_suspension_list(AttrName, SuspName, Slots, _Module) :-
	atom(AttrName),
	% We know the attribute name. If there was a declaration, use it.
	( recordedchk(suspension_lists, t(_, AttrName, _, Specs, _)) ->
	    memberchk(SuspName:Slots, Specs)
	;
	    % No declaration: For backward compatibility, if a like-named
	    % structure is visible, allow any of its field names.
	    visible_struct(AttrName, ProtoStruct, AttrName, _Scope), % semidet
	    struct_lookup_index(ProtoStruct, SuspName, Slot, AttrName),
	    integer(Slot), Slots = [Slot]
	).
lookup_suspension_list(AttrNameFound, SuspName, Slots, Module) :-
	var(AttrNameFound),
	% No attribute name given.
	% Search those attributes for which a like-named structure is visible.
	recorded_list(suspension_lists, AttrSusps),
	(
	    foreach(t(_,AttrName,_,Specs,_),AttrSusps),
	    param(SuspName,Module,AttrNameFound,Slots)
	do
	    (
		visible_struct(AttrName, _ProtoStruct, Module, _Scope),
		memberchk(SuspName:Slots0, Specs)
	    ->
		( AttrNameFound = AttrName -> Slots = Slots0 ;
		    printf(warning_output,
			"WARNING: Ignoring ambiguous suspension list name '%w'%n"
			"WARNING:    defined in attributes %w and %w.%n",
			[SuspName,AttrNameFound,AttrName]),
		    fail
		)
	    ;
		true
	    )
	),
	( nonvar(Slots) ->
	    true
	;
	    % No matching declaration.  For backward compatibility,
	    % try any field of structures that are named like attributes.
	    meta_attributes(Metas),
	    (
		foreach([AttrName|_],Metas),
		param(SuspName,Module,AttrNameFound,Slots)
	    do
		(
		    visible_struct(AttrName, ProtoStruct, Module, _Scope),
		    struct_lookup_index(ProtoStruct, SuspName, Slot, Module),
		    integer(Slot)
		->
		    ( AttrNameFound = AttrName -> Slots = [Slot] ;
			printf(warning_output,
			    "WARNING: Ignoring ambiguous suspension list name '%w'%n"
			    "WARNING:    defined in attributes %w and %w.%n",
			    [SuspName,AttrNameFound,AttrName]),
			fail
		    )
		;
		    true
		)
	    ),
	    nonvar(Slots)
	).


recompile_system_handlers :-
    recompile_unify_handler,
    recompile_pre_unify_handler,
    recompile_test_unify_handler,
    recompile_compare_instances_handler,
    recompile_copy_term_handler,
    recompile_delayed_goals_handler,
    recompile_suspensions_handler,
    recompile_delayed_goals_number_handler,
    recompile_get_bounds_handler,
    recompile_set_bounds_handler,
    recompile_print_handler.

/*
 *	The handlers have the format
 *		pre_unify_attributes(AttrVar, Term, Pair) :-
 *		    pre_handler1(AttrVar, Term, Goals1),
 *		    ....
 *		    do_meta_bind(Pair, Term),
 *		    call_list(Goals1),
 *		    ....
 *		    
 *		unify_attributes(Term, meta(Attr1, ...)) :-
 *		    post_handler1(Term, Attr1),
 *		    ...
 *	If there are no pre_unify handlers, their part is omitted.
 */

%------------------------------
:- mode unify_attributes(?,++).
unify_attributes(Term, Meta) :-
	arg(1, Meta, SuspAttr),
    	suspend:unify_suspend(Term , SuspAttr).

recompile_unify_handler :-
    collect_local_handlers(unify, List),
    local_unify_handlers(List, Meta, Term, SuspAttr, Body),
    compile_term((unify_attributes(Term, Meta) :- arg(1,Meta,SuspAttr),Body), [debug:off]).

local_unify_handlers([], _, _, _, untraced_true).
local_unify_handlers([t(I, _, _, N/A, M)], Meta, Term, SuspAttr, Body) :-
    !,
    ( I = 1 ->
	Attr = SuspAttr, Body = M:Goal
    ;
	Body = (arg(I,Meta,Attr), M:Goal)
    ),
    ( A = 3 ->
	Goal =.. [N, Term, Attr, SuspAttr]
    ;
	Goal =.. [N, Term, Attr]
    ).
local_unify_handlers([t(I, _, _, N/A, M)|List], Meta, Term, SuspAttr, Body) :-
    ( I = 1 ->
	Attr = SuspAttr, Body = (M:Goal, NewBody)
    ;
	Body = (arg(I,Meta,Attr), M:Goal, NewBody)
    ),
    ( A = 3 ->
	Goal =.. [N, Term, Attr, SuspAttr]
    ;
	Goal =.. [N, Term, Attr]
    ),
    local_unify_handlers(List, Meta, Term, SuspAttr, NewBody).


%------------------------------
pre_unify_attributes(_AttrVar, _Term, _Pair).

recompile_pre_unify_handler :-
    collect_local_handlers(pre_unify, PreList),
    (PreList = [] ->
	compile_term((pre_unify_attributes(_,_,_)), [debug:off]),
	set_default_error_handler(11, unify_handler/1),
	set_error_handler(11, unify_handler/1)
    ;
	local_pre_unify_handlers(PreList, AttrVar, Term, Pair, Body, Middle, Middle),
%	writeclause((pre_unify_attributes(AttrVar, Term, Pair) :- Body)),
	compile_term((pre_unify_attributes(AttrVar, Term, Pair) :- Body), [debug:off]),
	set_default_error_handler(11, pre_unify_handler/1),
	set_error_handler(11, pre_unify_handler/1)
    ).

undo_meta_bindings([], []).
undo_meta_bindings([Pair|List], [p(AttrVar, Term, Pair)|PList]) :-
    Pair = [Term|_],	% save the binding value in Term
    undo_meta_bind(Pair, AttrVar),
    undo_meta_bindings(List, PList).

local_pre_unify_handlers([t(_, _, _, N/A, M)|Hs], AttrVar, Term, Pair, Front, Middle, Back) :-
    functor(Goal, N, A),
    Goal =.. [N, AttrVar, Term|OptionalArg],
    Front = (M:Goal, Front1),
    ( Hs==[] ->
	Front1 = (do_meta_bind(Pair, Term), Middle),
	( OptionalArg=[], Back = true
	; OptionalArg=[Gs], Back = sepia_kernel:call_list(Gs,M)
	)
    ;
	( OptionalArg=[], Back = Back1
	; OptionalArg=[Gs], Back = (sepia_kernel:call_list(Gs,M), Back1)
	),
	local_pre_unify_handlers(Hs, AttrVar, Term, Pair, Front1, Middle, Back1)
    ).


%------------------------------
:- mode test_unify_attributes(?, ++).
test_unify_attributes(_Term, _Attr).

recompile_test_unify_handler :-
    get_sys_flag(14, I),	% meta_arity
    functor(Attr, meta, I),
    collect_local_handlers(test_unify, List),
    local_test_unify_handlers(List, Attr, Term, Body),
    compile_term((test_unify_attributes(Term, Attr) :- Body), [debug:off]).

local_test_unify_handlers([], _, _, untraced_true).
local_test_unify_handlers([t(I, _, _, N/_, M)], Attr, Term, M:Goal) :-
    !,
    arg(I, Attr, LA),
    Goal =.. [N, Term, LA].
local_test_unify_handlers([t(I, _, _, N/_, M)|List], Attr, Term, Body) :-
    arg(I, Attr, LA),
    Goal =.. [N, Term, LA],
    Body = (M:Goal, NewBody),
    local_test_unify_handlers(List, Attr, Term, NewBody).

%------------------------------
:- mode compare_instances_attributes(?, ?, ?).
compare_instances_attributes(Res, _TermL, _TermR) :-
	% one or both of TermL, TermR are attributed variables!
	x_res(=, Res).

recompile_compare_instances_handler :-
    collect_local_handlers(compare_instances, List),
    local_compare_instances_handlers(List, Res, TermL, TermR, Body, _),
    compile_term((compare_instances_attributes(Res, TermL, TermR) :- Body), [debug:off]).

local_compare_instances_handlers([t(_, _, _, N/_, M)|List], Res, TermL, TermR,
	Body, ResL) :-
    Goal =.. [N, R, TermL, TermR],
    Body = (M:Goal, NewBody),
    (List = [] ->
	(var(ResL) ->
	    NewBody = (Res is x_res(R))
	;
	    NewBody = (Res is x_res(R) /\ ResL)
	)
    ;
	(var(ResL) ->
	    ResR = x_res(R)
	;
	    ResR = x_res(R) /\ ResL
	),
	local_compare_instances_handlers(List, Res, TermL, TermR, NewBody, ResR)
    ).
local_compare_instances_handlers([], RR, _, _, true, _) :-
    x_res(=, RR).

%------------------------------
:- mode copy_term_attributes(?, ?).
copy_term_attributes(_Meta, _Term).

recompile_copy_term_handler :-
	collect_local_handlers(copy_term, List),
	local_copy_term_handlers(List, Meta, Term, Body),
	compile_term((copy_term_attributes(Meta, Term) :- Body), [debug:off]).

    local_copy_term_handlers([t(_, _, _, N/_, M)|List], Meta, Term, Body) :-
	Goal =.. [N, Meta, Term],
	(List = [] ->
	    Body = M:Goal
	;
	    Body = (M:Goal, NewBody),
	    local_copy_term_handlers(List, Meta, Term, NewBody)
	).
    local_copy_term_handlers([], _, _, true).

%------------------------------
% Create a handler that computes the minimum range from all bounds handlers.
% The result is always two floats, although the individual handlers may
% return integers.
% The handlers are only called if the attribute exists!

get_meta_bounds(_Meta, Lower, Upper) ?-
	Lower = -1.0Inf, Upper = 1.0Inf.

recompile_get_bounds_handler :-
	collect_local_handlers(get_bounds, List),
	local_get_bounds_handlers(List, Meta, -1.0Inf, 1.0Inf, Lower, Upper, Body),
	compile_term((get_meta_bounds(Meta, Lower, Upper) ?- Body), [debug:off]).

    local_get_bounds_handlers([], _Meta, L0, U0, L, U, (L=L0,U=U0)).
    local_get_bounds_handlers([t(AttrSlot, _, _, N/_, M)|List], Meta, L0, U0, L, U, Body) :-
	add_attribute(Meta, Attr, AttrSlot),
	Goal =.. [N, Meta, L1, U1],
	Goal1 = (nonvar(Attr) -> M:Goal,max(L0,L1,L2),min(U0,U1,U2) ; L2=L0,U2=U0),
	(List = [] ->
	    Body = Goal1,
	    U2=U, L2=L
	;
	    Body = (Goal1, NewBody),
	    local_get_bounds_handlers(List, Meta, L2, U2, L, U, NewBody)
	).

%------------------------------
set_meta_bounds(_Meta, _Lwb, _Upb).

recompile_set_bounds_handler :-
	collect_local_handlers(set_bounds, List),
	local_set_bounds_handlers(List, Meta, Lwb, Upb, Body),
	compile_term((set_meta_bounds(Meta, Lwb, Upb) ?- Body), [debug:off]).

    :- mode local_set_bounds_handlers(+,?,?,?,-).
    local_set_bounds_handlers([], _, _, _, true).
    local_set_bounds_handlers([t(AttrSlot, _, _, N/_, M)|List], Meta, Lwb, Upb, Body) :-
	add_attribute(Meta, Attr, AttrSlot),
	Goal =.. [N, Meta, Lwb, Upb],
	Goal1 = (nonvar(Attr) -> M:Goal ; true),
	(List = [] ->
	    Body = Goal1
	;
	    Body = (Goal1, NewBody),
	    local_set_bounds_handlers(List, Meta, Lwb, Upb, NewBody)
	).

%------------------------------
% Obsolete delayed_goals handlers
% (modified to work as well on top of new suspensions-handler)
:- mode delayed_goals_attributes(?, ?, ?).
delayed_goals_attributes(Meta, G, G0) :-
	suspend:suspensions_suspend(Meta, ListOfSuspLists, []),
	concat_live_suspensions(ListOfSuspLists, Susps, []),
	suspensions_to_goals(Susps, G, G0).

recompile_delayed_goals_handler :-
    collect_local_handlers(suspensions, ListSH),	% new
    collect_local_handlers(delayed_goals, ListDGH),	% old
    append(ListSH, ListDGH, List0),
    sort(1 /*index of t*/, <, List0, List), % keep only SH if both are there
    local_delayed_goals_handlers(List, Meta, G, G0, Body),
    compile_term((delayed_goals_attributes(Meta, G, G0) :- Body), [debug:off]).

local_delayed_goals_handlers([t(_, _, HandlerType, N/_, M)|List], Meta, G, G0, Body) :-
    ( HandlerType == delayed_goals ->
	HGoal =.. [N, Meta, G, G1], Goal = M:HGoal
    ;
	HGoal =.. [N, Meta, ListOfSuspLists, []],
	Goal = (
	    M:HGoal,
	    concat_live_suspensions(ListOfSuspLists, Susps, []),
	    suspensions_to_goals(Susps, G, G1)
	)
    ),
    (List = [] ->
	Body = Goal,
	G0 = G1
    ;
	Body = (Goal, NewBody),
	local_delayed_goals_handlers(List, Meta, G1, G0, NewBody)
    ).
local_delayed_goals_handlers([], _, G, G, true).

%------------------------------
:- mode suspensions_attributes(?, ?, ?).
suspensions_attributes(Meta, S, S0) :-
	suspend:suspensions_suspend(Meta, S, S0).

recompile_suspensions_handler :-
    collect_local_handlers(suspensions, List),
    local_suspensions_handlers(List, Meta, S, S0, Body),
    compile_term((suspensions_attributes(Meta, S, S0) :- Body), [debug:off]).

local_suspensions_handlers([t(_, _, _, N/_, M)|List], Meta, S, S0, Body) :-
    Goal =.. [N, Meta, S, S1],
    (List = [] ->
	Body = M:Goal,
	S0 = S1
    ;
	Body = (M:Goal, NewBody),
	local_suspensions_handlers(List, Meta, S1, S0, NewBody)
    ).
local_suspensions_handlers([], _, S, S, true).

%------------------------------
:- mode delayed_goals_number_attributes(?, ?).
delayed_goals_number_attributes(Meta, NG) :-
	suspend:delayed_goals_number_suspend(Meta, NG).

recompile_delayed_goals_number_handler :-
    collect_local_handlers(delayed_goals_number, List),
    local_delayed_goals_number_handlers(List, Meta, NG, Body, 0),
    compile_term((delayed_goals_number_attributes(Meta, NG) :- Body), [debug:off]).

local_delayed_goals_number_handlers([t(_, _, _, N/_, M)|List], Meta, NG, Body, NG0) :-
    Goal =.. [N, Meta, NG1],
    (List = [] ->
	( NG0 == 0 ->				% only one
	    Body = M:Goal,
	    NG = NG1
	;
	    Body = (M:Goal, NG is NG0 + NG1)
	)
    ;
	Body = (M:Goal, NewBody),
	( NG0 == 0 ->				% first
	    NG2 = NG1
	;
	    NG2 = NG0 + NG1
	),
	local_delayed_goals_number_handlers(List, Meta, NG, NewBody, NG2)
    ).
local_delayed_goals_number_handlers([], _, 0, true, _).

%------------------------------
print_attribute(_, _) :- fail.

recompile_print_handler :-
    collect_local_handlers(print, List),
    local_print_handlers(List, Var, OL, Body),
    (Body == (_ = []) ->
	compile_term((print_attribute(_, _) :- fail), [debug:off])
    ;
	compile_term((print_attribute(Var, OL) :- Body), [debug:off])
    ).

local_print_handlers([], _, L, L = []).
local_print_handlers([t(_, Name, _, N/_, M)|List], Var, L,
		((M:Goal -> L = [Name:Out|L1]; L = L1), Body1)) :-
    Goal =.. [N, Var, Out],
    local_print_handlers(List, Var, L1, Body1).

%------------------------------
collect_local_handlers(Key, List) :-
    get_sys_flag(14, I),	% meta_arity
    collect_local_handlers(I, Key, List).

collect_local_handlers(I, Key, List) :-
    I > 0,
    !,
    I1 is I - 1,
    (Cont = t(I, _, _, P, _),
    recorded(Key, Cont),
    P \= true/0 ->
	List = [Cont|NewList],
	collect_local_handlers(I1, Key, NewList)
    ;
	collect_local_handlers(I1, Key, List)
    ).
collect_local_handlers(_, _, []).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Global handlers
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


%
%%%% unification %%%%
%

:- pragma(debug).
unify_handler([]) :- -?->
    wake.	% we want to trace this call (only)
unify_handler([[Term|Attr]|List]) :-
    -?->
    unify_attributes(Term, Attr),
    unify_handler(List).
:- pragma(nodebug).

:- export call_list/2.
call_list([],_M).
call_list([G|Gs],M) :-
    call(G)@M,
    call_list(Gs,M).

pre_unify_handler(List) :-
    undo_meta_bindings(List, NewList),
    pre_unify_pairs(NewList),
    unify_handler(List).

pre_unify_pairs([]).
pre_unify_pairs([p(Var, Term, Pair)|L]) :-
    pre_unify_attributes(Var, Term, Pair),
    pre_unify_pairs(L).



%
%%%% not_unify/2 %%%%
%
not_unify(X, Y) :-
    unify(X, Y, List),		% like =/2 with an explicit list
    test_unify_handler(List),
    !,
    fail.
not_unify(_, _).

test_unify_handler([]).
test_unify_handler([[Term|Attr]|List]) :-
    test_unify_attributes(Term, Attr),
    test_unify_handler(List).


%
%%%% variant/2 %%%%
%
variant(Term1, Term2) :-
    compare_instances(=, Term1, Term2, List),
    variant_handler(List).

    variant_handler([]).
    variant_handler([[TermL|TermR]|List]) :-
	compare_instances_attributes(3, TermL, TermR),
	variant_handler(List).

%
%%%% instance/2 %%%%
%
instance(Term1, Term2) :-
    compare_instances(Res, Term1, Term2, List),
    Res \== (>),
    instance_handler(List).

    instance_handler([]).
    instance_handler([[TermL|TermR]|List]) :-
	compare_instances_attributes(Res, TermL, TermR),
	Res >= 2,	% fail early if any L>R
	instance_handler(List).

%
%%%% compare_instances/3 %%%%
% The cases where the first arg is instantiated are handled
% specially because they may fail early.
%
compare_instances(=, Term1, Term2) ?- !,
    compare_instances(=, Term1, Term2, List),
    variant_handler(List).
compare_instances(<, Term1, Term2) ?- !,
    compare_instances(Res, Term1, Term2, List),
    x_res(Res, R),
    proper_instance_handler(R, List, 2).
compare_instances(>, Term1, Term2) ?- !,
    compare_instances(Res, Term2, Term1, List), % swap args
    x_res(Res, R),
    proper_instance_handler(R, List, 2).
compare_instances(Res, Term1, Term2) :-
    compare_instances(Res0, Term1, Term2, List),
    x_res(Res0, R0),
    comp_instances_handler(R0, List, R),
    x_res(Res, R).

    proper_instance_handler(R, [], R).
    proper_instance_handler(Res, [[TermL|TermR]|List], ResL) :-
	Res >= 2,	% fail early if any L>R
	compare_instances_attributes(Res1, TermL, TermR),
	Res2 is Res1 /\ Res,
	proper_instance_handler(Res2, List, ResL).

    comp_instances_handler(R, [], R).
    comp_instances_handler(R1, [[TermL|TermR]|List], R) :-
	compare_instances_attributes(R2, TermL, TermR),
	R3 is R1 /\ R2,
	R3 > 0,		% fail early if incomparable
	comp_instances_handler(R3, List, R).


%
%%%% copy_term/2 %%%%
%
copy_term(Term, Copy) :-
    copy_term(Term, Copy, List),
    copy_term_handler(List).

copy_term_vars(Vars, Term, Copy) :-
    copy_term_vars(Vars, Term, Copy, List),
    copy_term_handler(List).

copy_term_handler([]).
copy_term_handler([[Meta|Term]|List]) :-
    copy_term_attributes(Meta, Term),
    copy_term_handler(List).


%
%%%% retrieve current numeric range %%%%
%
get_var_bounds(X, L, U) :-
	free(X), !,
	L = -1.0Inf, U = 1.0Inf.
get_var_bounds(X, L, U) :-
	meta(X), !,
	get_meta_bounds(X, L, U).
get_var_bounds(X, L, U) :-
	breal(X), !,
	breal_bounds(X, L, U).
get_var_bounds(X, L, U) :-
	number(X), !,
	L is float(X), U = L.
get_var_bounds(X, L, U) :-
	error(5, get_var_bounds(X, L, U)).

set_var_bounds(X, _, _) :- free(X), !.
set_var_bounds(X, L, U) :- meta(X), !,
	set_meta_bounds(X, L, U).
set_var_bounds(X, L, U) :- number(X), !,
	L =< X, X =< U.
set_var_bounds(X, L, U) :-
	error(5, set_var_bounds(X, L, U)).


%
%%%% delayed_goals/2 %%%%
%
delayed_goals(Meta, Goals) :-
	meta(Meta),
	!,
	delayed_goals_attributes(Meta, Goals, []).
delayed_goals(_free_or_instantiated, []).


%
%%%% suspensions/2 %%%%
%
suspensions(Meta, Susps) :-
	meta(Meta),
	!,
	suspensions_attributes(Meta, ListOfSuspLists, []),
	( Susps == [] ->
	    % if just testing, we can fail early
	    concat_live_suspensions(ListOfSuspLists, [], [])
	;
	    concat_live_suspensions(ListOfSuspLists, Susps0, []),
	    sort(0, <, Susps0, Susps)	% remove duplicates
	).
suspensions(_free_or_instantiated, []).

    concat_live_suspensions([], Susps, Susps).
    concat_live_suspensions([SuspList|SuspLists], Susps, Susps0) :-
	filter_live_suspensions(SuspList, Susps, Susps1),
	concat_live_suspensions(SuspLists, Susps1, Susps0).

    filter_live_suspensions(Empty, Ls, Ls) :- var(Empty), !.
    filter_live_suspensions([], Ls, Ls).
    filter_live_suspensions([S|Ss], SLs, Ls) :-
	( is_suspension(S) -> SLs = [S|Ls0] ; SLs = Ls0 ),
	filter_live_suspensions(Ss, Ls0, Ls).



%
%%%% delayed_goals_number/2 %%%%
%
delayed_goals_number(Meta, N) :-
	meta(Meta),
	!,
	delayed_goals_number_attributes(Meta, N).
delayed_goals_number(X, N) :-
	var(X),
	!,
	N = 0.
delayed_goals_number(_, 1000000).


%
%%%% print %%%%
%
print_attributes(Attr, {Out}) :-
    print_attribute(Attr, L),
    list_to_attr(L, OT),
    (OT = _:Out ->
	true
    ;
	L = [_|_],
	Out = OT
    ).

list_to_attr([A], A) :- !.
list_to_attr([A|L], (A,B)) :-
    list_to_attr(L, B).

x_res(>, 1).
x_res(<, 2).
x_res(=, 3).

?- set_default_error_handler(11, unify_handler/1),
   set_error_handler(11, unify_handler/1).
   
:- skipped unify_attributes/2.
:- set_flag(unify_handler/1, invisible, on).

:- unskipped
	test_unify_attributes/2,
	compare_instances_attributes/3,
	copy_term_attributes/2,
	print_attributes/2,
	delayed_goals_attributes/3,
	delayed_goals_number_attributes/2,
	delayed_goals/2,
	delayed_goals_number/2,
	unify_handler/1,
	copy_term_handler/1,
	test_unify_handler/1.

:- untraceable
	unify_attributes/2,
	test_unify_attributes/2,
	compare_instances_attributes/3,
	copy_term_attributes/2,
	print_attribute/2,
	print_attributes/2,
	delayed_goals_attributes/3,
	delayed_goals_number_attributes/2,
	unify_handler/1,
	undo_meta_bindings/2,
	copy_term_handler/1,
	test_unify_handler/1.

