%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
-module(user).
-compile( [ inline, { inline_size, 100 } ] ).

%% Basic standard i/o server for user interface port.

-export([start/0, start/1, start_out/0]).
-export([interfaces/1]).

-define(NAME, user).

%% Defines for control ops
-define(CTRL_OP_GET_WINSIZE,100).

%%
%% The basic server and start-up.
%%

start() ->
    start_port([eof,binary]).

start([Mod,Fun|Args]) ->
    %% Mod,Fun,Args should return a pid. That process is supposed to act
    %% as the io port.
    Pid = apply(Mod, Fun, Args),  % This better work!
    Id = spawn(fun() -> server(Pid) end),
    register(?NAME, Id),
    Id.

start_out() ->
    %% Output-only version of start/0
    start_port([out,binary]).

start_port(PortSettings) ->
    Id = spawn(fun() -> server({fd,0,1}, PortSettings) end),
    register(?NAME, Id),
    Id.

%% Return the pid of the shell process.
%% Note: We can't ask the user process for this info since it
%% may be busy waiting for data from the port.
interfaces(User) ->
    case process_info(User, dictionary) of
	{dictionary,Dict} ->
	    case lists:keysearch(shell, 1, Dict) of
		{value,Sh={shell,Shell}} when is_pid(Shell) ->
		    [Sh];
		_ ->
		    []
	    end;
	_ ->
	    []
    end.

server(Pid) when is_pid(Pid) ->
    process_flag(trap_exit, true),
    link(Pid),
    run(Pid).

server(PortName,PortSettings) ->
    process_flag(trap_exit, true),
    Port = open_port(PortName,PortSettings),
    run(Port).

run(P) ->
    put(read_mode,list),
    put(unicode,false),
    case init:get_argument(noshell) of
	%% non-empty list -> noshell
	{ok, [_|_]} -> 
	    put(shell, noshell),
	    server_loop(P, queue:new());
	_ ->
	    group_leader(self(), self()),
	    catch_loop(P, start_init_shell())
    end.

catch_loop(Port, Shell) ->
    catch_loop(Port, Shell, queue:new()).

catch_loop(Port, Shell, Q) ->
    case catch server_loop(Port, Q) of
	new_shell ->
	    exit(Shell, kill),
	    catch_loop(Port, start_new_shell());
	{unknown_exit,{Shell,Reason},_} ->		 % shell has exited
	    case Reason of
		normal ->
		    put_chars("*** ", Port, []);
		_ ->
		    put_chars("*** ERROR: ", Port, [])
	    end,
	    put_chars("Shell process terminated! ***\n", Port, []),
	    catch_loop(Port, start_new_shell());
	{unknown_exit,_,Q1} ->
	    catch_loop(Port, Shell, Q1);	     
	{'EXIT',R} ->
	    exit(R)
    end.

link_and_save_shell(Shell) ->
    link(Shell),
    put(shell, Shell),
    Shell.        

start_init_shell() ->
    link_and_save_shell(shell:start(init)).

start_new_shell() ->
    link_and_save_shell(shell:start()).

server_loop(Port, Q) ->
    receive
	{io_request,From,ReplyAs,Request} when is_pid(From) ->
	    server_loop(Port, do_io_request(Request, From, ReplyAs, Port, Q));
	{Port,{data,Bytes}} ->
	    case get(shell) of
		noshell ->
		    server_loop(Port, queue:snoc(Q, Bytes));
		_ ->
		    case contains_ctrl_g_or_ctrl_c(Bytes) of
			false ->
			    server_loop(Port, queue:snoc(Q, Bytes));
			_ ->
			    throw(new_shell)
		    end
	    end;
	{Port, eof} ->
	    put(eof, true),
	    server_loop(Port, Q);

	%% Ignore messages from port here.
	{'EXIT',Port,badsig} ->			% Ignore badsig errors
	    server_loop(Port, Q);
	{'EXIT',Port,What} ->			% Port has exited
	    exit(What);

	%% Check if shell has exited
	{'EXIT',SomePid,What} ->
	    case get(shell) of
		noshell ->
		    server_loop(Port, Q);	% Ignore
		_ ->
		    throw({unknown_exit,{SomePid,What},Q})
	    end;
	
	_Other ->				% Ignore other messages
	    server_loop(Port, Q)
    end.


get_fd_geometry(Port) ->
    case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of
	List when length(List) =:= 8 ->
	    <<W:32/native,H:32/native>> = list_to_binary(List),
	    {W,H};
	_ ->
	    error
    end.


%% NewSaveBuffer = io_request(Request, FromPid, ReplyAs, Port, SaveBuffer)

do_io_request(Req, From, ReplyAs, Port, Q0) ->
    case io_request(Req, Port, Q0) of
	{_Status,Reply,Q1} ->
	    io_reply(From, ReplyAs, Reply),
	    Q1;
	{exit,What} ->
	    send_port(Port, close),
	    exit(What)
    end.

%% New in R13B
%% Encoding option (unicode/latin1)
io_request({put_chars,unicode,Chars}, Port, Q) -> % Binary new in R9C
    put_chars(wrap_characters_to_binary(Chars,unicode,
					case get(unicode) of 
					    true -> unicode;
					    _ -> latin1
					end), Port, Q);
io_request({put_chars,unicode,Mod,Func,Args}, Port, Q) ->
    Result = case catch apply(Mod,Func,Args) of
		 Data when is_list(Data); is_binary(Data) ->
		     wrap_characters_to_binary(Data,unicode,
					       case get(unicode) of 
						   true -> unicode;
						   _ -> latin1
					       end);
		 Undef ->
		     Undef
	     end,
    put_chars(Result, Port, Q);
io_request({put_chars,latin1,Chars}, Port, Q) -> % Binary new in R9C
    Data = case get(unicode) of
	       true ->
		   unicode:characters_to_binary(Chars,latin1,unicode);
	       false ->
		   erlang:iolist_to_binary(Chars)
	   end,
    put_chars(Data, Port, Q);
io_request({put_chars,latin1,Mod,Func,Args}, Port, Q) ->
    Result = case catch apply(Mod,Func,Args) of
		 Data when is_list(Data); is_binary(Data) ->
		     unicode:characters_to_binary(Data,latin1,
						  case get(unicode) of 
						      true -> unicode;
						      _ -> latin1
						  end);
		 Undef ->
		     Undef
	     end,
    put_chars(Result, Port, Q);
io_request({get_chars,Enc,Prompt,N}, Port, Q) -> % New in R9C
    get_chars(Prompt, io_lib, collect_chars, N, Port, Q, Enc);
io_request({get_line,Enc,Prompt}, Port, Q) ->
    case get(read_mode) of
	binary ->
	    get_line_bin(Prompt,Port,Q,Enc);
	_ ->
	    get_chars(Prompt, io_lib, collect_line, [], Port, Q, Enc)
    end;
io_request({get_until,Enc,Prompt,M,F,As}, Port, Q) ->
    get_chars(Prompt, io_lib, get_until, {M,F,As}, Port, Q, Enc);
%%  End New in R13B
io_request(getopts, Port, Q) ->
    getopts(Port, Q);
io_request({setopts,Opts}, Port, Q) when is_list(Opts) ->
    setopts(Opts, Port, Q);
io_request({requests,Reqs}, Port, Q) ->
    io_requests(Reqs, {ok,ok,Q}, Port);

%% New in R12
io_request({get_geometry,columns},Port,Q) ->
    case get_fd_geometry(Port) of
	{W,_H} ->
	    {ok,W,Q};
	_ ->
	    {error,{error,enotsup},Q}
    end;
io_request({get_geometry,rows},Port,Q) ->
    case get_fd_geometry(Port) of
	{_W,H} ->
	    {ok,H,Q};
	_ ->
	    {error,{error,enotsup},Q}
    end;
%% BC with pre-R13 nodes
io_request({put_chars,Chars}, Port, Q) ->
    io_request({put_chars,latin1,Chars}, Port, Q);
io_request({put_chars,Mod,Func,Args}, Port, Q) ->
    io_request({put_chars,latin1,Mod,Func,Args}, Port, Q);
io_request({get_chars,Prompt,N}, Port, Q) -> 
    io_request({get_chars,latin1,Prompt,N}, Port, Q);
io_request({get_line,Prompt}, Port, Q) ->
    io_request({get_line,latin1,Prompt}, Port, Q);
io_request({get_until,Prompt,M,F,As}, Port, Q) ->
    io_request({get_until,latin1,Prompt,M,F,As}, Port, Q);

io_request(R, _Port, Q) ->                      %Unknown request
    {error,{error,{request,R}},Q}.		%Ignore but give error (?)

%% Status = io_requests(RequestList, PrevStat, Port)
%%  Process a list of output requests as long as the previous status is 'ok'.

io_requests([R|Rs], {ok,_Res,Q}, Port) ->
    io_requests(Rs, io_request(R, Port, Q), Port);
io_requests([_|_], Error, _) ->
    Error;
io_requests([], Stat, _) ->
    Stat.

%% put_port(DeepList, Port)
%%  Take a deep list of characters, flatten and output them to the
%%  port.

put_port(List, Port) ->
    send_port(Port, {command, List}).

%% send_port(Port, Command)

send_port(Port, Command) ->
    Port ! {self(),Command}.

%% io_reply(From, ReplyAs, Reply)
%%  The function for sending i/o command acknowledgement.
%%  The ACK contains the return value.

io_reply(From, ReplyAs, Reply) ->
    From ! {io_reply,ReplyAs,Reply}.

%% put_chars
put_chars(Chars, Port, Q) when is_binary(Chars) ->
    put_port(Chars, Port),
    {ok,ok,Q};
put_chars(Chars, Port, Q) ->
    case catch list_to_binary(Chars) of
	Binary when is_binary(Binary) ->
	    put_chars(Binary, Port, Q);
	_ ->
	    {error,{error,put_chars},Q}
    end.

expand_encoding([]) ->
    [];
expand_encoding([latin1 | T]) ->
    [{encoding,latin1} | expand_encoding(T)];
expand_encoding([unicode | T]) ->
    [{encoding,unicode} | expand_encoding(T)];
expand_encoding([H|T]) ->
    [H|expand_encoding(T)].

%% setopts
setopts(Opts0,Port,Q) ->
    Opts = proplists:unfold(
	     proplists:substitute_negations(
	       [{list,binary}], 
	       expand_encoding(Opts0))),
    case check_valid_opts(Opts) of
	true ->
	    do_setopts(Opts,Port,Q);
	false ->
	    {error,{error,enotsup},Q}
    end.
check_valid_opts([]) ->
    true;
check_valid_opts([{binary,_}|T]) ->
    check_valid_opts(T);
check_valid_opts([{encoding,Valid}|T]) when Valid =:= latin1; Valid =:= utf8; Valid =:= unicode ->
    check_valid_opts(T);
check_valid_opts(_) ->
    false.

do_setopts(Opts, _Port, Q) ->
    case proplists:get_value(encoding,Opts) of
	Valid when Valid =:= unicode; Valid =:= utf8 ->
	    put(unicode,true);
	latin1 ->
	    put(unicode,false);
	undefined ->
	    ok
    end,
    case proplists:get_value(binary, Opts) of
	true ->
	    put(read_mode,binary),
	    {ok,ok,Q};
	false ->
	    put(read_mode,list),
	    {ok,ok,Q};
	_ ->
	    {ok,ok,Q}
    end.

getopts(_Port,Q) ->
    Bin = {binary, get(read_mode) =:= binary},
    Uni = {encoding, case get(unicode) of
		       true ->
			   unicode;
		       _ ->
			   latin1
		   end},
    {ok,[Bin,Uni],Q}.


get_line_bin(Prompt,Port,Q, Enc) ->
    prompt(Port, Prompt),
    case {get(eof),queue:is_empty(Q)} of
	{true,true} ->
	    {ok,eof,Q};
	_ ->
	    get_line(Prompt,Port, Q, [], Enc)
    end.
get_line(Prompt, Port, Q, Acc, Enc) ->
    case queue:is_empty(Q) of
	true ->
	    receive
		{Port,{data,Bytes}} ->
		    get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc);
		{Port, eof} ->
		    put(eof, true),
		    {ok, eof, []};
                {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
                    do_io_request(Req, From, ReplyAs, Port, 
                                  queue:new()), 
                    %% No prompt.
                    get_line(Prompt, Port, Q, Acc, Enc);
		{io_request,From,ReplyAs,Request} when is_pid(From) ->
		    do_io_request(Request, From, ReplyAs, Port, queue:new()), 
		    prompt(Port, Prompt),
		    get_line(Prompt, Port, Q, Acc, Enc);
		{'EXIT',From,What} when node(From) =:= node() ->
		    {exit,What}
	    end;
	false ->
	    get_line_doit(Prompt, Port, Q, Acc, Enc)
    end.

get_line_bytes(Prompt, Port, Q, Acc, Bytes, Enc) ->
    case get(shell) of
	noshell ->
	    get_line_doit(Prompt, Port, queue:snoc(Q, Bytes),Acc,Enc);
	_ ->
	    case contains_ctrl_g_or_ctrl_c(Bytes) of
		false ->
		    get_line_doit(Prompt, Port, queue:snoc(Q, Bytes), Acc, Enc);
		_ ->
		    throw(new_shell)
	    end
    end.
is_cr_at(Pos,Bin) ->
    case Bin of
  	<<_:Pos/binary,$\r,_/binary>> ->
  	    true;
  	_ ->
  	    false
    end.
srch(<<>>,_,_) ->
    nomatch;
srch(<<X:8,_/binary>>,X,N) ->
    {match,[{N,1}]};
srch(<<_:8,T/binary>>,X,N) ->
    srch(T,X,N+1).
get_line_doit(Prompt, Port, Q, Accu, Enc) -> 
    case queue:is_empty(Q) of
	true ->
	    case get(eof) of
		true ->
		   case Accu of
		       [] ->
			   {ok,eof,Q};
		       _ ->
			   {ok,binrev(Accu,[]),Q}
		   end;
		_ ->
		    get_line(Prompt, Port, Q, Accu, Enc)
	    end;
	false ->
	    Bin = queue:head(Q),
	    case srch(Bin,$\n,0) of
		nomatch ->
		    X = byte_size(Bin)-1,
		    case is_cr_at(X,Bin) of
			true ->
			    <<D:X/binary,_/binary>> = Bin,
			    get_line_doit(Prompt, Port, queue:tail(Q), 
					  [<<$\r>>,D|Accu], Enc); 
			false ->
			    get_line_doit(Prompt, Port, queue:tail(Q), 
					  [Bin|Accu], Enc)
		    end;
		{match,[{Pos,1}]} ->
		    %% We are done
		    PosPlus = Pos + 1,
		    case Accu of
			[] ->
			    {Head,Tail} = 
				case is_cr_at(Pos - 1,Bin) of
				    false ->
					<<H:PosPlus/binary,
					 T/binary>> = Bin,
					{H,T};
				    true ->
					PosMinus = Pos - 1,
					<<H:PosMinus/binary,
					 _,_,T/binary>> = Bin,
					{binrev([],[H,$\n]),T}
				end,
			    case Tail of
				<<>> ->
				    {ok, cast(Head,Enc), queue:tail(Q)};
				_ ->
				    {ok, cast(Head,Enc), 
				     queue:cons(Tail, queue:tail(Q))}
			    end;
			[<<$\r>>|Stack1] when Pos =:= 0 ->
			    <<_:PosPlus/binary,Tail/binary>> = Bin, 
			    case Tail of
				<<>> ->
				    {ok, cast(binrev(Stack1, [$\n]),Enc), 
				     queue:tail(Q)};
				_ ->
				    {ok, cast(binrev(Stack1, [$\n]),Enc), 
				     queue:cons(Tail, queue:tail(Q))}
			    end;
			_ ->
			    {Head,Tail} = 
				case is_cr_at(Pos - 1,Bin) of
				    false ->
					<<H:PosPlus/binary,
					 T/binary>> = Bin,
					{H,T};
				    true ->
					PosMinus = Pos - 1,
					<<H:PosMinus/binary,
					 _,_,T/binary>> = Bin,
					{[H,$\n],T}
				end,
			    case Tail of
				<<>> ->
				    {ok, cast(binrev(Accu,[Head]),Enc), 
				     queue:tail(Q)};
				_ ->
				    {ok, cast(binrev(Accu,[Head]),Enc), 
				     queue:cons(Tail, queue:tail(Q))}
			    end
		    end
	    end
    end.

binrev(L, T) ->
    list_to_binary(lists:reverse(L, T)).

%%  is_cr_at(Pos,Bin) ->
%%      case Bin of
%%  	<<_:Pos/binary,$\r,_/binary>> ->
%%  	    true;
%%  	_ ->
%%  	    false
%%      end.

%%  collect_line_bin_re(Bin,_Data,Stack,_) ->
%%      case re:run(Bin,<<"\n">>) of
%%  	nomatch ->
%%  	    X = byte_size(Bin)-1,
%%  	    case is_cr_at(X,Bin) of
%%  		true ->
%%  		    <<D:X/binary,_/binary>> = Bin,
%%  		    [<<$\r>>,D|Stack]; 
%%  		false ->
%%  		    [Bin|Stack]
%%  	    end;
%%  	{match,[{Pos,1}]} ->
%%  	    PosPlus = Pos + 1,
%%  	    case Stack of
%%  		[] ->
%%  		    case is_cr_at(Pos - 1,Bin) of
%%  			false ->
%%  			    <<Head:PosPlus/binary,Tail/binary>> = Bin, 
%%  			    {stop, Head, Tail};
%%  			true ->
%%  			    PosMinus = Pos - 1,
%%  			    <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
%%  			    {stop, binrev([],[Head,$\n]),Tail}
%%  		    end;
%%  		[<<$\r>>|Stack1] when Pos =:= 0 ->

%%  		    <<_:PosPlus/binary,Tail/binary>> = Bin, 
%%  		    {stop,binrev(Stack1, [$\n]),Tail};
%%  		_ ->
%%  		    case is_cr_at(Pos - 1,Bin) of
%%  			false ->
%%  			    <<Head:PosPlus/binary,Tail/binary>> = Bin, 
%%  			    {stop,binrev(Stack, [Head]),Tail};
%%  			true ->
%%  			    PosMinus = Pos - 1,
%%  			    <<Head:PosMinus/binary,_,_,Tail/binary>> = Bin,
%%  			    {stop, binrev(Stack,[Head,$\n]),Tail}
%%  		    end
%%  	    end
%%      end.
%% get_chars(Prompt, Module, Function, XtraArg, Port, Queue)
%%  Gets characters from the input port until the applied function
%%  returns {stop,Result,RestBuf}. Does not block output until input 
%%  has been received.
%%  Returns:
%%	{Status,Result,NewQueue}
%%	{exit,Reason}
    
%% Entry function.
get_chars(Prompt, M, F, Xa, Port, Q, Fmt) ->
    prompt(Port, Prompt),
    case {get(eof),queue:is_empty(Q)} of
	{true,true} ->
	    {ok,eof,Q};
	_ ->
	    get_chars(Prompt, M, F, Xa, Port, Q, start, Fmt)
    end.

%% First loop. Wait for port data. Respond to output requests.
get_chars(Prompt, M, F, Xa, Port, Q, State, Fmt) ->
    case queue:is_empty(Q) of
	true ->
	    receive
		{Port,{data,Bytes}} ->
		    get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt);
		{Port, eof} ->
		    put(eof, true),
		    {ok, eof, []};
		%%{io_request,From,ReplyAs,Request} when is_pid(From) ->
		%%    get_chars_req(Prompt, M, F, Xa, Port, queue:new(), State,
		%%		  Request, From, ReplyAs);
                {io_request,From,ReplyAs,{get_geometry,_}=Req} when is_pid(From) ->
                    do_io_request(Req, From, ReplyAs, Port, 
                                  queue:new()), %Keep Q over this call
                    %% No prompt.
                    get_chars(Prompt, M, F, Xa, Port, Q, State, Fmt);
		{io_request,From,ReplyAs,Request} when is_pid(From) ->
		    get_chars_req(Prompt, M, F, Xa, Port, Q, State,
				  Request, From, ReplyAs, Fmt);
		{'EXIT',From,What} when node(From) =:= node() ->
		    {exit,What}
	    end;
	false ->
	    get_chars_apply(State, M, F, Xa, Port, Q, Fmt)
    end.

get_chars_req(Prompt, M, F, XtraArg, Port, Q, State,
	      Req, From, ReplyAs, Fmt) ->
    do_io_request(Req, From, ReplyAs, Port, queue:new()), %Keep Q over this call
    prompt(Port, Prompt),
    get_chars(Prompt, M, F, XtraArg, Port, Q, State, Fmt).

%% Second loop. Pass data to client as long as it wants more.
%% A ^G in data interrupts loop if 'noshell' is not undefined.
get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt) ->
    case get(shell) of
	noshell ->
	    get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, Bytes),Fmt);
	_ ->
	    case contains_ctrl_g_or_ctrl_c(Bytes) of
		false ->
		    get_chars_apply(State, M, F, Xa, Port, 
				    queue:snoc(Q, Bytes),Fmt);
		_ ->
		    throw(new_shell)
	    end
    end.

get_chars_apply(State0, M, F, Xa, Port, Q, Fmt) ->
    case catch M:F(State0, cast(queue:head(Q),Fmt), Fmt, Xa) of
	{stop,Result,<<>>} ->
	    {ok,Result,queue:tail(Q)};
	{stop,Result,[]} ->
	    {ok,Result,queue:tail(Q)};
	{stop,Result,eof} ->
	    {ok,Result,queue:tail(Q)};
	{stop,Result,Buf} ->
	    {ok,Result,queue:cons(Buf, queue:tail(Q))};
	{'EXIT',_} ->
	    {error,{error,err_func(M, F, Xa)},queue:new()};
	State1 ->
	    get_chars_more(State1, M, F, Xa, Port, queue:tail(Q), Fmt)
    end.

get_chars_more(State, M, F, Xa, Port, Q, Fmt) ->
    case queue:is_empty(Q) of
	true ->
	    case get(eof) of
		undefined ->
		    receive
			{Port,{data,Bytes}} ->
			    get_chars_bytes(State, M, F, Xa, Port, Q, Bytes, Fmt);
			{Port,eof} ->
			    put(eof, true),
			    get_chars_apply(State, M, F, Xa, Port, 
					    queue:snoc(Q, eof), Fmt);
			{'EXIT',From,What} when node(From) =:= node() ->
			    {exit,What}
		    end;
		_ ->
		    get_chars_apply(State, M, F, Xa, Port, queue:snoc(Q, eof), Fmt)
	    end;
	false ->
	    get_chars_apply(State, M, F, Xa, Port, Q, Fmt)
    end.


%% prompt(Port, Prompt)
%%  Print Prompt onto Port

%% common case, reduces execution time by 20%
prompt(_Port, '') -> ok;

prompt(Port, Prompt) ->
    put_port(wrap_characters_to_binary(io_lib:format_prompt(Prompt),unicode,
				       case get(unicode) of 
					   true -> unicode;
					   _ -> latin1
				       end), Port).

%% Convert error code to make it look as before
err_func(io_lib, get_until, {_,F,_}) ->
    F;
err_func(_, F, _) ->
    F.

%% using regexp reduces execution time by >50% compared to old code
%% running two regexps in sequence is much faster than \\x03|\\x07
contains_ctrl_g_or_ctrl_c(BinOrList)->
    case {re:run(BinOrList, <<3>>),re:run(BinOrList, <<7>>)} of
	{nomatch, nomatch} -> false;
	_ -> true
    end.

%% Convert a buffer between list and binary
cast(Data, _Format) when is_atom(Data) ->
    Data;
cast(Data, Format) ->
    cast(Data, get(read_mode), Format, get(unicode)).

cast(B, binary, latin1, false) when is_binary(B) ->
    B;
cast(B, binary, latin1, true) when is_binary(B) ->
    unicode:characters_to_binary(B, unicode, latin1);
cast(L, binary, latin1, false) ->
    erlang:iolist_to_binary(L);
cast(L, binary, latin1, true) ->
    case unicode:characters_to_binary(
	   erlang:iolist_to_binary(L),unicode,latin1) of % may fail
	{error,_,_} -> exit({no_translation, unicode, latin1});
	Else -> Else
    end;
cast(B, binary, unicode, true) when is_binary(B) ->
    B;
cast(B, binary, unicode, false) when is_binary(B) ->
    unicode:characters_to_binary(B,latin1,unicode);
cast(L, binary, unicode, true) ->
    % possibly a list containing UTF-8 encoded characters
    unicode:characters_to_binary(erlang:iolist_to_binary(L));
cast(L, binary, unicode, false) ->
    unicode:characters_to_binary(L, latin1, unicode);
cast(L, list, latin1, UniTerm) ->
    case UniTerm of
	true -> % Convert input characters to protocol format (i.e latin1)
	    case unicode:characters_to_list(
		   erlang:iolist_to_binary(L),unicode) of % may fail
		{error,_,_} -> exit({no_translation, unicode, latin1});
		Else -> [ case X of 
			      High when High > 255 -> 
				  exit({no_translation, unicode, latin1}); 
			      Low -> 
				  Low 
			  end || X <- Else ]
	    end;
	_ ->
	    binary_to_list(erlang:iolist_to_binary(L))
    end;
cast(L, list, unicode, UniTerm)  ->
    unicode:characters_to_list(erlang:iolist_to_binary(L),
			       case UniTerm of 
				   true -> unicode; 
				   _ -> latin1 
			       end);
cast(Other, _, _,_) ->
    Other.

wrap_characters_to_binary(Chars,unicode,latin1) ->
    case unicode:characters_to_binary(Chars,unicode,latin1) of
	{error,_,_} -> 
	    list_to_binary( 
	      [ case X of
		    High when High > 255 ->
			["\\x{",erlang:integer_to_list(X, 16),$}];
		    Low ->
			Low
		end || X <- unicode:characters_to_list(Chars,unicode) ]);
	Bin ->
	    Bin
    end;
		       
wrap_characters_to_binary(Bin,From,From) when is_binary(Bin) ->
    Bin;
wrap_characters_to_binary(Chars,From,To) ->
    unicode:characters_to_binary(Chars,From,To).
