src/bert.erl

-module(bert).
-version('0.1.0').
-author("Yuce Tekol").

%% API exports
-export([encode/1,
         decode/1]).


%%====================================================================
%% API functions
%%====================================================================

%%@doc Encode Erlang term to a binary data.

-spec encode(term()) -> binary().
encode(Term) ->
    term_to_binary(encode_term(Term)).

%%@doc Decode a binary data to Erlang term.

-spec decode(binary()) -> term().
decode(Bin) ->
    decode_term(binary_to_term(Bin)).


%%====================================================================
%% Internal functions
%%====================================================================

-spec encode_term(term()) -> term().

encode_term([]) -> {bert, nil};
encode_term(true) -> {bert, true};
encode_term(false) -> {bert, false};

encode_term(Map) when is_map(Map) ->
    {bert, dict, encode_term(maps:to_list(Map))};

encode_term(List) when is_list(List) ->
    % TODO: Handle improper lists
    lists:map(fun encode_term/1, List);

encode_term(Tuple) when is_tuple(Tuple), Tuple =/={} ->
    list_to_tuple(encode_term(tuple_to_list(Tuple)));

encode_term(Term) -> Term.

-spec decode_term(term()) -> term().

decode_term({bert, nil}) -> [];
decode_term({bert, true}) -> true;
decode_term({bert, false}) -> false;

decode_term({bert, dict, {bert, nil}}) ->
    #{};

decode_term({bert, dict, KeysValues}) when is_list(KeysValues) ->
    maps:from_list(decode_term(KeysValues));

decode_term(List) when is_list(List) ->
    lists:map(fun decode_term/1, List);

decode_term(Tuple) when is_tuple(Tuple) ->
    list_to_tuple(decode_term(tuple_to_list(Tuple)));

decode_term(Term) -> Term.

%%====================================================================
%% Tests
%%====================================================================

-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").

encode_nil_test() ->
    ?assertEqual({bert, nil}, encode_term([])).

encode_bool_test() ->
    ?assertEqual({bert, true}, encode_term(true)),
    ?assertEqual({bert, false}, encode_term(false)).

encode_tuple_test() ->
    Tuple = {"a", true},
    Expected = {"a", {bert, true}},
    ?assertEqual(Expected, encode_term(Tuple)).
	
encode_empty_tuple_test() ->
    Tuple = {},
    Expected = {},
    ?assertEqual(Expected, encode_term(Tuple)).	

encode_list_test() ->
    List = [{"a", true}, {<<"bbb">>, 42}],
    Expected = [{"a", {bert, true}}, {<<"bbb">>, 42}],
    ?assertEqual(Expected, encode_term(List)).

encode_dict_test() ->
    Dict = maps:from_list([{"a", true}, {<<"bbb">>, 42}]),
    {bert, dict, EncodeDict} = encode_term(Dict),
    ExpectedSortedKV = [{"a",{bert,true}}, {<<"bbb">>,42}],
    ?assertEqual(ExpectedSortedKV, lists:sort(EncodeDict)).


encode_number_test() ->
    ?assertEqual(42, encode_term(42)).

encode_binary_test() ->
    ?assertEqual(<<"bin">>, encode_term(<<"bin">>)).


decode_nil_test() ->
    ?assertEqual([], decode_term({bert, nil})).

decode_bool_test() ->
    ?assertEqual(true, decode_term({bert, true})).

decode_tuple_test() ->
    Tuple = {"a", {bert, true}},
    Expected = {"a", true},
    ?assertEqual(Expected, decode_term(Tuple)).

decode_list_test() ->
    List = [{"a", {bert, true}}, {<<"bbb">>, 42}],
    Expected = [{"a", true}, {<<"bbb">>, 42}],
    ?assertEqual(Expected, decode_term(List)).


decode_dict_test() ->
    Dict = {bert,dict,[{<<"bbb">>,42},{"a",{bert,true}}]},
    Expected = maps:from_list([{"a", true}, {<<"bbb">>, 42}]),
    ?assertEqual(Expected, decode_term(Dict)).

decode_empty_dict_test() ->
    ?assertEqual(#{}, decode_term(encode_term(#{}))).

decode_number_test() ->
    ?assertEqual(42, decode_term(42)).

decode_binary_test() ->
    ?assertEqual(<<"bin">>, decode_term(<<"bin">>)).


-endif.