# Copyright 2017 OpenCensus 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.


require "opencensus/context"
require "opencensus/trace/annotation"
require "opencensus/trace/config"
require "opencensus/trace/exporters"
require "opencensus/trace/formatters"
require "opencensus/trace/integrations"
require "opencensus/trace/link"
require "opencensus/trace/message_event"
require "opencensus/trace/samplers"
require "opencensus/trace/span_builder"
require "opencensus/trace/span_context"
require "opencensus/trace/span"
require "opencensus/trace/status"
require "opencensus/trace/trace_context_data"
require "opencensus/trace/truncatable_string"

module OpenCensus
  ##
  # # OpenCensus Trace API
  #
  # This is a Ruby implementation of OpenCensus Trace, providing a common API
  # for latency trace tools.
  #
  module Trace
    ##
    # Internal key for storing the current SpanContext in the thread local
    # Context
    #
    # @private
    #
    SPAN_CONTEXT_KEY = :__span_context__

    class << self
      ##
      # Sets the current thread-local SpanContext, which governs the behavior
      # of the span creation class methods of OpenCensus::Trace.
      #
      # @param [SpanContext] span_context
      #
      def span_context= span_context
        OpenCensus::Context.set SPAN_CONTEXT_KEY, span_context
      end

      ##
      # Unsets the current thread-local SpanContext, disabling span creation
      # class methods of OpenCensus::Trace
      #
      def unset_span_context
        OpenCensus::Context.unset SPAN_CONTEXT_KEY
      end

      ##
      # Returns the current thread-local SpanContext, which governs the
      # behavior of the span creation class methods of OpenCensus::Trace.
      # Returns `nil` if there is no current SpanContext.
      #
      # @return [SpanContext, nil]
      #
      def span_context
        OpenCensus::Context.get SPAN_CONTEXT_KEY
      end

      ##
      # Starts tracing a request in the current thread, by creating a new
      # SpanContext and setting it as the current thread-local context.
      # Generally you should call this when beginning the handling of a
      # request. If there is a rack environment or a provided traceparent
      # header, pass it in so the SpanContext is constructed accordingly.
      #
      # If you pass a block, this method will yield the SpanContext to the
      # block. When the block finishes, the span context will automatically
      # be unset. If you do not pass a block, this method will return the
      # SpanContext. You must then call `unset_span_context` yourself at the
      # end of the request
      #
      # @param [TraceContextData] trace_context The request's incoming trace
      #      context (optional)
      # @param [boolean, nil] same_process_as_parent Set to `true` if the parent
      #      span is local, `false` if it is remote, or `nil` if there is no
      #      parent span or this information is not available.
      #
      def start_request_trace trace_context: nil, same_process_as_parent: nil
        span_context = SpanContext.create_root \
          trace_context: trace_context,
          same_process_as_parent: same_process_as_parent
        self.span_context = span_context
        if block_given?
          begin
            yield span_context
          ensure
            unset_span_context
          end
        else
          span_context
        end
      end

      ##
      # Create a new span in the current thread-local context.
      # You must pass a name for the span. All other span attributes should
      # be set using {OpenCensus::Trace::SpanBuilder} methods.
      #
      # The span will be started automatically with the current timestamp.
      # However, you are responsible for finishing the span yourself.
      # Furthermore, the current thread-local SpanContext will be updated so
      # subsequent calls to `start_span` will create spans within the new span.
      #
      # You should always match `start_span` calls with a corresponding call to
      # `end_span`, which finishes the span and updates the SpanContext
      # accordingly. If you want this done automatically, consider using
      # the `in_span` method.
      #
      # Will throw an exception if there is no current SpanContext.
      #
      # @param [String] name Name of the span. Required.
      # @param [Symbol] kind Kind of span. Optional. Defaults to unspecified.
      #     Other allowed values are {OpenCensus::Trace::SpanBuilder::SERVER}
      #     and {OpenCensus::Trace::SpanBuilder::CLIENT}.
      # @param [Sampler,Boolean,nil] sampler Span-scoped sampler. Optional.
      #     If provided, the sampler may be a sampler object as defined in the
      #     {OpenCensus::Trace::Samplers} module docs, or the values `true` or
      #     `false` as shortcuts for {OpenCensus::Trace::Samplers::AlwaysSample}
      #     or {OpenCensus::Trace::Samplers::NeverSample}, respectively. If no
      #     span-scoped sampler is provided, the local parent span's sampling
      #     decision is used. If there is no local parent span, the configured
      #     default sampler is used to make a sampling decision.
      #
      # @return [SpanBuilder] A SpanBuilder object that you can use to
      #     set span attributes and create children.
      #
      def start_span name, kind: nil, skip_frames: 0, sampler: nil
        context = span_context
        raise "No currently active span context" unless context
        span = context.start_span name, kind: kind,
                                        skip_frames: skip_frames + 1,
                                        sampler: sampler
        self.span_context = span.context
        span
      end

      ##
      # Create a new span in this context.
      # You must pass a name for the span. All other span attributes should
      # be set using {OpenCensus::Trace::SpanBuilder} methods.
      #
      # The span will be started automatically with the current timestamp. The
      # SpanBuilder will then be passed to the block you provide. The span will
      # be finished automatically at the end of the block. Within the block,
      # the thread-local SpanContext will be updated so calls to `start_span`
      # will create subspans.
      #
      # @param [String] name Name of the span. Required.
      # @param [Symbol] kind Kind of span. Optional. Defaults to unspecified.
      #     Other allowed values are {OpenCensus::Trace::SpanBuilder::SERVER}
      #     and {OpenCensus::Trace::SpanBuilder::CLIENT}.
      # @param [Sampler,Boolean,nil] sampler Span-scoped sampler. Optional.
      #     If provided, the sampler may be a sampler object as defined in the
      #     {OpenCensus::Trace::Samplers} module docs, or the values `true` or
      #     `false` as shortcuts for {OpenCensus::Trace::Samplers::AlwaysSample}
      #     or {OpenCensus::Trace::Samplers::NeverSample}, respectively. If no
      #     span-scoped sampler is provided, the local parent span's sampling
      #     decision is used. If there is no local parent span, the configured
      #     default sampler is used to make a sampling decision.
      #
      def in_span name, kind: nil, skip_frames: 0, sampler: nil
        span = start_span name, kind: kind, skip_frames: skip_frames + 1,
                                sampler: sampler
        begin
          yield span
        ensure
          end_span span
        end
      end

      ##
      # Finish the given span, which must be the span that created the
      # current thread-local SpanContext. Also updates the thread-local
      # SpanContext so subsequent calls no longer create subspans of the
      # finished span.
      #
      # @param [SpanBuilder] span The expected currently active span to finish.
      #
      def end_span span
        context = span_context
        raise "No currently active span context" unless context
        unless span.equal? context.this_span
          raise "The given span doesn't match the currently active span"
        end
        span.finish!
        self.span_context = context.parent
        span
      end
    end
  end
end
