%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2013. 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%
%%
-module(si_sasl_supp).

-behaviour(gen_server).

%%%---------------------------------------------------------------------------
%%% Description:
%%% This module contains the BOS specific parts of the Status Inspection Tool.
%%%---------------------------------------------------------------------------


%% user interface
-export([h/0, help/0, start_log/1, stop_log/0, abbrevs/0, pi/1, pi/2, pi/3,
	 pi/4, ppi/1, ppi/3, start/0, start/1, stop/0, start_link/1]).

%% intermodule exports
-export([make_pid/1, make_pid/3, process_abbrevs/0, expand_abbrev/2,
	 status_info/1, valid_opt/1, p/1, do_best_printout/4,
	 si_exec/2, handle_call/3, terminate/2]).

%% exports for use within module
-export([init/1, start_log_impl/1, pi_impl/2, ppi_impl/1]).

%% other gen_server callbacks (not used)
-export([handle_cast/2, handle_info/2, code_change/3]).

%%--------------------------------------------------
%% Table of contents
%% 1. Interface
%% 2. SI - Server
%% 3. Code
%% 4. Selectors
%%--------------------------------------------------


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1. Interface
%% -----------------------------------------------------

h() -> print_help().
help() -> print_help().

si_exec(Fun, Args) -> call({si_exec, Fun, Args}).

start_log(FileName) ->
    call({start_log, FileName}).

stop_log() ->
    call(stop_log).

abbrevs() ->
    io:format("~p", [process_abbrevs()]).

%%-----------------------------------------------------------------
%% All functions can be called with an option 'normal' or 'all';
%% default is 'normal'.
%%-----------------------------------------------------------------
%% Process Info that tries to determine processtype (=Module), then
%% it uses this Module:format_info to format data from status_info/1.
%%-----------------------------------------------------------------
pi(XPid) -> 
    si_exec({si_sasl_supp, pi_impl}, [normal, XPid]).

pi(Opt, XPid) ->
    si_exec({si_sasl_supp, pi_impl}, [valid_opt(Opt), XPid]).

pi(A, B, C) when is_integer(A), is_integer(B), is_integer(C) ->
    si_exec({si_sasl_supp, pi_impl}, [normal, {A, B, C}]).

pi(Opt, A, B, C) when is_integer(A), is_integer(B), is_integer(C) ->
    si_exec({si_sasl_supp, pi_impl}, [valid_opt(Opt), {A, B, C}]).

%%-----------------------------------------------------------------
%% Pretty print Process_Info.
%%-----------------------------------------------------------------
ppi(XPid) ->
    case whereis(si_server) of
	undefined ->           % You can always run ppi.
	    ppi_impl(XPid);    % if si_server is down, use standard_io
	_ ->
	    si_exec({si_sasl_supp, ppi_impl}, [XPid])
    end.
ppi(A, B, C) ->
    case whereis(si_server) of
	undefined ->               % You can always run ppi.
	    ppi_impl({A, B, C});   % if si_server is down, use standard_io
	_ ->
	    si_exec({si_sasl_supp, ppi_impl}, [{A, B, C}])
    end.




%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 2. SI - Server
%%--------------------------------------------------
-record(state, {}).

start() -> start([]).
start(Options) ->
    supervisor:start_child(sasl_sup, 
			   {si_server, {si_sasl_supp, start_link, [Options]},
			    temporary, brutal_kill, worker, [si_sasl_supp]}).

start_link(_Options) ->
    gen_server:start_link({local, si_server}, si_sasl_supp, [], []).

stop() ->
    call(stop),
    supervisor:delete_child(sasl_sup, si_server).


init(Options) ->
    process_flag(trap_exit, true),
    start_log_impl(get_option(Options, start_log, standard_io)),
    {ok, #state{}}.

%%-----------------------------------------------------------------
%% If an error occurs and we're logging to file: write the error
%% to the file.
%% Always return the error.
%% The only data held by the si_server is the device in its process dictionary.
%%-----------------------------------------------------------------
handle_call({si_exec, Fun, Args}, _From, State) ->
    case catch apply(Fun, Args) of
	{'EXIT', Reason} ->
	    print_error(get(device),
			"SI internal error. Reason: ~w~n",
			[Reason]),
	    {stop, shutdown, {internal_error, Reason}, State};
	{error, Reason} ->
	    print_error(get(device), "~nSI error: ~w~n", [Reason]),
	    {reply, {error, Reason}, State};
	X -> 
	    {reply, X, State}
    end;
handle_call({start_log, FileName}, _From, State) ->
    start_log_impl(FileName),
    {reply, ok, State};
handle_call(stop_log, _From, State) ->
    start_log_impl(standard_io),
    {reply, ok, State};
handle_call(stop, _From, State) ->
    start_log_impl(standard_io),
    {stop, normal, stopped, State}.

terminate(_Reason, _State) ->
    _ = close_device(get(device)),
    ok.

handle_cast(_Msg, State) ->
    {noreply, State}.
handle_info(_Info, State) ->
    {noreply, State}.
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

close_device(standard_io) -> ok;
close_device(Fd) -> file:close(Fd).

print_error(standard_io, _, _) -> ok;
print_error(Device, Format, Args) ->
    io:format(Device, Format, Args).

get_option(Options, Key, Default) ->
    case lists:keysearch(Key, 1, Options) of
	{value, {_Key, Value}} -> Value;
	_ -> Default
    end.

open_log_file(undefined, NewFile) ->
    open_log_file(NewFile);
open_log_file(standard_io, NewFile) ->
    open_log_file(NewFile);
open_log_file(OldFile, NewFile) ->
    _ = file:close(OldFile),
    open_log_file(NewFile).

open_log_file(standard_io) -> standard_io;
open_log_file(FileName) ->
    case file:open(FileName, [write]) of
	{ok, Fd} -> Fd;
	Error -> 
	    io:format("si_sasl_supp: Cannot open file '~s' (~w).~n",
		      [FileName, Error]),
	    io:format("si_sasl_supp: Using standard_io~n"),
	    standard_io
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 3. Code
%%--------------------------------------------------

%%-----------------------------------------------------------------
%% call(Request) -> Term
%%-----------------------------------------------------------------
call(Req) ->
    gen_server:call(si_server, Req, infinity).

%%--------------------------------------------------
%% Makes a Pid of almost anything.
%% Returns: Pid|{error, Reason}
%% Fails: Never.
%%--------------------------------------------------
make_pid(A,B,C) when is_integer(A), is_integer(B), is_integer(C) ->
    list_to_pid(lists:concat(["<",A,".",B,".",C,">"])).
make_pid(P) when is_pid(P) -> P;
make_pid(undefined) -> undefined;
make_pid(P) when is_atom(P) ->
    case whereis(P) of
	undefined ->
	    case expand_abbrev(P, process_abbrevs()) of
		{error, Reason} -> {error, Reason};
		{value, {_Abbrev, FullName}} ->
		    case whereis(FullName) of
			undefined ->
			    {error, {'process not registered', P}};
			Pid -> Pid
		    end
	    end;
	Pid -> Pid
    end;
make_pid(P) when is_list(P) -> list_to_pid(P);
make_pid({A, B, C}) -> make_pid(A, B, C);
make_pid(X) -> {error, {'can not make a pid of', X}}.

process_abbrevs() ->
    [{init, init},
     {fs, file_server}].
     
%%--------------------------------------------------
%% Args: Abbrevs is an assoc-list of {Abbrev, RealName}
%% Returns: {value, {Abbrev, FullName}}|{error, Reason}
%%--------------------------------------------------
expand_abbrev(ProcessName, Abbrevs) ->
    case lists:keysearch(ProcessName, 1, Abbrevs) of
	{value, {Abbrev, FullName}} ->
	    {value, {Abbrev, FullName}};
	_ ->
	    case lists:keysearch(ProcessName, 2, Abbrevs) of
		{value, {Abbrev, FullName}} ->
		    {value, {Abbrev, FullName}};
		_ ->
		    {error, {'invalid process name', ProcessName}}
	    end
    end.

%%-----------------------------------------------------------------
%% This is the function that actually gets the information out
%% of the agent/server/...
%% Returns: {status_info, Pid, Type, Data}
%%        | {error, Reason}
%%-----------------------------------------------------------------
status_info(Pid) when is_pid(Pid) ->
    case catch sys:get_status(Pid, 5000) of
	{status, Pid, Type, Info} ->
	    {status_info, Pid, Type, Info};
	_ ->
	    {error, {'process does not respond', Pid}}
    end;

status_info(X) ->
    {error, {'not a pid', X}}.

%%--------------------------------------------------
%% Implementation starts here.
%%--------------------------------------------------
start_log_impl(FileName) ->
    put(device, open_log_file(get(device), FileName)).

valid_opt(all) -> all;
valid_opt(_Opt) -> normal.


print_help() ->
    p("- - - - - - - - PROCESSES - - - - - - - - - "),
    p("si_sasl_supp:pi([Opt,] Pid)   - Formatted information about any process that"),
    p("                      SI recognises."),
    p("si_sasl_supp:pi([Opt,] A,B,C) - Same as si_sasl_supp:pi({A, B, C})."), 
    p("si_sasl_supp:ppi(Pid)         - Pretty formating of process_info."),
    p("                      Works for any process."),
    p("- - - - - - - - MISC  - - - - - - - - - - - "),
    p("si_sasl_supp:abbrevs()        - Lists valid abbreviations."),
    p("si_sasl_supp:start_log(FileNname)"),
    p("si_sasl_supp:stop_log()"),
    p("si_sasl_supp:start()          - Starts Status Inspection (the si_server)."),
    p("si_sasl_supp:start([{start_log, FileName}])"),
    p("si_sasl_supp:stop()           - Shut down SI.").



%% Convenient shorthand
p(X) ->
    io:format(lists:append(X, "~n")).

pi_impl(Opt, XPid) ->
    case make_pid(XPid) of
	Pid when is_pid(Pid) ->
	    case status_info(Pid) of
		{status_info, Pid, {module, Module}, Data} ->
		    do_best_printout(Opt, Pid, Module, Data);
		{error, Reason} ->
		    _ = ppi_impl(Pid),
		    {error, {"can not get status info from process:",
			     XPid,
			     Reason}}
	    end;
	{error, Reason} ->
	    {error, Reason}
    end.

%%--------------------------------------------------
%% Is there a format_info for this process? In that case, run it!
%% Return ok|{error, Reason}
%% Fails: Never.
%%--------------------------------------------------
do_best_printout(Opt, Pid, Mod, Data) when is_pid(Pid) ->
    case print_info(get(device), Pid, {Mod, format_status}, Opt, Data) of
	ok -> ok;
	{error, Reason} ->
	    _ = ppi_impl(Pid),
	    {error, Reason}
    end.

ppi_impl(XPid) -> 
    case make_pid(XPid) of
	P when is_pid(P) -> 
	    case process_info(P) of
		undefined ->
		    {error, {'dead process', P}};
		PI ->
		    Device = case get(device) of
				 undefined -> standard_io;
				 X -> X
			     end,
		    io:format(Device, "~nPretty Process Info~n", []),
		    io:format(Device, "-------------------~n", []),
		    io:format(Device, "~p~n", [PI])
	    end;
	_ -> {error, {no_pid, XPid}}
    end.

print_info(Device, Pid, {Module, Func}, Opt, Data) ->
    case erlang:function_exported(Module, Func, 2) of
	true ->
	    case catch apply(Module, Func, [Opt, Data]) of
		Format when is_list(Format) ->
		    format_lib_supp:print_info(Device, 79,
					       add_pid_to_format(Pid, Format)),
		    ok;
		Other -> {error, {'invalid format', Other}}
	    end;
	_ ->
	    {error, {no_such_function, Module, Func}}
    end.

add_pid_to_format(Pid, [{header, H} | T]) ->
    [{header, H}, {data, [{"Pid", Pid}]} | T];
add_pid_to_format(Pid, List) ->
    [{data, [{"Pid", Pid}]} | List].


