%%%------------------------------------------------------------------------
%% 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.
%%
%% @private
%%%-------------------------------------------------------------------------
-module(otel_span_utils).

-export([start_span/5,
         end_span/1,
         end_span/2]).

-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include("otel_sampler.hrl").
-include("otel_span.hrl").

-spec start_span(otel_ctx:t(), opentelemetry:span_name(), otel_sampler:t(), otel_id_generator:t(),
                 otel_span:start_config()) -> {opentelemetry:span_ctx(), opentelemetry:span() | undefined}.
start_span(Ctx, Name, Sampler, IdGenerator, Opts) ->
    SpanAttributeCountLimit = otel_span_limits:attribute_count_limit(),
    SpanAttributeValueLengthLimit= otel_span_limits:attribute_value_length_limit(),
    EventCountLimit = otel_span_limits:event_count_limit(),
    LinkCountLimit = otel_span_limits:link_count_limit(),
    AttributePerEventLimit = otel_span_limits:attribute_per_event_limit(),
    AttributePerLinkLimit = otel_span_limits:attribute_per_link_limit(),


    Attributes = otel_attributes:new(maps:get(attributes, Opts),
                                     SpanAttributeCountLimit,
                                     SpanAttributeValueLengthLimit),
    Links = otel_links:new(maps:get(links, Opts),
                           LinkCountLimit,
                           AttributePerLinkLimit,
                           SpanAttributeValueLengthLimit),
    Events = otel_events:new(EventCountLimit, AttributePerEventLimit, SpanAttributeValueLengthLimit),

    Kind = maps:get(kind, Opts),
    StartTime = maps:get(start_time, Opts),

    new_span(Ctx, Name, Sampler, IdGenerator, StartTime, Kind, Attributes, Events, Links).

new_span(Ctx, Name, Sampler, IdGeneratorModule, StartTime, Kind, Attributes, Events, Links) ->
    {NewSpanCtx, ParentSpanId, ParentIsRemote} = new_span_ctx(Ctx, IdGeneratorModule),

    TraceId = NewSpanCtx#span_ctx.trace_id,
    SpanId = NewSpanCtx#span_ctx.span_id,

    {TraceFlags, IsRecording, SamplerAttributes, TraceState} =
        sample(Ctx, Sampler, TraceId, Links, Name, Kind, otel_attributes:map(Attributes)),

    Span = #span{trace_id=TraceId,
                 span_id=SpanId,
                 tracestate=TraceState,
                 start_time=StartTime,
                 parent_span_id=ParentSpanId,
                 parent_span_is_remote=ParentIsRemote,
                 kind=Kind,
                 name=Name,
                 attributes=otel_attributes:set(SamplerAttributes, Attributes),
                 events=Events,
                 links=Links,
                 trace_flags=TraceFlags,
                 is_recording=IsRecording},

    {NewSpanCtx#span_ctx{trace_flags=TraceFlags,
                         tracestate=TraceState,
                         is_valid=true,
                         is_recording=IsRecording}, Span}.

-spec new_span_ctx(otel_ctx:t(), otel_id_generator:t()) ->
          {opentelemetry:span_ctx(), opentelemetry:span_id() | undefined, boolean() | undefined}.
new_span_ctx(Ctx, IdGeneratorModule) ->
    case otel_tracer:current_span_ctx(Ctx) of
        undefined ->
            {root_span_ctx(IdGeneratorModule), undefined, undefined};
        #span_ctx{is_valid=false} ->
            {root_span_ctx(IdGeneratorModule), undefined, undefined};
        ParentSpanCtx=#span_ctx{span_id=ParentSpanId, is_remote=ParentIsRemote} ->
            %% keep the rest of the parent span ctx, simply need to update the span_id
            SpanId = IdGeneratorModule:generate_span_id(),
            HexSpanId = otel_utils:encode_hex(<<SpanId:64>>),
            {ParentSpanCtx#span_ctx{span_id=SpanId, hex_span_id=HexSpanId}, ParentSpanId, ParentIsRemote}
    end.

root_span_ctx(IdGeneratorModule) ->
    TraceId = IdGeneratorModule:generate_trace_id(),
    HexTraceId = otel_utils:encode_hex(<<TraceId:128>>),
    SpanId = IdGeneratorModule:generate_span_id(),
    HexSpanId = otel_utils:encode_hex(<<SpanId:64>>),
    #span_ctx{trace_id=TraceId,
              hex_trace_id=HexTraceId,
              span_id=SpanId,
              hex_span_id=HexSpanId,
              is_valid=true,
              trace_flags=0}.

%%--------------------------------------------------------------------
%% @doc
%% Set the end time for a span if it hasn't been set before.
%% @end
%%--------------------------------------------------------------------
-spec end_span(opentelemetry:span()) -> opentelemetry:span().
end_span(Span=#span{end_time=undefined,
                    trace_flags=TraceFlags}) when ?IS_SAMPLED(TraceFlags) ->
    end_span(Span, undefined);
end_span(Span) ->
    Span.

-spec end_span(#span{}, integer() | undefined) -> opentelemetry:span().
end_span(Span, Timestamp) when is_integer(Timestamp) ->
    Span#span{end_time=Timestamp};
end_span(Span, _) ->
    Span#span{end_time=opentelemetry:timestamp()}.
%%

sample(Ctx, Sampler, TraceId, Links, SpanName, Kind, Attributes) ->
    {Decision, NewAttributes, TraceState} = otel_sampler:should_sample(
        Sampler, Ctx, TraceId, Links, SpanName, Kind, Attributes
    ),
    case Decision of
        ?DROP ->
            {0, false, NewAttributes, TraceState};
        ?RECORD_ONLY ->
            {0, true, NewAttributes, TraceState};
        ?RECORD_AND_SAMPLE ->
            {1, true, NewAttributes, TraceState}
    end.
