%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2018. 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(ssh_shell).

-include("ssh.hrl").
-include("ssh_connect.hrl").

%%% As this is an user interactive client it behaves like a daemon
%%% channel inspite of it being a client. 
-behaviour(ssh_server_channel).

%% ssh_server_channel callbacks
-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).

%% Spawn export
-export([input_loop/2]).

-export([dbg_trace/3]).

-record(state, 
	{
	 io,      %% Io process
	 channel, %% Id of the ssh channel
	 cm       %% Ssh connection manager
	 }
       ).

%%====================================================================
%% ssh_server_channel callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} 
%%                        
%% Description: Initiates the CLI
%%--------------------------------------------------------------------
init([ConnectionManager, ChannelId] = Args) ->
    
    %% Make sure that we are proclib compatible as
    %% this client should be runnable from the
    %% erlang shell.
    case get('$initial_call') of
	undefined ->
	    Me = get_my_name(),
	    Ancestors = get_ancestors(),
	    put('$ancestors', [Me | Ancestors]),
	    put('$initial_call', {?MODULE, init, Args});
	_ ->
	    ok
    end,

    case ssh_connection:shell(ConnectionManager, ChannelId) of
	ok ->
	    {group_leader, GIO} = 
		process_info(self(), group_leader),
	    IoPid = spawn_link(?MODULE, input_loop,
			       [GIO, self()]),
	    {ok, #state{io = IoPid, 
			channel = ChannelId, 
			cm = ConnectionManager}};
	Error ->
	    {stop, Error}
    end.

%%--------------------------------------------------------------------
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles channel messages received on the ssh-connection.
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
    %% TODO: When unicode support is ready
    %% should we call this function or perhaps a new
    %% function.
    io:put_chars(Data),
    {ok, State};

handle_ssh_msg({ssh_cm, _, 
		{data, _ChannelId, ?SSH_EXTENDED_DATA_STDERR, Data}},
	       State) ->
    %% TODO: When unicode support is ready
    %% should we call this function or perhaps a new
    %% function.
    io:put_chars(Data),
    {ok, State};

handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
    {ok, State};

handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
    %% Ignore signals according to RFC 4254 section 6.9.
    {ok, State};

handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
    io:put_chars("Connection closed by peer"),
    %% TODO: When unicode support is ready
    %% should we call this function or perhaps a new
    %% function. The error is encoded as UTF-8!
    io:put_chars(Error),
    {stop, ChannelId,  State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
    io:put_chars("logout"),
    io:put_chars("Connection closed"),
    {stop, ChannelId, State};

handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
    io:put_chars("Connection closed by peer"),
    io:put_chars("Status: " ++ integer_to_list(Status)),
    {stop, ChannelId, State}.

%%--------------------------------------------------------------------
%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%                        
%% Description: Handles other channel messages
%%--------------------------------------------------------------------
handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
	   #state{channel = ChannelId,
		  cm = ConnectionManager} = State) ->
    {ok,  State};

handle_msg({input, IoPid, eof}, #state{io = IoPid, channel = ChannelId, 
				       cm = ConnectionManager} = State) ->
    ssh_connection:send_eof(ConnectionManager, ChannelId),
    {ok, State};

handle_msg({input, IoPid, Line}, #state{io = IoPid,
					channel = ChannelId,
					cm = ConnectionManager} = State) ->
    ssh_connection:send(ConnectionManager, ChannelId, Line),
    {ok, State}.
    
%%--------------------------------------------------------------------
%% Function: terminate(Reasons, State) -> _
%%                        
%% Description: Cleanup when shell channel is terminated
%%--------------------------------------------------------------------
terminate(_Reason, #state{io = IoPid}) ->
    exit(IoPid, kill).
    
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

input_loop(Fd, Pid) ->
    case io:get_line(Fd, '>') of
	eof ->
	    Pid ! {input, self(), eof},
	    ok; 
	Line ->
	    Pid ! {input, self(), Line},
	    input_loop (Fd, Pid)
    end.
    
get_my_name() ->
    case process_info(self(),registered_name) of
	{registered_name,Name} -> Name;
	_                      -> self()
    end.

get_ancestors() ->
    case get('$ancestors') of
	A when is_list(A) -> A;
	_              -> []
    end.

%%%################################################################
%%%#
%%%# Tracing
%%%#

dbg_trace(points,         _,  _) -> [terminate];

dbg_trace(flags,  terminate,  _) -> [c];
dbg_trace(on,     terminate,  _) -> dbg:tp(?MODULE,  terminate, 2, x);
dbg_trace(off,    terminate,  _) -> dbg:ctpg(?MODULE, terminate, 2);
dbg_trace(format, terminate, {call, {?MODULE,terminate, [Reason, State]}}) ->
    ["Shell Terminating:\n",
     io_lib:format("Reason: ~p,~nState:~n~s", [Reason, wr_record(State)])
    ].

?wr_record(state).
