-module(lager_rotator_default).

-include_lib("kernel/include/file.hrl").

-behaviour(lager_rotator_behaviour).

-export([
    create_logfile/2, open_logfile/2, ensure_logfile/4, rotate_logfile/2
]).

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

create_logfile(Name, Buffer) ->
    open_logfile(Name, Buffer).

open_logfile(Name, Buffer) ->
    case filelib:ensure_dir(Name) of
        ok ->
            Options = [append, raw] ++
            case  Buffer of
                {Size, Interval} when is_integer(Interval), Interval >= 0, is_integer(Size), Size >= 0 ->
                    [{delayed_write, Size, Interval}];
                _ -> []
            end,
            case file:open(Name, Options) of
                {ok, FD} ->
                    case file:read_file_info(Name) of
                        {ok, FInfo} ->
                            Inode = FInfo#file_info.inode,
                            {ok, {FD, Inode, FInfo#file_info.size}};
                        X -> X
                    end;
                Y -> Y
            end;
        Z -> Z
    end.

ensure_logfile(Name, FD, Inode, Buffer) ->
    case file:read_file_info(Name) of
        {ok, FInfo} ->
            Inode2 = FInfo#file_info.inode,
            case Inode == Inode2 of
                true ->
                    {ok, {FD, Inode, FInfo#file_info.size}};
                false ->
                    %% delayed write can cause file:close not to do a close
                    _ = file:close(FD),
                    _ = file:close(FD),
                    case open_logfile(Name, Buffer) of
                        {ok, {FD2, Inode3, Size}} ->
                            %% inode changed, file was probably moved and
                            %% recreated
                            {ok, {FD2, Inode3, Size}};
                        Error ->
                            Error
                    end
            end;
        _ ->
            %% delayed write can cause file:close not to do a close
            _ = file:close(FD),
            _ = file:close(FD),
            case open_logfile(Name, Buffer) of
                {ok, {FD2, Inode3, Size}} ->
                    %% file was removed
                    {ok, {FD2, Inode3, Size}};
                Error ->
                    Error
            end
    end.

%% renames failing are OK
rotate_logfile(File, 0) ->
    file:delete(File);
rotate_logfile(File, 1) ->
    case file:rename(File, File++".0") of
        ok ->
            ok;
        _ ->
            rotate_logfile(File, 0)
    end;
rotate_logfile(File, Count) ->
    _ = file:rename(File ++ "." ++ integer_to_list(Count - 2), File ++ "." ++ integer_to_list(Count - 1)),
    rotate_logfile(File, Count - 1).

-ifdef(TEST).

rotate_file_test() ->
    RotCount = 10,
    TestDir = lager_util:create_test_dir(),
    TestLog = filename:join(TestDir, "rotation.log"),
    Outer = fun(N) ->
        ?assertEqual(ok, file:write_file(TestLog, erlang:integer_to_list(N))),
        Inner = fun(M) ->
            File = lists:flatten([TestLog, $., erlang:integer_to_list(M)]),
            ?assert(filelib:is_regular(File)),
            %% check the expected value is in the file
            Number = erlang:list_to_binary(integer_to_list(N - M - 1)),
            ?assertEqual({ok, Number}, file:read_file(File))
        end,
        Count = erlang:min(N, RotCount),
        % The first time through, Count == 0, so the sequence is empty,
        % effectively skipping the inner loop so a rotation can occur that
        % creates the file that Inner looks for.
        % Don't shoot the messenger, it was worse before this refactoring.
        lists:foreach(Inner, lists:seq(0, Count-1)),
        rotate_logfile(TestLog, RotCount)
    end,
    lists:foreach(Outer, lists:seq(0, (RotCount * 2))),
    lager_util:delete_test_dir(TestDir).

rotate_file_fail_test() ->
    TestDir = lager_util:create_test_dir(),
    TestLog = filename:join(TestDir, "rotation.log"),
    %% set known permissions on it
    os:cmd("chmod -R u+rwx " ++ TestDir),
    %% write a file
    file:write_file(TestLog, "hello"),
    %% hose up the permissions
    os:cmd("chmod u-w " ++ TestDir),
    ?assertMatch({error, _}, rotate_logfile(TestLog, 10)),
    ?assert(filelib:is_regular(TestLog)),
    %% fix the permissions
    os:cmd("chmod u+w " ++ TestDir),
    ?assertMatch(ok, rotate_logfile(TestLog, 10)),
    ?assert(filelib:is_regular(TestLog ++ ".0")),
    ?assertEqual(false, filelib:is_regular(TestLog)),
    lager_util:delete_test_dir(TestDir).

-endif.
