%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2004-2012. 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%
%%

%%% While similar to bs_construct_SUITE in the emulator test suite,
%%% this module is more corncerned with testing sizes than the contents
%%% of binaries.

-module(bs_construct_SUITE).

-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
	 init_per_group/2,end_per_group/2,
	 init_per_testcase/2,end_per_testcase/2,
	 two/1,test1/1,fail/1,float_bin/1,in_guard/1,in_catch/1,
	 nasty_literals/1,coerce_to_float/1,side_effect/1,
	 opt/1,otp_7556/1,float_arith/1,otp_8054/1]).

-include_lib("test_server/include/test_server.hrl").

suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    test_lib:recompile(?MODULE),
    [{group,p}].

groups() -> 
    [{p,test_lib:parallel(),
      [two,test1,fail,float_bin,in_guard,in_catch,
       nasty_literals,side_effect,opt,otp_7556,float_arith,
       otp_8054]}].


init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
    Dog = test_server:timetrap(?t:minutes(1)),
    [{watchdog,Dog}|Config].

end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
    Dog = ?config(watchdog, Config),
    ?t:timetrap_cancel(Dog),
    ok.

two(Config) when is_list(Config) ->
    <<0,1,2,3,4,6,7,8,9>> = two_1([0], [<<1,2,3,4>>,<<6,7,8,9>>]),
    ok.

two_1(P, L) ->
    list_to_binary([P|L]).


big(1) ->
    57285702734876389752897683.

i(X) -> X.

id(I) -> I.

-define(T(B, L), {B, ??B, L}).
-define(N(B), {B, ??B, unknown}).

-define(FAIL(Expr), ?line {'EXIT',{badarg,_}} = (catch Expr)).

l(I_13, I_big1, I_16, Bin) ->
    [
     ?T(<<I_13:0>>,
	[]),
     ?T(<<-43>>,
	[256-43]),
     ?T(<<4:4,7:4>>,
	[4*16+7]),
     ?T(<<45:I_16/little>>,
        [45,0]),
     ?T(<<777:16/little>>,
	[9,3]),
     ?T(<<777:I_13,13:3>>,
        [24,77]),
     ?T(<<5:4,987:I_13,537:7>>,
        [81,237,153]),
     ?T(<<0.0:32/float>>,
	[0,0,0,0]),
     ?T(<<0.125:32/float>>,
	[62,0,0,0]),
     ?T(<<1.0:32/little-float>>,
	[0,0,128,63]),
     ?T(<<I_big1:32>>,
	[138,99,0,147]),
     ?T(<<57285702734876389752897684:(I_16+16)>>,
	[138,99,0,148]),
     ?T(<<-1:17/unit:8>>,
	lists:duplicate(17, 255)),
     ?T(<<-1:8/unit:17>>,
	lists:duplicate(17, 255)),
     ?T(<<4:(I_16-8)/unit:2,5:2/unit:8>>,
	[0,4,0,5]),
     ?T(<<1:1, 0:(I_13-7), 1:1>>,
	[129]),
     ?T(<<1:3,"string",9:5>>,
	[46,110,142,77,45,204,233]),
     ?T(<<37.98:64/native-float>>,
	native_3798()),
     ?T(<<32978297842987249827298387697777669766334937:128/native-integer>>,
	native_bignum()),

     ?T(<<Bin/binary>>,
        [165,90,195]),
     ?T(<<79,Bin/binary>>,
        [79,165,90,195]),
     ?T(<<3479:I_13,Bin/binary,7:3>>,
        [108,189,42,214,31]),
     ?T(<<3479:I_13,Bin/binary,7:1/unit:3>>,
        [108,189,42,214,31]),
     ?T(<<869:16/little,3479:I_13,Bin/binary,7:1/unit:3>>,
        [101,3,108,189,42,214,31]),
     ?T(<<869:16/little,3479:I_13,Bin/binary,7:1/unit:3,Bin/binary>>,
        [101,3,108,189,42,214,31,165,90,195]),

     %% Test of aligment flag.
     ?T(<<0:I_13/unit:8,1:6,0:2>>,
	[0,0,0,0,0,0,0,0,0,0,0,0,0,4]),

     %% Test of literals (coverage).
     ?T(<<0:128>>,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]),
     ?T(<<0:13/little,7:3>>,[0,7]),
     ?T(<<16#77FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:264>>,
	[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
	 16#77,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,
	 16#FF,16#FF,16#FF,16#FF,16#FF,16#FF]),

     %% Mix different units.
     ?T(<<37558955:(I_16-12)/unit:8,1:1>>,
	[2,61,26,171,<<1:1>>])
     ].

native_3798() ->
    case <<1:16/native>> of
	<<0,1>> -> [64,66,253,112,163,215,10,61];
	<<1,0>> -> [61,10,215,163,112,253,66,64]
    end.

native_bignum() ->
    case <<1:16/native>> of
	<<0,1>> -> [129,205,18,177,1,213,170,101,39,231,109,128,176,11,73,217];
	<<1,0>> -> [217,73,11,176,128,109,231,39,101,170,213,1,177,18,205,129]
    end.

evaluate(Str, Vars) ->
    {ok,Tokens,_} =
	erl_scan:string(Str ++ " . "),
    {ok, [Expr]} = erl_parse:parse_exprs(Tokens),
    case erl_eval:expr(Expr, Vars) of
	{value, Result, _} ->
	    Result
    end.

eval_list([], _Vars) ->
    [];
eval_list([{C_bin, Str, Bytes} | Rest], Vars) ->
    case catch evaluate(Str, Vars) of
	{'EXIT', Error} ->
	    io:format("Evaluation error: ~p, ~p, ~p~n", [Str, Vars, Error]),
	    exit(Error);
	E_bin ->
	    [{C_bin, E_bin, Str, Bytes} | eval_list(Rest, Vars)]
    end.

one_test({C_bin, E_bin, Str, Bytes}) when is_list(Bytes) ->
    io:format("  ~s, ~p~n", [Str, Bytes]),
    Bin = list_to_bitstring(Bytes),
    if
	C_bin == Bin ->
	    ok;
	true ->
	    io:format("ERROR: Compiled: ~p. Expected ~p. Got ~p.~n",
		      [Str, Bytes, bitstring_to_list(C_bin)]),
	    test_server:fail(comp)
    end,
    if
	E_bin == Bin ->
	    ok;
	true ->
	    io:format("ERROR: Interpreted: ~p. Expected ~p. Got ~p.~n",
		      [Str, Bytes, bitstring_to_list(E_bin)]),
	    test_server:fail(comp)
    end;
one_test({C_bin, E_bin, Str, Result}) ->
    io:format("  ~s ~p~n", [Str, C_bin]),
    if
	C_bin == E_bin ->
	    ok;
	true ->
	    Arbitrary = case Result of
			    unknown ->
				size(C_bin);
			    _ ->
				Result
			end,
	    case equal_lists(bitstring_to_list(C_bin),
			     bitstring_to_list(E_bin),
			     Arbitrary) of
		false ->
		    io:format("ERROR: Compiled not equal to interpreted:"
			      "~n ~p, ~p.~n",
			      [bitstring_to_list(C_bin), bitstring_to_list(E_bin)]),
		    test_server:fail(comp);
		0 ->
		    ok;
		%% For situations where the final bits may not matter, like
		%% for floats:
		N when is_integer(N) ->
		    io:format("Info: compiled and interpreted differ in the"
			      " last bytes:~n ~p, ~p.~n",
			      [bitstring_to_list(C_bin), bitstring_to_list(E_bin)]),
		    ok
	    end
    end.

equal_lists([], [], _) ->
    0;
equal_lists([], _, _) ->
    false;
equal_lists(_, [], _) ->
    false;
equal_lists([A|AR], [A|BR], R) ->
    equal_lists(AR, BR, R);
equal_lists(A, B, R) ->
    if
	length(A) /= length(B) ->
	    false;
	length(A) =< R ->
	    R;
	true ->
	    false
    end.

test1(Config) when is_list(Config) ->
    ?line I_13 = i(13),
    ?line I_big1 = big(1),
    ?line I_16 = i(16),
    ?line Bin = i(<<16#A5,16#5A,16#C3>>),
    ?line Vars = lists:sort([{'I_13',I_13},
			     {'I_big1',I_big1},
			     {'I_16',I_16},
			     {'Bin',Bin}]),
    ?line lists:foreach(fun one_test/1, eval_list(l(I_13, I_big1, I_16, Bin), Vars)).

fail(Config) when is_list(Config) ->
    I_minus_777 = i(-777),
    I_minus_2047 = i(-2047),

    %% One negative field size, but the sum of field sizes will be 1 byte.
    %% Make sure that we reject that properly.

    ?line {'EXIT',{badarg,_}} = (catch <<I_minus_777:2048/unit:8,
					57:I_minus_2047/unit:8>>),

    %% Same thing, but use literals.
    ?line {'EXIT',{badarg,_}} = (catch <<I_minus_777:2048/unit:8,
					57:(-2047)/unit:8>>),

    %% Not numbers.
    ?line {'EXIT',{badarg,_}} = (catch <<45:(i(not_a_number))>>),
    ?line {'EXIT',{badarg,_}} = (catch <<13:8,45:(i(not_a_number))>>),

    %% Unaligned sizes.
    BadSz = i(7),
    Bitstr = i(<<42:17>>),

    ?line {'EXIT',{badarg,_}} = (catch <<Bitstr:4/binary>>),
    ?line {'EXIT',{badarg,_}} = (catch <<Bitstr:BadSz/binary>>),

    ?line [] = [X || {X} <- [], X == <<Bitstr:BadSz/binary>>],
    ?line [] = [X || {X} <- [], X == <<Bitstr:4/binary>>],

    %% Literals with incorrect type.
    ?line {'EXIT',{badarg,_}} = (catch <<42.0/integer>>),
    ?line {'EXIT',{badarg,_}} = (catch <<42/binary>>),
    ?line {'EXIT',{badarg,_}} = (catch <<an_atom/integer>>),
    
    ok.

float_bin(Config) when is_list(Config) ->
    %% Some more coverage.
    ?line {<<1,2,3>>,7.0} = float_bin_1(4),
    F = 42.0,
    ?line <<42,0,0,0,0,0,0,69,64>> = <<(id(42)),F/little-float>>,
    ok.

float_bin_1(F) ->
    {<<1,2,3>>,F+3.0}.

in_guard(Config) when is_list(Config) ->
    ?line 1 = in_guard_1(<<16#74ad:16>>, 16#e95, 5),
    ?line 2 = in_guard_1(<<16#3A,16#F7,"hello">>, 16#3AF7, <<"hello">>),
    ?line 3 = in_guard_1(<<16#FBCD:14,3.1415/float,3:2>>, 16#FBCD, 3.1415),
    ?line nope = in_guard_1(<<1>>, 42, b),
    ?line nope = in_guard_1(<<1>>, a, b),
    ?line nope = in_guard_1(<<1,2>>, 1, 1),
    ?line nope = in_guard_1(<<4,5>>, 1, 2.71),
    ?line nope = in_guard_1(<<4,5>>, 1, <<12,13>>),

    ?line 1 = in_guard_2(<<0,56>>, 7, blurf),
    ?line 2 = in_guard_2(<<1,255>>, 511, blurf),
    ?line 3 = in_guard_2(<<0,3>>, 0, blurf),
    ?line 4 = in_guard_2(<<>>, 1, {<<7:16>>}),
    ?line nope = in_guard_2(<<4,5>>, 1, blurf),

    ?line 42 = in_guard_3(<<1,2,3,42>>, <<1,2,3>>),
    ?line 42 = in_guard_3(<<1,2,3,42>>, <<1,2,3>>),
    ?line nope = in_guard_3(<<>>, <<>>),

    ?line ok = in_guard_4(<<15:4>>, 255),
    ?line nope = in_guard_4(<<15:8>>, 255),
    ok.

in_guard_1(Bin, A, B) when <<A:13,B:3>> == Bin -> 1;
in_guard_1(Bin, A, B) when <<A:16,B/binary>> == Bin -> 2;
in_guard_1(Bin, A, B) when <<A:14,B/float,3:2>> == Bin -> 3;
in_guard_1(Bin, A, B) when {a,b,<<A:14,B/float,3:2>>} == Bin -> cant_happen;
in_guard_1(_, _, _) -> nope.

in_guard_2(Bin, A, _T) when <<A:13,0:3>> == Bin -> 1;
in_guard_2(Bin, A, _T) when <<A:16>> == Bin -> 2;
in_guard_2(Bin, A, _T) when <<A:14,3:2>> == Bin -> 3;
in_guard_2(_Bin, A, T) when {A,b} > {0,1}, {<<A:14,3:2>>} == T -> 4;
in_guard_2(_, _, _) -> nope.

in_guard_3(Bin, A) when <<A/binary,42>> =:= Bin -> 42;
in_guard_3(_, _) -> nope.

in_guard_4(Bin, A) when <<A:4>> =:= Bin -> ok;
in_guard_4(_, _) -> nope.

in_catch(Config) when is_list(Config) ->
    ?line <<42,0,5>> = small(42, 5),
    ?line <<255>> = small(255, <<1,2,3,4,5,6,7,8,9>>),
    ?line <<1,2>> = small(<<7,8,9,10>>, 258),
    ?line <<>> = small(<<1,2,3,4,5>>, <<7,8,9,10>>),

    <<15,240,0,42>> = small2(255, 42),
    <<7:20>> = small2(<<1,2,3>>, 7),
    <<300:12>> = small2(300, <<1,2,3>>),
    <<>> = small2(<<1>>, <<2>>),
    ok.

small(A, B) ->
    case begin
	     case catch <<A:8>> of
		 {'EXIT',_} -> <<>>;
		 ResA0 -> ResA0
	     end
	 end of
	ResA -> ok
    end,
    case begin
	     case catch <<B:16>> of
		 {'EXIT',_} -> <<>>;
		 ResB0 -> ResB0
	     end
	 end of
	ResB -> ok
    end,
    <<ResA/binary,ResB/binary>>.

small2(A, B) ->
    case begin
	     case catch <<A:12>> of
		 {'EXIT',_} -> <<>>;
		 ResA0 -> ResA0
	     end
	 end of
	ResA -> ok
    end,
    case begin
	     case catch <<B:20>> of
		 {'EXIT',_} -> <<>>;
		 ResB0 -> ResB0
	     end
	 end of
	ResB -> ok
    end,
    <<ResA/binary-unit:1,ResB/binary-unit:1>>.

nasty_literals(Config) when is_list(Config) ->
    case erlang:system_info(endian) of
	big ->
	    ?line [0,42] = binary_to_list(id(<<42:16/native>>));
	little ->
	    ?line [42,0] = binary_to_list(id(<<42:16/native>>))
    end,

    ?line Bin0 = id(<<1,2,3,0:10000000,4,5,6>>),
    ?line 1250006 = size(Bin0),
    ?line <<1,2,3,0:10000000,4,5,6>> = Bin0,

    ?line Bin1 = id(<<0:10000000,7,8,-1:10000000,9,10,0:10000000>>),
    ?line 3750004 = size(Bin1),
    ?line <<0:10000000,7,8,-1:10000000/signed,9,10,0:10000000>> = Bin1,

    ?line <<255,255,0,0,0>> = id(<<255,255,0,0,0>>),

    %% Coverage.
    I = 16#7777FFFF7777FFFF7777FFFF7777FFFF7777FFFF7777FFFF,
    id(<<I:260>>),

    ok.

-define(COF(Int0),
	?line (fun(Int) ->
		       true = <<Int:32/float>> =:= <<(float(Int)):32/float>>,
		       true = <<Int:64/float>> =:= <<(float(Int)):64/float>>
			   end)(nonliteral(Int0)),
 	?line true = <<Int0:32/float>> =:= <<(float(Int0)):32/float>>,
 	?line true = <<Int0:64/float>> =:= <<(float(Int0)):64/float>>).

-define(COF64(Int0),
	?line (fun(Int) ->
		       true = <<Int:64/float>> =:= <<(float(Int)):64/float>>
			   end)(nonliteral(Int0)),
 	?line true = <<Int0:64/float>> =:= <<(float(Int0)):64/float>>).

nonliteral(X) -> X.
    
coerce_to_float(Config) when is_list(Config) ->
    ?COF(0),
    ?COF(-1),
    ?COF(1),
    ?COF(42),
    ?COF(255),
    ?COF(-255),
    ?COF(38474),
    ?COF(387498738948729893849444444443),
    ?COF(-37489378937773899999999999999993),
    ?COF64(298748888888888888888888888883478264866528467367364766666666666666663),
    ?COF64(-367546729879999999999947826486652846736736476555566666663),
    ok.

side_effect(Config) when is_list(Config) ->
    {'EXIT',{badarg,_}} = (catch side_effect_1(a)),
    {'EXIT',{badarg,_}} = (catch side_effect_1(<<>>)),
    ?line ok = side_effect_1(42),
    ok.

side_effect_1(A) ->
    <<A:17>>,					%Warning intentional.
    ok.

-record(otp_7029, {a,b}).

opt(Config) when is_list(Config) ->
    ?line 42 = otp_7029(#otp_7029{a = <<>>,b = 42}),
    N = 16,
    ?line <<1,3,65>> = id(<<1,833:N>>),
    ?line <<1,66,3>> = id(<<1,834:N/little>>),
    ?line <<1,65,136,0,0>> = id(<<1,17.0:32/float>>),
    ?line <<1,64,8,0,0,0,0,0,0>> = id(<<1,3.0:N/float-unit:4>>),
    ?line <<1,0,0,0,0,0,0,8,64>> = id(<<1,3.0:N/little-float-unit:4>>),
    ?line {'EXIT',{badarg,_}} = (catch id(<<3.1416:N/float>>)),

    B = <<1,2,3,4,5>>,
    ?line <<0,1,2,3,4,5>> = id(<<0,B/binary>>),
    ?line <<1,2,3,4,5,19>> = id(<<B:5/binary,19>>),
    ?line <<1,2,3,42>> = id(<<B:3/binary,42>>),

    ?line {'EXIT',_} = (catch <<<<23,56,0,2>>:(2.5)/binary>>),
    ?line {'EXIT',_} = (catch <<<<23,56,0,2>>:(-16)/binary>>),
    ?line {'EXIT',_} = (catch <<<<23,56,0,2>>:(anka)>>),
    ?line {'EXIT',_} = (catch <<<<23,56,0,2>>:64/float>>),
    ?line {'EXIT',_} = (catch <<<<23,56,0,2:7>>/binary>>),

    %% Test constant propagation - there should be a warning.
    BadSz = 2.5,
    {'EXIT',_} = (catch <<<<N,56,0,2>>:BadSz/binary>>),

    case id(false) of
	true -> ?line opt_dont_call_me();
	false -> ok
    end,

    ok.

opt_dont_call_me() ->
    N = 16#12345678,
    <<0:N>>.

otp_7029(R) ->
    #otp_7029{a = <<>>} = R,
    R#otp_7029.b.

otp_7556(Config) when is_list(Config) ->
    [otp_7556(<<>>, 1024, 1024, 1024) || _ <- lists:seq(0, 1023)],
    ok.

otp_7556(Bin, A, B, C) ->
    %% When allocating the binary, the sizes 16*A and 16*A would
    %% be forgotten.
    <<Bin/binary,(-1):A/unit:16,0:B/unit:16,(-1):C/unit:16>>.

%% Test binary construction combined with floating point operations
%% (mostly to cover code in beam_flatten that combines the allocation
%% for a binary construction with a later allocation).

float_arith(Config) when is_list(Config) ->
    ?line {<<1,2,3,64,69,0,0,0,0,0,0>>,21.0} = do_float_arith(<<1,2,3>>, 42, 2),
    ok.

do_float_arith(Bin0, X, Y)  ->
    Bin = <<Bin0/binary,X/float>>,
    {Bin,X / Y}.

otp_8054(Config) when is_list(Config) ->
    ?line <<"abc">> = otp_8054_1([null,1,2,3], <<"abc">>),
    ok.

otp_8054_1([H|T], Bin) ->
    _ = case H of
	    null ->
		%% The beam_validator would complain about {x,3}
		%% not being live in bs_append/8 because of a live
		%% optimization bug.
		<<Bin/binary>>;
	    _ ->
		ok
	end,
    otp_8054_1(T, Bin);
otp_8054_1([], Bin) -> Bin.