% 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.

-module(json_stream_parse_tests).

-include_lib("couch/include/couch_eunit.hrl").

-define(CASES, [
    {1, "1", "integer numeric literial"},
    % text representation may truncate, trail zeroes
    {3.1416, "3.14160", "float numeric literal"},
    {-1, "-1", "negative integer numeric literal"},
    {-3.1416, "-3.14160", "negative float numeric literal"},
    {12.0e10, "1.20000e+11", "float literal in scientific notation"},
    {1.234E+10, "1.23400e+10", "another float literal in scientific notation"},
    {-1.234E-10, "-1.23400e-10", "negative float literal in scientific notation"},
    {10.0, "1.0e+01", "yet another float literal in scientific notation"},
    {123.456, "1.23456E+2", "yet another float literal in scientific notation"},
    {10.0, "1e1", "yet another float literal in scientific notation"},
    {<<"foo">>, "\"foo\"", "string literal"},
    {<<"foo", 5, "bar">>, "\"foo\\u0005bar\"", "string literal with \\u0005"},
    {<<"">>, "\"\"", "empty string literal"},
    {<<"\n\n\n">>, "\"\\n\\n\\n\"", "only new lines literal"},
    {<<"\" \b\f\r\n\t\"">>, "\"\\\" \\b\\f\\r\\n\\t\\\"\"", "only white spaces string literal"},
    {null, "null", "null literal"},
    {true, "true", "true literal"},
    {false, "false", "false literal"},
    {<<"null">>, "\"null\"", "null string literal"},
    {<<"true">>, "\"true\"", "true string literal"},
    {<<"false">>, "\"false\"", "false string literal"},
    {{[]}, "{}", "empty object literal"},
    {{[{<<"foo">>, <<"bar">>}]}, "{\"foo\":\"bar\"}", "simple object literal"},
    {
        {[{<<"foo">>, <<"bar">>}, {<<"baz">>, 123}]},
        "{\"foo\":\"bar\",\"baz\":123}",
        "another simple object literal"
    },
    {[], "[]", "empty array literal"},
    {[[]], "[[]]", "empty array literal inside a single element array literal"},
    {[1, <<"foo">>], "[1,\"foo\"]", "simple non-empty array literal"},
    {[1199344435545.0, 1], "[1199344435545.0,1]", "another simple non-empty array literal"},
    {[false, true, 321, null], "[false, true, 321, null]", "array of literals"},
    {{[{<<"foo">>, [123]}]}, "{\"foo\":[123]}", "object literal with an array valued property"},
    {{[{<<"foo">>, {[{<<"bar">>, true}]}}]}, "{\"foo\":{\"bar\":true}}", "nested object literal"},
    {
        {[
            {<<"foo">>, []},
            {<<"bar">>, {[{<<"baz">>, true}]}},
            {<<"alice">>, <<"bob">>}
        ]},
        "{\"foo\":[],\"bar\":{\"baz\":true},\"alice\":\"bob\"}",
        "complex object literal"
    },
    {
        [-123, <<"foo">>, {[{<<"bar">>, []}]}, null],
        "[-123,\"foo\",{\"bar\":[]},null]",
        "complex array literal"
    }
]).

raw_json_input_test_() ->
    Tests = lists:map(
        fun({EJson, JsonString, Desc}) ->
            {Desc, ?_assert(equiv(EJson, json_stream_parse:to_ejson(JsonString)))}
        end,
        ?CASES
    ),
    {"Tests with raw JSON string as the input", Tests}.

one_byte_data_fun_test_() ->
    Tests = lists:map(
        fun({EJson, JsonString, Desc}) ->
            DataFun = fun() -> single_byte_data_fun(JsonString) end,
            {Desc, ?_assert(equiv(EJson, json_stream_parse:to_ejson(DataFun)))}
        end,
        ?CASES
    ),
    {"Tests with a 1 byte output data function as the input", Tests}.

test_multiple_bytes_data_fun_test_() ->
    Tests = lists:map(
        fun({EJson, JsonString, Desc}) ->
            DataFun = fun() -> multiple_bytes_data_fun(JsonString) end,
            {Desc, ?_assert(equiv(EJson, json_stream_parse:to_ejson(DataFun)))}
        end,
        ?CASES
    ),
    {"Tests with a multiple bytes output data function as the input", Tests}.

%% Test for equivalence of Erlang terms.
%% Due to arbitrary order of construction, equivalent objects might
%% compare unequal as erlang terms, so we need to carefully recurse
%% through aggregates (tuples and objects).
equiv({Props1}, {Props2}) ->
    equiv_object(Props1, Props2);
equiv(L1, L2) when is_list(L1), is_list(L2) ->
    equiv_list(L1, L2);
equiv(N1, N2) when is_number(N1), is_number(N2) ->
    N1 == N2;
equiv(B1, B2) when is_binary(B1), is_binary(B2) ->
    B1 == B2;
equiv(true, true) ->
    true;
equiv(false, false) ->
    true;
equiv(null, null) ->
    true.

%% Object representation and traversal order is unknown.
%% Use the sledgehammer and sort property lists.
equiv_object(Props1, Props2) ->
    L1 = lists:keysort(1, Props1),
    L2 = lists:keysort(1, Props2),
    Pairs = lists:zip(L1, L2),
    true = lists:all(
        fun({{K1, V1}, {K2, V2}}) ->
            equiv(K1, K2) andalso equiv(V1, V2)
        end,
        Pairs
    ).

%% Recursively compare tuple elements for equivalence.
equiv_list([], []) ->
    true;
equiv_list([V1 | L1], [V2 | L2]) ->
    equiv(V1, V2) andalso equiv_list(L1, L2).

single_byte_data_fun([]) ->
    done;
single_byte_data_fun([H | T]) ->
    {<<H>>, fun() -> single_byte_data_fun(T) end}.

multiple_bytes_data_fun([]) ->
    done;
multiple_bytes_data_fun(L) ->
    N = rand:uniform(7) - 1,
    {Part, Rest} = split(L, N),
    {list_to_binary(Part), fun() -> multiple_bytes_data_fun(Rest) end}.

split(L, N) when length(L) =< N ->
    {L, []};
split(L, N) ->
    take(N, L, []).

take(0, L, Acc) ->
    {lists:reverse(Acc), L};
take(N, [H | L], Acc) ->
    take(N - 1, L, [H | Acc]).
