%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1998-2021. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
%% Purpose : User interface to the Erlang debugger/interpreter.

-module(i).

-export([help/0,ia/1,ia/2,ia/3,ia/4,iaa/1,iaa/2,
	 ib/2,ib/3,ib/4,ibd/2,ibe/2,iba/3,ibc/3,ic/0,ii/1,ii/2,
	 il/0,im/0,ini/1,ini/2,inq/1,ip/0,ipb/0,ipb/1,iq/1,
	 ir/0,ir/1,ir/2,ir/3,iv/0,ist/1]).

-import(io, [format/1,format/2]).
-import(lists, [sort/1,foreach/2]).

iv() ->
    Vsn = string:slice(filename:basename(code:lib_dir(debugger)), 9),
    list_to_atom(Vsn).

%% -------------------------------------------
%% Start a new graphical monitor.
%% A monitor displays status for all processes
%% running interpreted modules.
%% -------------------------------------------

im() ->
    case debugger:start() of
	{ok, Pid} ->
	    Pid;
	{error, {already_started, Pid}} ->
	    Pid
    end.

%% -------------------------------------------
%% Add Module(s) as being interpreted.
%% The actual paths will be searched for the
%% corresponding source file(s) (Module.erl).
%% Module(s) can be given with absolute path.
%% -------------------------------------------

ii(Module) ->
    int:i(Module).

ii(Module,_Options) ->
    int:i(Module).

%% -------------------------------------------
%% Don't interpret module(s). The module will be
%% removed from the set of modules interpreted.
%% -------------------------------------------

iq(Module) ->
    int:n(Module).

%% -------------------------------------------
%% The corresponding functions for distributed
%% erlang. The loading ... will be performed
%% at all nodes using the broadcast facility.
%% -------------------------------------------

ini(Module) ->
    int:ni(Module).

ini(Module,_Options) ->
    int:ni(Module).

inq(Module) ->
    int:nn(Module).

%% -------------------------------------------
%% Add a new break point at Line in Module.
%% -------------------------------------------

ib(Module,Line) ->
    int:break(Module,Line).

%% -------------------------------------------
%% Break at entrance of specified function.
%% Breaks is set at the first expression for 
%% all function clauses.
%% -------------------------------------------

ib(Module,Function,Arity) ->
    int:break_in(Module,Function,Arity).

%% -------------------------------------------
%% Break at entrance of specified function.
%% Breaks is set at the first expression for 
%% all function clauses.
%% Associate the condition to the break.
%% -------------------------------------------

ib(Module,Function,Arity,Cond) ->
    Breaks1 = int:all_breaks(Module),
    ok = int:break_in(Module,Function,Arity),
    Breaks2 = int:all_breaks(Module),
    lists:foreach(fun({Mod,Line}) -> int:test_at_break(Mod,Line,Cond) end,
		  Breaks2--Breaks1).

%% -------------------------------------------
%% Make an existing break point inactive.
%% -------------------------------------------

ibd(Mod,Line) ->
    int:disable_break(Mod,Line).
    
%% -------------------------------------------
%% Make an existing break point active.
%% -------------------------------------------

ibe(Mod,Line) ->
    int:enable_break(Mod,Line).

%% -------------------------------------------
%% Set which status a break point shall have
%% after it has been triggered the next time.
%% Action is: enable, disable or delete.
%% -------------------------------------------

iba(Mod,Line,Action) ->
    int:action_at_break(Mod,Line,Action).

%% -------------------------------------------
%% Add a conditional function to a break point.
%% The given function shall have arity 1 and
%% return either true or false.
%% The argument of the given function is the
%% current variable bindings of the process at
%% the place of the break point, the bindings
%% can be inspected using int:get_binding/2.

%% Fnk == {Module,Function}
%% Fnk == {Module,Function,ExtraArgs}
%% -------------------------------------------

ibc(Mod,Line,Fnk) ->
    int:test_at_break(Mod,Line,Fnk).

%% -------------------------------------------
%% Delete break point.
%% -------------------------------------------

ir(Module,Line) ->
    int:delete_break(Module,Line).

%% -------------------------------------------
%% Delete break at entrance of specified function.
%% -------------------------------------------

ir(Module,Function,Arity) ->
    int:del_break_in(Module,Function,Arity).

%% -------------------------------------------
%% Delete all break points in module.
%% -------------------------------------------

ir(Module) ->
    int:no_break(Module).

%% -------------------------------------------
%% Delete all break points (for all modules).
%% -------------------------------------------

ir() ->
    int:no_break().

%% -------------------------------------------
%% Print all interpreted modules.
%% -------------------------------------------

il() ->
    Mods = sort(int:interpreted()),
    ilformat("Module","File"),
    foreach(fun(Mod) -> ilformat(atom_to_list(Mod), get_file(Mod)) end, Mods).

get_file(Mod) ->
    case int:file(Mod) of
	{error,not_loaded} -> % Marked interpreted but not loaded
	    "not loaded";
	File ->
	    File
    end.

ilformat(A1, A2) ->
    format("~-20s     ~ts\n", [A1,A2]).

%% -------------------------------------------
%% Print all break points in modules.
%% -------------------------------------------

ipb() ->
    Bps = lists:keysort(1,int:all_breaks()),
    bhformat("Module","Line","Status","Action","Condition"),
    pb_print(Bps).

ipb(Module) when is_atom(Module) ->
    ipb1(Module);
ipb(Module) when is_list(Module) ->
    ipb1(list_to_atom(Module)).

ipb1(Module) ->
    Bps = lists:keysort(1,int:all_breaks(Module)),
    bhformat("Module","Line","Status","Action","Condition"),
    pb_print(Bps).

pb_print([{{Mod,Line},[Status,Action,_,null|_]}|Bps]) ->
    bformat(Mod,Line,Status,Action,""),
    pb_print(Bps);
pb_print([{{Mod,Line},[Status,Action,_,Cond|_]}|Bps]) ->
    bformat(Mod,Line,Status,Action,
	    io_lib:format("~w",[Cond])),
    pb_print(Bps);
pb_print(_) ->
    ok.

bhformat(A1, A2, A3, A4, A5) ->
    format("~-15s ~-9s ~-12s ~-12s ~-21s~n", [A1,A2,A3,A4,A5]).

bformat(A1, A2, A3, A4, A5) ->
    format("~-15w ~-9w ~-12w ~-12w ~-21s~n", [A1,A2,A3,A4,A5]).

%% -------------------------------------------
%% Set the stack trace flag.
%% Flag can be all (true), no_tail or false.
%% -------------------------------------------

ist(Flag) ->
    int:stack_trace(Flag),
    true.

%% -------------------------------------------
%% Set the automatic attachment flag.
%% Flags can be init, break and exit.
%% iaa(Flag) or ia([Flag,Flag,...])
%% -------------------------------------------

iaa(Flag) ->
    iaa(Flag,{dbg_wx_trace,start,[]}).

%% -------------------------------------------
%% Set the automatic attachment flag.
%% Flags can be init, break and exit.
%% Use given function to start up an attachment
%% window.
%% ia(Flag,Fnk) or ia([Flag,Flag,...],Fnk)
%%   where Fnk == {M,F}
%% The given Fnk must have arity 3 or 4.
%% -------------------------------------------

iaa(Flag,Fnk) ->
    int:auto_attach(Flag,Fnk),
    true.

%% -------------------------------------------
%% Attach to process.
%% -------------------------------------------

ia(Pid) ->
    ia(Pid,{dbg_wx_trace,start}).

%% -------------------------------------------
%% Attach to process.
%% X,Y,Z is combined to a process identity.
%% -------------------------------------------

ia(X,Y,Z) ->
    ia(c:pid(X,Y,Z)).

%% -------------------------------------------
%% Attach to process.
%% Use Fnk == {M,F} as the attaching interface.
%% -------------------------------------------

ia(Pid,Fnk) ->
    case lists:keymember(Pid, 1, int:snapshot()) of
	false -> no_proc;
	true  -> int:attach(Pid,Fnk)
    end.

ia(X,Y,Z,Fnk) ->
    ia(c:pid(X,Y,Z),Fnk).

%% -------------------------------------------
%% Print status for all interpreted processes.
%% -------------------------------------------

ip() ->
    Stats = int:snapshot(),
    hformat("Pid","Initial Call","Status","Info"),
    ip(Stats).

ip([{Pid,{M,F,A},Status,{}}|Stats]) ->
    hformat(io_lib:format("~w",[Pid]),
	    io_lib:format("~w:~tw/~w",[M,F,length(A)]),
	    io_lib:format("~w",[Status]),
	    ""),
    ip(Stats);
ip([{Pid,{M,F,A},Status,Info}|Stats]) ->
    hformat(io_lib:format("~w",[Pid]),
	    io_lib:format("~w:~tw/~w",[M,F,length(A)]),
	    io_lib:format("~w",[Status]),
	    io_lib:format("~w",[Info])),
    ip(Stats);
ip([]) ->
    ok.

hformat(A1, A2, A3, A4) ->
    format("~-12s ~-21ts ~-9s ~-21s~n", [A1,A2,A3,A4]).


%% -------------------------------------------
%% Delete all terminated processes from the 
%% interpreter.
%% -------------------------------------------

ic() ->
    int:clear().

%% -------------------------------------------
%% Help printout
%% -------------------------------------------

help() ->
    format("iv()         -- print the current version of the interpreter~n"),
    format("im()         -- pop up a monitor window~n"),
    format("ii(Mod)      -- interpret Mod(s) (or AbsMod(s))~n"),
    format("ii(Mod,Op)   -- interpret Mod(s) (or AbsMod(s))~n"),
    format("                use Op as options (same as for compile)~n"),
    format("iq(Mod)      -- do not interpret Mod(s)~n"),
    format("ini(Mod)     -- ii/1 at all Erlang nodes~n"),
    format("ini(Mod,Op)  -- ii/2 at all Erlang nodes~n"),
    format("inq(Mod)     -- iq at all Erlang nodes~n"),
    format("ib(Mod,Line) -- set a break point at Line in Mod~n"),
    format("ib(M,F,Arity)-- set a break point in M:F/Arity~n"),
    format("ibd(Mod,Line)-- disable the break point at Line in Mod~n"),
    format("ibe(Mod,Line)-- enable the break point at Line in Mod~n"),
    format("iba(M,L,Action)-- set a new action at break~n"),
    format("ibc(M,L,Action)-- set a new condition for break~n"),
    format("ir(Mod,Line) -- remove the break point at Line in Mod~n"),
    format("ir(M,F,Arity)-- remove the break point in M:F/Arity~n"),
    format("ir(Mod)      -- remove all break points in Mod~n"),
    format("ir()         -- remove all existing break points~n"),
    format("il()         -- list all interpreted modules~n"),
    format("ip()         -- print status of all interpreted processes~n"),
    format("ic()         -- remove all terminated interpreted processes~n"),
    format("ipb()        -- list all break points~n"),
    format("ipb(Mod)     -- list all break points in Mod~n"),
    format("ia(Pid)      -- attach to Pid~n"),
    format("ia(X,Y,Z)    -- attach to pid(X,Y,Z)~n"),
    format("ia(Pid,Fun)  -- use own Fun = {M,F} as attach application~n"),
    format("ia(X,Y,Z,Fun)-- use own Fun = {M,F} as attach application~n"),
    format("iaa([Flag])  -- set automatic attach to process~n"),
    format("                Flag is init,break and exit~n"),
    format("iaa([Fl],Fun)-- use own Fun = {M,F} as attach application~n"),
    format("ist(Flag)    -- set stack trace flag~n"),
    format("                Flag is all (true),no_tail or false~n"),
    ok.


