%%%------------------------------------------------------------------------
%% Copyright 2019, OpenTelemetry Authors
%% 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.
%%
%% @doc An {@link otel_sampler} that makes the decision based on the parent.
%%
%% This sampler decides with the following possibilities:
%% <ol>
%%   <li>a remote parent that is sampled (by default `always_on');</li>
%%   <li>a remote parent that is not sampled (by default `always_off');</li>
%%   <li>a local parent that is sampled (by default `always_on');</li>
%%   <li>a local parent that is not sampled (by default `always_off');</li>
%%   <li>no parent (by default `always_on').</li>
%% </ol>
%%
%% For each of these cases a different sampler can be configured.
%% For options, see {@link opts()}.
%%
%% This is one of the
%% <a href="https://opentelemetry.io/docs/specs/otel/trace/sdk/#built-in-samplers">built-in
%% samplers</a> provided by the OpenTelemetry SDK.
%% @end
%%%-------------------------------------------------------------------------
-module(otel_sampler_parent_based).

-behavior(otel_sampler).

-export([description/1, setup/1, should_sample/7]).

-export_type([opts/0]).

-include_lib("kernel/include/logger.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").

-type opts() :: #{
    remote_parent_sampled => otel_sampler:sampler_spec(),
    remote_parent_not_sampled => otel_sampler:sampler_spec(),
    local_parent_sampled => otel_sampler:sampler_spec(),
    local_parent_not_sampled => otel_sampler:sampler_spec(),
    root => otel_sampler:sampler_spec()
}.
%% Options to configure this sampler.

%% @private
setup(Opts = #{root := RootSpec}) ->
    RemoteParentSampledSampler = sampler_for_spec(remote_parent_sampled, Opts, always_on),
    RemoteParentNotSampledSampler = sampler_for_spec(remote_parent_not_sampled, Opts, always_off),
    LocalParentSampledSampler = sampler_for_spec(local_parent_sampled, Opts, always_on),
    LocalParentNotSampledSampler = sampler_for_spec(local_parent_not_sampled, Opts, always_off),
    RootSampler = otel_sampler:new(RootSpec),
    #{
        root => RootSampler,
        remote_parent_sampled => RemoteParentSampledSampler,
        remote_parent_not_sampled => RemoteParentNotSampledSampler,
        local_parent_sampled => LocalParentSampledSampler,
        local_parent_not_sampled => LocalParentNotSampledSampler
    };
setup(Opts) ->
    ?LOG_INFO("No sampler spec found for parent_based 'root' option. The 'always_on' sampler will be used for root spans."),
    setup(Opts#{root => always_on}).

sampler_for_spec(Key, Opts, DefaultModule) ->
    Spec = maps:get(Key, Opts, DefaultModule),
    otel_sampler:new(Spec).

%% @private
description(#{
    root := RootSampler,
    remote_parent_sampled := RemoteParentSampler,
    remote_parent_not_sampled := RemoteParentNotSampler,
    local_parent_sampled := LocalParentSampler,
    local_parent_not_sampled := LocalParentNotSampler
}) ->
    <<"ParentBased{root:", (otel_sampler:description(RootSampler))/binary, ",remoteParentSampled:",
        (otel_sampler:description(RemoteParentSampler))/binary, ",remoteParentNotSampled:",
        (otel_sampler:description(RemoteParentNotSampler))/binary, ",localParentSampled:",
        (otel_sampler:description(LocalParentSampler))/binary, ",localParentNotSampled:",
        (otel_sampler:description(LocalParentNotSampler))/binary, "}">>.

%% @private
should_sample(Ctx, TraceId, Links, SpanName, SpanKind, Attributes, Config) ->
    ParentSpanCtx = otel_tracer:current_span_ctx(Ctx),
    SamplerKey = parent_based_sampler(ParentSpanCtx),
    {Sampler, _Description, SamplerOpts} = maps:get(SamplerKey, Config),
    Sampler:should_sample(Ctx, TraceId, Links, SpanName, SpanKind, Attributes, SamplerOpts).

%% remote parent sampled
parent_based_sampler(#span_ctx{trace_flags = TraceFlags, is_remote = true}) when
    ?IS_SAMPLED(TraceFlags)
->
    remote_parent_sampled;
%% remote parent not sampled
parent_based_sampler(#span_ctx{is_remote = true}) ->
    remote_parent_not_sampled;
%% local parent sampled
parent_based_sampler(#span_ctx{trace_flags = TraceFlags}) when
    ?IS_SAMPLED(TraceFlags)
->
    local_parent_sampled;
%% local parent not sampled
parent_based_sampler(#span_ctx{}) ->
    local_parent_not_sampled;
%% root
parent_based_sampler(_SpanCtx) ->
    root.
