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

/*

   2 Mechanisms: trace+leash, debug+spy


   Debugger integration issue:

     We could use conditional spypoints of the Prolog debugger
     to get hooked, but then we depend a lot on it ...

     :- spypoint_condition( debug_event(E), P, chr:de(P,E)).

   Todo:

     -) module_wrap/3 for all terms (M as arg to debug_event)

     -) guard-fail = rule-delay reason?

*/

:- dynamic spy_rule/2.
:- dynamic spy_constraint/2.

:- initialization
	getval( debug, _) -> true ; setval( debug, off).
:- initialization
	retractall( spy_rule(_,_)).
:- initialization
	retractall( spy_constraint(_,_)).

chr_trace :-
	setval( debug, trace),
	what_is_on( informational).

chr_notrace :-
	setval( debug, off),
	what_is_on( informational).

chr_debug :-
	setval( debug, debug),
	what_is_on( informational).

chr_nodebug :-
	chr_notrace.

chr_spy( constraints(Cs)) :-
	parse_spy_constraints( Cs, L, []),
	member( N/A, L),
	assert( spy_constraint(N,A)),
	fail.
chr_spy( rules(Rs)) :-
	parse_spy_rules( Rs, L, []),
	member( Handler:Rule, L),
	assert( spy_rule(Rule,Handler)),
	fail.
chr_spy( _) :- getval( debug, trace), !.
chr_spy( _) :- chr_debug.

chr_nospy( rules(Rs)) :-
	parse_spy_rules( Rs, L1, []),
	member( Handler:Rule, L1),
	retract( spy_rule(Rule,Handler)),
	fail.
chr_nospy( constraints(Cs)) :-
	parse_spy_constraints( Cs, L, []),
	member( N/A, L),
	retract( spy_constraint(N,A)),
	fail.
chr_nospy( _).

parse_spy_constraints( C) --> {var(C)}, !, [ _ ].
parse_spy_constraints( (C,Cs)) -->
	parse_spy_constraints( C),
	parse_spy_constraints( Cs).
parse_spy_constraints( N) --> {atom(N)}, [ N/_ ].
parse_spy_constraints( N/A) --> {atom(N),integer(A),A>0}, [ N/A ].

parse_spy_rules( R) --> {var(R)}, !, [ _ ].
parse_spy_rules( (R,Rs)) --> !,
	parse_spy_rules( R),
	parse_spy_rules( Rs).
parse_spy_rules( H:R) --> !, [ H:R ].
parse_spy_rules( R) --> [ _:R ].		% any handler

chr_leash( Spec) :-
	nonvar( Spec),
	chr_leash( Spec, I),
	setval( leashing, I),
	what_is_leashed( informational).

chr_leash( none,    0) :- !.
chr_leash( off,     0) :- !.
chr_leash( all,    -1) :- !.
chr_leash( default, I) :- !, chr_leash( 0, I, [call,wake,apply,exit,fail], []).
chr_leash( L,	    I) :- chr_leash( 0, I, L, []), !.
chr_leash( X,	    I) :- chr_leash( 0, I, [X], []).

chr_leash( I, K) --> [call],   {J is I\/2'100000000}, chr_leash( J, K).
chr_leash( I, K) --> [wake],   {J is I\/2'010000000}, chr_leash( J, K).
chr_leash( I, K) --> [try],    {J is I\/2'001000000}, chr_leash( J, K).
chr_leash( I, K) --> [apply],  {J is I\/2'000100000}, chr_leash( J, K).
chr_leash( I, K) --> [exit],   {J is I\/2'000010000}, chr_leash( J, K).
chr_leash( I, K) --> [redo],   {J is I\/2'000001000}, chr_leash( J, K).
chr_leash( I, K) --> [fail],   {J is I\/2'000000100}, chr_leash( J, K).
%
chr_leash( I, K) --> [insert], {J is I\/2'000000010}, chr_leash( J, K).
chr_leash( I, K) --> [remove], {J is I\/2'000000001}, chr_leash( J, K).
chr_leash( I, I) --> [].

:- initialization
	chr_leash( default, I), setval( leashing, I).

debug_stop( call(S), L, Why) :-
	( L/\2'100000000 > 0 -> true
	; spypoint_susp( S, Why)
	).
debug_stop( wake(S), L, Why) :-
	( L/\2'010000000 > 0 -> true
	; spypoint_susp( S, Why)
	).
debug_stop( exit(S), L, Why) :-
	( L/\2'000010000 > 0 -> true
	; spypoint_susp( S, Why)
	).
debug_stop( redo(S), L, Why) :-
	( L/\2'000001000 > 0 -> true
	; spypoint_susp( S, Why)
	).
debug_stop( fail(S), L, Why) :-
	( L/\2'000000100 > 0 -> true
	; spypoint_susp( S, Why)
	).
%
debug_stop( insert(S), L, Why) :-
	( L/\2'000000010 > 0 -> true
	; spypoint_susp( S, Why)
	).
debug_stop( remove(S), L, Why) :-
	( L/\2'000000001 > 0 -> true
	; spypoint_susp( S, Why)
	).
%
debug_stop( try(H,R,_,Hs,_,_), L, Why)	 :-
	( L/\2'001000000 > 0 -> true
	; spy_rule(R,H) -> Why = r
	; spypoint_head( Hs, Why)
	).
debug_stop( apply(H,R,_,Hs,_,_), L, Why) :-
	( L/\2'000100000 > 0 -> true
	; spy_rule(R,H) -> Why = r
	; spypoint_head( Hs, Why)
	).

spypoint_susp( S, c) :-
	S =.. [suspension,_,_,_,_,_,N|Args],
	length( Args, A),
	spy_constraint( N, A).

spypoint_head( Hs, c) :-
	member( H, Hs),
	arg( 1, H, Term),
	functor( Term, N, A),
	spy_constraint( N, A).

debug_stop_reason( Why, _) :- nonvar( Why).
debug_stop_reason( Why, Event) :- var( Why),
	( debug_stop( Event, 0, Why) ->
	    true
	;
	    Why = ' '
	).

chr_debugging :-
	what_is_on( help),
	what_is_leashed( help),
	what_spypoints( help).

what_is_on( Type) :-
	getval( debug, Mode),
	print_message( Type, debug(Mode)).

what_is_leashed( Type) :-
	getval( leashing, Leash),
	findall( P, (chr_leash(0,K,[P],[]),K/\Leash>0), L),
	print_message( Type, leash(L)).

what_spypoints( Type) :-
	findall( rules(E), (spy_rule(R,H),(var(H)->E=R;E=H:R)), L0, L1),
	findall( constraints(E), (spy_constraint(N,A),(var(A)->E=N;E=N/A)), L1, []),
	sort( L0, Ls),
	print_message( Type, spypoints(Ls)).

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

debug_event( Event) :-
	getval( debug, State),
	( State == off ->
	    true
	;
	    debug_event( State, Event),
	    debug_stack( Event)
	).

debug_event( trace, Event) :-
	getval( leashing, L),
	( debug_stop( Event, L, SpyInd) ->
	    debug_stop_reason( SpyInd, Event),
	    debug_show( SpyInd, Event),
	    get_command( Cmd),
	    debug_do( Cmd, Event, trace)
	;
	    debug_stop_reason( SpyInd, Event),
	    debug_show( SpyInd, Event),
	    errnl
	).
debug_event( debug, Event) :-
	( debug_stop( Event, 0, SpyInd) ->
	    debug_show( SpyInd, Event),
	    get_command( Cmd),
	    debug_do( Cmd, Event, debug)
	;
	    true
	).
debug_event( skip(N,S), Event) :-
	stack_depth( M),
	( M =< N, member( Event, [exit(_),fail(_)]) ->
	    setval( debug, S),
	    debug_event( S, Event)
	;
	    true
	).
debug_event( off, _).

debug_stack( Event) :- Event = call(_), !, stack_push( Event).
debug_stack( Event) :- Event = wake(_), !, stack_push( Event).
%
debug_stack( Event) :- Event = apply(_,_,_,_,_,_), !,
	stack_pop,
	stack_push( Event).
%
debug_stack( exit(_)) :- !, stack_pop.
%
debug_stack( _).

stack_push( S) :-
	get_dbg_state( Ref),
	get_mutable( Stack, Ref),
	update_mutable( [S|Stack], Ref).

stack_pop :-
	get_dbg_state( Ref),
	get_mutable( [_|Stack], Ref),
	update_mutable( Stack, Ref).

stack_depth( Depth) :-
	get_dbg_state( Ref),
	get_mutable( Stack, Ref),
	length( Stack, Depth).

show_stack :-
	get_dbg_state( Ref),
	get_mutable( Stack, Ref),
	length( Stack, N),
	errwrite('Ancestors:'), errnl,
	show_stack( Stack, N), errnl.

show_stack( [],     _).
show_stack( [S|Ss], N) :-
	M is N-1,
	show_stack( Ss, M),
	Spy = ' ',
	( arg( 3, S, Hp) -> true ; Hp = '-' ),
	functor( S, Port, _),
	errformat( ' ~w ~|~t~d~4+ ~|~t~w~3+ ~|~p~t~7+', [Spy,N,Hp,Port]),
	debug_show_event( S),
	errnl.

debug_show( Spy, Event) :-
	functor( Event, Port, _),
	( arg( 3, Event, Hp) -> true ; Hp = '-' ),
	stack_depth( Depth),
	errformat( ' ~w ~|~t~d~4+ ~|~t~w~3+ ~|~p~t~7+', [Spy,Depth,Hp,Port]),
	debug_show_event( Event).

debug_show_event( call(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( wake(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( exit(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( redo(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( fail(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( remove(S)) :-
	debug_susp_term( S, Term), errtab( 1), errprint( Term).
debug_show_event( insert(C)) :-
	errtab( 1), errprint( C).
debug_show_event( try(Handler,Rule,_,Heads,_,_)) :-
	errformat( ' ~p:~p @ ', [Handler,Rule]),
	show_heads( Heads, 0, 0, _).
debug_show_event( apply(Handler,Rule,_,Heads,_,_)) :-
	errformat( ' ~p:~p @ ', [Handler,Rule]),
	show_heads( Heads, 0, 0, _).

debug_susp_term( S, Term#S) :-
	S =.. [suspension,_,_,_,_,_,F|Args],
	Term =.. [F|Args].

debug_do( 0'a, _, _) :- !, abort.
debug_do( 0'n, _, _) :- !, chr_notrace.
debug_do( 0'&, E, S) :- !, show_store( 0), debug_event( S, E).
debug_do( [0'&|_], E, S) :- !, show_store( 1), debug_event( S, E).
debug_do( 0'g, E, S) :- !, show_stack, debug_event( S, E).
debug_do( 0'., E, S) :-
	dbg_at_rule( E, _, _),
	!,
	show_rule( E),
	debug_event( S, E).
debug_do( 0'+, E, S) :- !,
	( dbg_at_rule( E, Handler, Rule) ->
	    chr_spy( rules( Handler:Rule))
	; dbg_at_constraint( E, N, A) ->
	    chr_spy( constraints( N/A))
	),
	debug_event( S, E).
debug_do( 0'-, E, S) :- !,
	( dbg_at_rule( E, Handler, Rule) ->
	    chr_nospy( rules( Handler:Rule))
	; dbg_at_constraint( E, N, A) ->
	    chr_nospy( constraints( N/A))
	),
	debug_event( S, E).
debug_do( 0'b, E, S) :- !,
	setval( debug, off),
	break,
	setval( debug, S),
	debug_event( S, E).
debug_do( 0'
	     , _, _) :- !, setval( debug, trace). % CR = creep
debug_do( 0'c, _, _) :- !, setval( debug, trace). % creep
debug_do( 0'l, _, _) :- !, setval( debug, debug). % leap
debug_do( 0's, E, S) :- chr_skip( E, S, _), !.	% skip
debug_do( [0's,N], E, S) :- chr_skip( E, S, N), !. % skip
debug_do( 0'<, E, S) :- !, set_pd(10), debug_event( S, E).
debug_do( [0'<,N], E, S) :- !, set_pd(N), debug_event( S, E).
debug_do( 0'=, E, S) :- !, chr_debugging, debug_event( S, E).
debug_do( 0'?, E, S) :- !, dbg_help, debug_event( S, E).
debug_do( 0'h, E, S) :- !, dbg_help, debug_event( S, E).
debug_do( _,   E, S) :-
	print_message( informational, wrong_option),
	debug_event( S, E).

chr_skip( E, S, K) :- E = exit(_), stack_depth( K), !, debug_event( S, E).
chr_skip( E, S, K) :- E = fail(_), stack_depth( K), !, debug_event( S, E).
chr_skip( _, S, K) :-
	stack_depth( Depth),
	( var(K) ->
	    N is Depth+1
	;
	    1 =< K, K =< Depth,
	    N = K
	),
	setval( debug, skip(N,S)).

dbg_at_rule( try(Handler,Rule,_,_,_,_),   Handler, Rule).
dbg_at_rule( apply(Handler,Rule,_,_,_,_), Handler, Rule).

dbg_at_constraint( E, N, A) :-
	dbg_at_constraint( E, S),
	S =.. [suspension,_,_,_,_,_,N|Args],
	length( Args, A).

dbg_at_constraint( call(S), S).
dbg_at_constraint( wake(S), S).
dbg_at_constraint( exit(S), S).
dbg_at_constraint( redo(S), S).
dbg_at_constraint( fail(S), S).
dbg_at_constraint( insert(S), S).
dbg_at_constraint( remove(S), S).

%
% numbervars binds variables ...
%
show_rule( Event) :-
	Event =.. [Which,Handler,Rule,_,Heads,Guard,Body],
	member( Which, [try,apply]),
	current_handler( Handler, _),
	!,
	show_rule( Rule, Heads, Guard, Body).
show_rule( _).

show_rule :-
	chrcmp:rule( _, _, Name, Heads, Guard, Body, _),
	numbervars( Heads/Name/Guard/Body, 0, _),
	show_rule( Name, Heads, Guard, Body),
	fail.
show_rule.

show_rule( Name, Heads, Guard, Body) :-
	errformat( '~n ~p @', [Name]),
	show_heads( Heads, 2, 2, Ident),
	( member( k(_,_), Heads) ->
	    errformat( ' <=>~n~n', [])
	;
	    errformat( ' ==>~n~n', [])
	),
	( Guard==true ->
	    show_body( Body, Ident)
	;
	    show_body( Guard, Ident), errnl,
	    errtab( Ident), errwrite( '|'), errnl,
	    show_body( Body, Ident)
	),
	errput( 0'.), errnl, errnl.

show_body( (A,B), Tab) :- !,
	show_body( A, Tab),
	errwrite( ','), errnl,
	show_body( B, Tab).
show_body( (A->B;C), Tab) :- !,
	errtab( Tab), errwrite( '('), errnl,
	NTab1 is Tab+2,
	NTab2 is Tab+5,
	show_body( A, NTab1),
	errwrite( '  ->'), errnl,
	show_body( B, NTab2), errnl,
	errtab( Tab), errwrite( ';'), errnl,
	show_body( C, NTab2), errnl,
	errtab( Tab), errwrite( ')').
show_body( (A->B), Tab) :- !,
	errtab( Tab), errwrite( '('), errnl,
	NTab1 is Tab+2,
	NTab2 is Tab+5,
	show_body( A, NTab1),
	errwrite( '  ->'), errnl,
	show_body( B, NTab2), errnl,
	errtab( Tab), errwrite( ')').
show_body( (A;B), Tab) :- !,
	errtab( Tab), errwrite( '('), errnl,
	NTab is Tab+5,
	show_body( A, NTab), errnl,
	errtab( Tab), errwrite( ';'), errnl,
	show_body( B, NTab), errnl,
	errtab( Tab), errwrite( ')').
show_body( A, Tab) :-
	errtab( Tab),
	errwriteq( A).

show_heads( [],     I, _, I).
show_heads( [H|Hs], I, D, If) :-
	arg( 1, H, C),
	arg( 2, H, T),
	( I>0 -> errnl ; true ),
	errtab( I), errprint( C#T),
	( Hs=[] ->
	    If = I
	; H=r(_,_), Hs=[k(_,_)|_] ->
	    errput(0' ), errput(0'\\ ), errput(0' ),
	    J is I+D,
	    show_heads( Hs, J, D, If)
	;
	    errput(0',), errput(0' ),
	    J is I+D,
	    show_heads( Hs, J, D, If)
	).

show_store( 0) :-
	errnl,
	global_term_ref_1( Global),
	find_constraint_internal( Global, Term, S, active, Module),
	module_wrap( Term, Module, Wrapped),
	errprint( Wrapped#S), errnl,
	fail.
show_store( 1) :-
	prolog_flag( debugger_print_options, Options),
	errnl,
	global_term_ref_1( Global),
	find_constraint_internal( Global, Term, S, State, Module),
	S =.. [suspension,Id,_,_Closure,Gref,Href|_],
	get_mutable( Generation, Gref),
	get_mutable( Hist, Href),
	assoc_to_list( Hist, History),
	module_wrap( Term, Module, Wrapped),
	errformat( '~|~t~p~5+ ~|~t~d~3+ ~|~p~t~10+ ~|~@~t~50+ ',
	  [Id,Generation,State,write_term(Wrapped,Options)]),
	show_history( History),
	errnl,
	fail.
show_store( _) :- errnl.

show_history( []).
show_history( [K-_|Hs]) :-
	errprint( K),
	( Hs==[] -> true ; errput(0',) ),
	show_history( Hs).

set_pd( N) :-
	prolog_flag( debugger_print_options, Old),
	( select( max_depth(_), Old, Rest) ->
	    true
	;
	    Rest = Old
	),
	( N < 0 -> D = 0 ; D = N ),
	prolog_flag( debugger_print_options, _, [max_depth(D)|Rest]).


dbg_help :-
	errnl,
	errwrite('CHR debugging options:'), errnl,
	errwrite('   <cr>   creep            c      creep'), errnl,
	errwrite('    l     leap			 '), errnl,
	errwrite('    s     skip             s <i>  skip i'), errnl,
	errwrite('    g     ancestors			'), errnl,
	errwrite('    &     constraints      & <i>  constraints (details)'), errnl,
	errwrite('    n     nodebug          =      debugging'), errnl,
	errwrite('    +     spy this			'), errnl,
	errwrite('    -     nospy this       .      show rule'), errnl,
	errwrite('    <     reset printdepth < <n>  set printdepth'), errnl,
	errwrite('    a     abort            b      break'), errnl,
	errwrite('    ?     help             h      help'), errnl,
	errnl.

errnl :- nl( user_error).

errput( X) :- put( user_error, X).

errtab( X) :- tab( user_error, X).

errwrite( X) :- write( user_error, X).

errwriteq( X) :- writeq( user_error, X).

errprint( X) :-
	prolog_flag( debugger_print_options, Options),
	write_term( user_error, X, Options).

errformat( F, A) :- format( user_error, F, A).

% ----------------------------------------------------------
%
% code from the Bips/trace.pl
%

get_command(Command) :-
	errwrite(' ? '),
	ttyflush,
	ttyget0(C1),
	get_command(C1, Command).

get_command(0'
	, 0'
	   ) :- !.
get_command(C1, Command) :-
	ttyget0(C2),
	get_args(C2, Args),
	(   Args = [] -> Command = C1
	;   Command = [C1|Args]
	).

get_args(0'
	 , []) :- !.
get_args(C1, [Arg|Args]) :-
	C1 >= 0'0, C1 =< 0'9, !,
	get_arg(C1, 0, Arg, C2),
	get_args(C2, Args).
get_args(0'-, [Arg|Args]) :- !,
	ttyget0(C2),
	get_arg(C2, 0, Arg1, C3),
	Arg is -Arg1,
	get_args(C3, Args).
get_args(_, Args) :-
	ttyget0(C2),
	get_args(C2, Args).

get_arg(C1, Arg0, Arg, C) :-
	C1 >= 0'0, C1 =< 0'9, !,
	Arg1 is Arg0*10 + C1 - 0'0,
	ttyget0(C2),
	get_arg(C2, Arg1, Arg, C).
get_arg(C1, Arg, Arg, C1).
