%%%-------------------------------------------------------------------
%%% @doc Dynamic supervisor for container deployment tasks.
%%% @end
%%%-------------------------------------------------------------------
-module(iot_container_task_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).
-export([ensure_started/2, stream/4, close/3, fail/3]).

%% supervisor callbacks
-export([init/1]).

-define(SERVER, ?MODULE).

%%%===================================================================
%%% API
%%%===================================================================

-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

-spec ensure_started(binary(), integer()) -> {ok, pid()} | {error, term()}.
ensure_started(UUID, TaskId) when is_binary(UUID), is_integer(TaskId) ->
    case iot_container_task:get_pid(UUID, TaskId) of
        undefined ->
            case supervisor:start_child(?SERVER, [UUID, TaskId]) of
                {ok, Pid} ->
                    {ok, Pid};
                {ok, Pid, _Info} ->
                    {ok, Pid};
                {error, {already_started, Pid}} ->
                    {ok, Pid};
                {error, {shutdown, {failed_to_start_child, _Id, {already_started, Pid}}}} ->
                    {ok, Pid};
                {error, Reason} ->
                    {error, Reason}
            end;
        Pid when is_pid(Pid) ->
            {ok, Pid}
    end.

-spec stream(binary(), integer(), binary(), binary()) -> ok.
stream(UUID, TaskId, Type, Stream)
  when is_binary(UUID), is_integer(TaskId), is_binary(Type), is_binary(Stream) ->
    case ensure_started(UUID, TaskId) of
        {ok, Pid} ->
            iot_container_task:stream(Pid, Type, Stream);
        {error, Reason} ->
            logger:warning("[iot_container_task_sup] start task failed, uuid: ~p, task_id: ~p, reason: ~p", [UUID, TaskId, Reason]),
            ok
    end.

-spec close(binary(), integer(), binary()) -> ok.
close(UUID, TaskId, Reason) when is_binary(UUID), is_integer(TaskId), is_binary(Reason) ->
    case ensure_started(UUID, TaskId) of
        {ok, Pid} ->
            iot_container_task:close(Pid, Reason);
        {error, StartReason} ->
            logger:warning("[iot_container_task_sup] start task before close failed, uuid: ~p, task_id: ~p, reason: ~p", [UUID, TaskId, StartReason]),
            ok
    end.

-spec fail(binary(), integer(), term()) -> ok.
fail(UUID, TaskId, Reason) when is_binary(UUID), is_integer(TaskId) ->
    ReasonBin = reason_to_binary(Reason),
    ok = stream(UUID, TaskId, <<"error">>, ReasonBin),
    close(UUID, TaskId, <<"fail">>).

%%%===================================================================
%%% supervisor callbacks
%%%===================================================================

-spec init([]) -> {ok, {{simple_one_for_one, non_neg_integer(), pos_integer()}, [supervisor:child_spec()]}}.
init([]) ->
    SupFlags = {simple_one_for_one, 10, 60},
    ChildSpec = #{
        id => iot_container_task,
        start => {iot_container_task, start_link, []},
        restart => temporary,
        shutdown => 5000,
        type => worker,
        modules => [iot_container_task]
    },
    {ok, {SupFlags, [ChildSpec]}}.

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

-spec reason_to_binary(term()) -> binary().
reason_to_binary(Reason) when is_binary(Reason) ->
    Reason;
reason_to_binary(timeout) ->
    <<"timeout">>;
reason_to_binary(Reason) ->
    unicode:characters_to_binary(io_lib:format("~p", [Reason])).
