#!/usr/bin/env escript
%% -*- erlang -*-

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

main(_) ->
    test_util:init_code_path(),
    etap:plan(28),
    case (catch test()) of
        ok ->
            etap:end_tests();
        Other ->
            etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
            etap:bail(Other)
    end,
    ok.

get_task_prop(Pid, Prop) ->
    From = list_to_binary(pid_to_list(Pid)),
    Element = lists:foldl(
        fun(PropList,Acc) ->
            case couch_util:get_value(pid,PropList) of
                From ->
                    [PropList | Acc];
                _ ->
                    []
            end
        end,
        [], couch_task_status:all()
    ),
    case couch_util:get_value(Prop, hd(Element), nil) of
    nil ->
        etap:bail("Could not get property '" ++ couch_util:to_list(Prop) ++
            "' for task " ++ pid_to_list(Pid));
    Value ->
        Value
    end.


loop() ->
    receive
    {add, Props, From} ->
        Resp = couch_task_status:add_task(Props),
        From ! {ok, self(), Resp},
        loop();
    {update, Props, From} ->
        Resp = couch_task_status:update(Props),
        From ! {ok, self(), Resp},
        loop();
    {update_frequency, Msecs, From} ->
        Resp = couch_task_status:set_update_frequency(Msecs),
        From ! {ok, self(), Resp},
        loop();
    {done, From} ->
        From ! {ok, self(), ok}
    end.

call(Pid, Command) ->
    Pid ! {Command, self()},
    wait(Pid).

call(Pid, Command, Arg) ->
    Pid ! {Command, Arg, self()},
    wait(Pid).

wait(Pid) ->
    receive
        {ok, Pid, Msg} -> Msg
    after 1000 ->
        throw(timeout_error)
    end.

test() ->
    {ok, TaskStatusPid} = couch_task_status:start_link(),

    TaskUpdater = fun() -> loop() end,
    % create three updaters
    Pid1 = spawn(TaskUpdater),
    Pid2 = spawn(TaskUpdater),
    Pid3 = spawn(TaskUpdater),

    ok = call(Pid1, add, [{type, replication}, {progress, 0}]),
    etap:is(
        length(couch_task_status:all()),
        1,
        "Started a task"
    ),
    Task1StartTime = get_task_prop(Pid1, started_on),
    etap:is(
        is_integer(Task1StartTime),
        true,
        "Task start time is defined."
    ),
    etap:is(
        get_task_prop(Pid1, updated_on),
        Task1StartTime,
        "Task's start time is the same as the update time before an update."
    ),

    etap:is(
        call(Pid1, add, [{type, compaction}, {progress, 0}]),
        {add_task_error, already_registered},
        "Unable to register multiple tasks for a single Pid."
    ),

    etap:is(
        get_task_prop(Pid1, type),
        replication,
        "Task type is 'replication'."
    ),
    etap:is(
        get_task_prop(Pid1, progress),
        0,
        "Task progress is 0."
    ),

    ok = timer:sleep(1000),
    call(Pid1, update, [{progress, 25}]),
    etap:is(
        get_task_prop(Pid1, progress),
        25,
        "Task progress is 25."
    ),
    etap:is(
        get_task_prop(Pid1, updated_on) > Task1StartTime,
        true,
        "Task's last update time has increased after an update."
    ),

    call(Pid2, add, [{type, compaction}, {progress, 0}]),
    etap:is(
        length(couch_task_status:all()),
        2,
        "Started a second task."
    ),
    Task2StartTime = get_task_prop(Pid2, started_on),
    etap:is(
        is_integer(Task2StartTime),
        true,
        "Second task's start time is defined."
    ),
    etap:is(
        get_task_prop(Pid2, updated_on),
        Task2StartTime,
        "Second task's start time is the same as the update time before an update."
    ),

    etap:is(
        get_task_prop(Pid2, type),
        compaction,
        "Second task's type is 'compaction'."
    ),
    etap:is(
        get_task_prop(Pid2, progress),
        0,
        "Second task's progress is 0."
    ),

    ok = timer:sleep(1000),
    call(Pid2, update, [{progress, 33}]),
    etap:is(
        get_task_prop(Pid2, progress),
        33,
        "Second task's progress updated to 33."
    ),
    etap:is(
        get_task_prop(Pid2, updated_on) > Task2StartTime,
        true,
        "Second task's last update time has increased after an update."
    ),

    call(Pid3, add, [{type, indexer}, {progress, 0}]),
    etap:is(
        length(couch_task_status:all()),
        3,
        "Registered a third task."
    ),
    Task3StartTime = get_task_prop(Pid3, started_on),
    etap:is(
        is_integer(Task3StartTime),
        true,
        "Third task's start time is defined."
    ),
    etap:is(
        get_task_prop(Pid3, updated_on),
        Task3StartTime,
        "Third task's start time is the same as the update time before an update."
    ),

    etap:is(
        get_task_prop(Pid3, type),
        indexer,
        "Third task's type is 'indexer'."
    ),
    etap:is(
        get_task_prop(Pid3, progress),
        0,
        "Third task's progress is 0."
    ),

    ok = timer:sleep(1000),
    call(Pid3, update, [{progress, 50}]),
    etap:is(
        get_task_prop(Pid3, progress),
        50,
        "Third task's progress updated to 50."
    ),
    etap:is(
        get_task_prop(Pid3, updated_on) > Task3StartTime,
        true,
        "Third task's last update time has increased after an update."
    ),

    call(Pid3, update_frequency, 500),
    call(Pid3, update, [{progress, 66}]),
    etap:is(
        get_task_prop(Pid3, progress),
        66,
        "Third task's progress updated to 66."
    ),

    call(Pid3, update, [{progress, 67}]),
    etap:is(
        get_task_prop(Pid3, progress),
        66,
        "Task update dropped because of frequency limit."
    ),

    call(Pid3, update_frequency, 0),
    call(Pid3, update, [{progress, 77}]),
    etap:is(
        get_task_prop(Pid3, progress),
        77,
        "Task updated after reseting frequency limit."
    ),


    call(Pid1, done),
    etap:is(
        length(couch_task_status:all()),
        2,
        "First task finished."
    ),

    call(Pid2, done),
    etap:is(
        length(couch_task_status:all()),
        1,
        "Second task finished."
    ),

    call(Pid3, done),
    etap:is(
        length(couch_task_status:all()),
        0,
        "Third task finished."
    ),

    erlang:monitor(process, TaskStatusPid),
    couch_task_status:stop(),
    receive
        {'DOWN', _, _, TaskStatusPid, _} ->
            ok
    after
        1000 ->
            throw(timeout_error)
    end,

    ok.
