-module(eini_tests).

-author('shino@accense.com').

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

-import(eini, [parse/1]).

setup() ->
  ok.

teardown(_) ->
  ok.

empty_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    ?_assertEqual({ok, []}, parse("")),
    ?_assertEqual({ok, []}, parse("\n"))
   ]}.

one_section_title_only_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% comment only
    ?_assertEqual({ok, []},
                  parse(
                    ";"
                   )),
    ?_assertEqual({ok, []},
                  parse(
                    ";    "
                   )),
    ?_assertEqual({ok, []},
                  parse(
                    "; comment"
                   )),
    ?_assertEqual({ok, []},
                  parse(
                    "; comment in Japanese 日本語"
                   )),
    %% Title only
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "[title]\n"
                   )),
    %% Title only, but trailing spaces
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "[title]  \n"
                   )),
    %% Title only, but comment lines
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "; comment line\n"
                    "  \n"
                    "[title]\n"
                   )),
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "; comment line\n"
                    "; comment line 2\n"
                    "  \n"
                    "[title]\n"
                   )),
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "; comment line\n"
                    "; comment line 2\n"
                    "  \n"
                    "[title]\n"
                    "; comment after section title"
                   )),
    %% Title only, but preceding blank lines
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "\n"
                    "  \n"
                    "[title]\n"
                   )),
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "  \n"
                    "\n"
                    "[title]\n"
                   )),
    %% Title only, but preceding blank lines and trailing spaces
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "\n"
                    "  \n"
                    "[title]\t\s\n"
                   )),
    %% Title only, but trailing blank lines
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "[title]\n"
                    "\n"
                    "  \n"
                    "\n"
                   )),
    %% Title only, but trailing spaces and trailing blank lines
    ?_assertEqual({ok, [
                        {title, []}
                       ]},
                  parse(
                    "[title]  \n"
                    "\n"
                    "\n"
                   ))
   ]}.

one_section_title_only_syntax_error_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% No ] char
    ?_assertMatch({error, {syntax_error, 1, _Reason}},
                  parse(
                    "[title\n"
                   ))
   ]}.

one_section_title_and_one_prop_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% Simple case
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]\n"
                    "key1=value1\n"
                   )),
    %% title has trailing spaces
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=value1\n"
                   )),
    %% Single blank line between title and a prop
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "\n"
                    "key1=value1\n"
                   )),
    %% Single comment line between title and a prop
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "; comment\n"
                    "key1=value1\n"
                   )),
    %% Single comment line after a prop
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=value1\n"
                    "; comment\n"
                   )),
    %% Multi blank lines between title and a prop
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "\n"
                    "    \n"
                    "\n"
                    "key1=value1\n"
                   )),
    %% Multi blank lines after a prop
    ?_assertEqual({ok, [
                        {title, 
                         [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=value1\n"
                    "\n"
                    "    \n"
                    "\n"
                   )),
    %% Multi blank lines between title and a prop and
    %% multi blank lines after a prop
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "\n"
                    "    \n"
                    "\n"
                    "key1=value1\n"
                    "\n"
                    "    \n"
                    "\n"
                   )),

    %% value has [ char
    ?_assertEqual({ok, [
                        {title, [{key1, <<"va[lue1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=va[lue1\n"
                   )),
    %% value has ] char
    ?_assertEqual({ok, [
                        {title, [{key1, <<"valu]e1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=valu]e1\n"
                   )),
    %% value has [ and ] chars
    ?_assertEqual({ok, [
                        {title, [{key1, <<"va[lu]e1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=va[lu]e1\n"
                   )),
    %% value has < and > chars
    ?_assertEqual({ok, [
                        {title, [{key1, <<"va<lu>e1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=va<lu>e1\n"
                   )),
    %% value has ; char
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1;continue">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=value1;continue\n"
                   )),
    %% key has preceding spaces
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "  key1=value1\n"
                   )),
    %% key has trailing spaces
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1   =value1\n"
                   )),
    %% key has preceding and trailing spaces
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "   key1   =value1\n"
                   )),
    %% value has preceding and trailing spaces
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1">>}]}
                       ]},
                  parse(
                    "[title]  \n"
                    "key1=  value1  \n"
                   )),
    %% value has characters which can not used in titles or keys
    ?_assertEqual({ok, [
                        {title, [{key1, <<"value1$% '""#!+*=@/:+">>}]}
                       ]},
                  parse(
                    "[title]\n"
                    "key1=value1$% '""#!+*=@/:+\n"
                   ))
   ]}.

one_section_title_and_two_props_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% Simple case
    ?_assertEqual({ok, [
                        {title,
                         [{key1, <<"value1">>},
                          {key2, <<"value2">>}]}
                       ]},
                  parse(
                    "[title]\n"
                    "key1=value1\n"
                    "key2=value2\n"
                   )),
    %% Blank lines
    ?_assertEqual({ok, [
                        {title,
                         [{key1, <<"value1">>},
                          {key2, <<"value2">>}]}
                       ]},
                  parse(
                    "[title]\n"
                    "\n"
                    "key1=value1\n"
                    "  \n"
                    "\n"
                    "key2=value2\n"
                    "\n"
                    "\n"
                   ))
   ]}.

two_section_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    ?_assertEqual({ok, [
                        {title_A, []},
                        {'title-B', []}
                       ]},
                  parse(
                    "[title_A]\n"
                    "[title-B]\n"
                   )),
    ?_assertEqual({ok, [
                        {'Title_A',
                         [{'Key_A1', <<"value_A1">>}]},
                        {'Title-B',
                         [{'Key-B1', <<"value-B1">>}]}
                       ]},
                  parse(
                    "[Title_A]\n"
                    "Key_A1=value_A1\n"
                    "[Title-B]  \n"
                    "Key-B1=value-B1\n"
                   ))
   ]}.

binary_two_section_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    ?_assertEqual({ok, [
                        {titleA, []},
                        {titleB, []}
                       ]},
                  parse(
                    "[titleA]\n"
                    "[titleB]\n"
                   )),
    ?_assertEqual({ok, [
                        {titleA,
                         [{keyA1, <<"valueA1">>}]},
                        {titleB,
                         [{keyB1, <<"valueB1">>}]}
                       ]},
                  parse(
                    <<"[titleA]\n"
                      "keyA1=valueA1\n"
                      "[titleB]  \n"
                      "keyB1=valueB1\n">>
                   ))
   ]}.

lex_error_title_test_() ->
  {setup,
   fun setup/0,
   fun teardown/ 1,
   [
    %% vertical tab in section title
    ?_assertMatch({error, {illegal_character, 1, _Reason}},
                  parse("[ti\vtle]")),
    ?_assertMatch({error, {illegal_character, 3, _Reason}},
                  parse(
                    "[titleA]\n"
                    "keyA1=valueA1\n"
                    "[tit\vleB]  \n"
                    "keyB1=valueB1\n"
                   ))
   ]}.
  
syntax_error_title_test_() ->
  %% TODO: Erlang 17 lost the ability to correctly report line numbers from errors.
  %% Put the numbers back in some day when the fix is released
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% blank char before section title
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("  [title]")),
    %% blank char before section title, with a preceding empty line
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("\n"
                        "  [title]")),
    %% blank char before section title, with preceding empty lines
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("\n"
                        "\n"
                        "  [title]")),
    %% blank char before section title, with preceding blank lines
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("  \n"
                        "\t\n"
                        "  [title]")),
    %% blank char before section title, with preceding comment lines
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("; comment 1\n"
                        ";\n"
                        "; comment 2\n"
                        "  [title]")),
    %% blank char in section title
    ?_assertMatch({error, {syntax_error, _, _Reason}},
                  parse(
                    "[titleA]\n"
                    "keyA1=valueA1\n"
                    "[tit  leB]\n"
                    "keyB1=valueB1\n"
                   )),
    %% comment after title
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("[title] ;comment")),
    %% comment after blank
    ?_assertMatch({error, {syntax_error, _, ["syntax error before: ", _]}},
                  parse("[title]\n"
                        " ;comment\n"))
   ]}.
  
syntax_error_property_test_() ->
  {setup,
   fun setup/0,
   fun teardown/1,
   [
    %% blank char in key
    ?_assertMatch({error, {syntax_error, 2, _Reason}},
                  parse(
                    "[title]\n"
                    "key with blank=value\n"
                   )),
    %% comment after blank
    ?_assertMatch({error, {syntax_error, 2, ["syntax error before: ", _]}},
                  parse("[title]\n"
                        "key;comment=value\n"))
   ]}.
  
dup_title_test_() ->
  {setup,
   fun setup/0,
   fun teardown/ 1,
   [
    ?_assertEqual({error, {duplicate_title, titleA}},
                  parse(
                    "[titleA]\n"
                    "keyA1=valueA1\n"
                    "[titleA]  \n"
                    "keyB1=valueB1\n"
                   ))
   ]}.
  
dup_key_test_() ->
  {setup,
   fun setup/0,
   fun teardown/ 1,
   [
    ?_assertEqual({error, {duplicate_key, titleB, key2}},
                  parse(
                    "[titleA]\n"
                    "key1=value1\n"
                    "[titleB]  \n"
                    "key1=value1\n"
                    "key2=value2\n"
                    "key3=value3\n"
                    "key2=value4\n"
                   )),
    ?_assertEqual({error, {duplicate_key, titleB, key2}},
                  parse(
                    "[titleA]\n"
                    "key1=value1\n"
                    "[titleB]  \n"
                    "key1=value1\n"
                    "key2=value2\n"
                    "key3=value3\n"
                    "key2  =  value4\n"
                   ))
   ]}.

register_test_() ->
  {foreach,
    fun() ->
      application:start(eini)
    end,
    fun(_) ->
      application:stop(eini)
    end,
    [
      {"syntax Error",
        ?_assertMatch({error, {syntax_error, 1, _Reason}},
                       eini:register("spam.ini" ,"[title\n"))},
      {"",
        fun() ->
          ?assertEqual(ok,
                       eini:register("spam.ini", "[title]\nkey=value")),
          ?assertEqual(<<"value">>,
                       eini:lookup_value("spam.ini", title, key)),
          ?assertEqual(not_found,
                       eini:lookup_value("spam.ini", title, key1)),
          ?assertEqual(ok,
                       eini:register("spam.ini", title, key1, <<"value">>)),
          ?assertEqual(<<"value">>,
                       eini:lookup_value("spam.ini", title, key1)),
          ?assertEqual({error, {duplicate_key, title, key1}},
                       eini:register("spam.ini", title, key1, <<"value">>)),
          ?assertEqual({error, {duplicate_key, title, key}},
                       eini:register("spam.ini", "[title]\nkey=value"))
        end}
    ]
  }.

is_section_test_() ->
  {foreach,
    fun() ->
      application:start(eini)
    end,
    fun(_) ->
      application:stop(eini)
    end,
    [
      {"",
        fun() ->
          ?assertEqual(ok,
                       eini:register("spam.ini", "[title]\nkey=value")),
          ?assertEqual(true,
                       eini:is_section("spam.ini", title)),
          ?assertEqual(false,
                       eini:is_section("spam.ini", title1))
        end}
    ]
  }.