# Copyright (c) 2013 AppNeta, Inc.
# All rights reserved.

# Constants from liboboe
OBOE_TRACE_NEVER   = 0
OBOE_TRACE_ALWAYS  = 1
OBOE_TRACE_THROUGH = 2

OBOE_SAMPLE_RATE_SOURCE_FILE                   = 1
OBOE_SAMPLE_RATE_SOURCE_DEFAULT                = 2
OBOE_SAMPLE_RATE_SOURCE_OBOE                   = 3
OBOE_SAMPLE_RATE_SOURCE_LAST_OBOE              = 4
OBOE_SAMPLE_RATE_SOURCE_DEFAULT_MISCONFIGURED  = 5
OBOE_SAMPLE_RATE_SOURCE_OBOE_DEFAULT           = 6

# Masks for bitwise ops
ZERO_MASK = 0b0000000000000000000000000000

SAMPLE_RATE_MASK   = 0b0000111111111111111111111111
SAMPLE_SOURCE_MASK = 0b1111000000000000000000000000

ZERO_SAMPLE_RATE_MASK   = 0b1111000000000000000000000000
ZERO_SAMPLE_SOURCE_MASK = 0b0000111111111111111111111111

##
# This module is the base module for the various implementations of TraceView reporting.
# Current variations as of 2014-09-10 are a c-extension, JRuby (using TraceView Java
# instrumentation) and a Heroku c-extension (with embedded tracelyzer)
module TraceViewBase
  extend ::TraceView::ThreadLocal

  attr_accessor :reporter
  attr_accessor :loaded
  thread_local :sample_source
  thread_local :sample_rate
  thread_local :layer
  thread_local :layer_op
  # Semaphore used during the test suite to test
  # global config options.
  thread_local :config_lock

  # The following accessors indicate the incoming tracing state received
  # by the rack layer.  These are primarily used to identify state
  # between the Ruby and JTraceView instrumentation under JRuby.
  #
  # This is because that even though there may be an incoming
  # X-Trace request header, tracing may have already been started
  # by Joboe.  Such a scenario occurs when the application is being
  # hosted by a Java container (such as Tomcat or Glassfish) and
  # JTraceView has already initiated tracing.  In this case, we shouldn't
  # pickup the X-Trace context in the X-Trace header and we shouldn't
  # set the outgoing response X-Trace header or clear context.
  # Yeah I know.  Yuck.

  # Occurs only on Jruby.  Indicates that Joboe (the java instrumentation)
  # has already started tracing before it hit the JRuby instrumentation.
  thread_local :has_incoming_context

  # Indicates the existence of a valid X-Trace request header
  thread_local :has_xtrace_header

  # This indicates that this trace was continued from
  # an incoming X-Trace request header or in the case
  # of JRuby, a trace already started by JTraceView.
  thread_local :is_continued_trace

  ##
  # extended
  #
  # Invoked when this module is extended.
  # e.g. extend TraceViewBase
  #
  def self.extended(cls)
    cls.loaded = true

    # This gives us pretty accessors with questions marks at the end
    # e.g. is_continued_trace --> is_continued_trace?
    TraceView.methods.select{ |m| m =~ /^is_|^has_/ }.each do |c|
      unless c =~ /\?$|=$/
        # TraceView.logger.debug "aliasing #{c}? to #{c}"
        alias_method "#{c}?", c
      end
    end
  end

  ##
  # pickup_context
  #
  # Determines whether we should pickup context
  # from an incoming X-Trace request header.  The answer
  # is generally yes but there are cases in JRuby under
  # Tomcat (or Glassfish etc.) where tracing may have
  # been already started by the Java instrumentation (Joboe)
  # in which case we don't want to do this.
  #
  def pickup_context?(xtrace)
    return false unless TraceView::XTrace.valid?(xtrace)

    if defined?(JRUBY_VERSION) && TraceView.tracing?
      return false
    else
      return true
    end
  end

  ##
  # tracing_layer?
  #
  # Queries the thread local variable about the current
  # layer being traced.  This is used in cases of recursive
  # operation tracing or one instrumented operation calling another.
  #
  def tracing_layer?(layer)
    return TraceView.layer == layer
  end

  ##
  # tracing_layer_op?
  #
  # Queries the thread local variable about the current
  # operation being traced.  This is used in cases of recursive
  # operation tracing or one instrumented operation calling another.
  #
  # In such cases, we only want to trace the outermost operation.
  #
  def tracing_layer_op?(operation)
    if operation.is_a?(Array)
      return operation.include?(TraceView.layer_op)
    else
      return TraceView.layer_op == operation
    end
  end

  ##
  # Returns true if the tracing_mode is set to always.
  # False otherwise
  #
  def always?
    TraceView::Config[:tracing_mode].to_s == 'always'
  end

  ##
  # Returns true if the tracing_mode is set to never.
  # False otherwise
  #
  def never?
    TraceView::Config[:tracing_mode].to_s == 'never'
  end

  ##
  # Returns true if the tracing_mode is set to always or through.
  # False otherwise
  #
  def passthrough?
    %w(always through).include?(TraceView::Config[:tracing_mode])
  end

  ##
  # Returns true if the tracing_mode is set to through.
  # False otherwise
  #
  def through?
    TraceView::Config[:tracing_mode] == 'through'
  end

  ##
  # Returns true if we are currently tracing a request
  # False otherwise
  #
  def tracing?
    return false if !TraceView.loaded || TraceView.never?
    TraceView::Context.isValid
  end

  def log(layer, label, options = {})
    # WARN: TraceView.log will be deprecated in a future release.  Please use TraceView::API.log instead.
    TraceView::API.log(layer, label, options)
  end

  def heroku?
    ENV.key?('TRACEVIEW_URL')
  end

  ##
  # Determines if we are running under a forking webserver
  #
  def forking_webserver?
    if (defined?(::Unicorn) && ($PROGRAM_NAME =~ /unicorn/i)) ||
       (defined?(::Puma)    && ($PROGRAM_NAME =~ /puma/i))
      true
    else
      false
    end
  end

  ##
  # Debugging helper method
  #
  def pry!
    # Only valid for development or test environments
    env = ENV['RACK_ENV'] || ENV['RAILS_ENV']
    return unless [ "development", "test" ].include? env

    if RUBY_VERSION > '1.8.7'
      require 'pry-byebug'

      if defined?(PryByebug)
        Pry.commands.alias_command 'c', 'continue'
        Pry.commands.alias_command 's', 'step'
        Pry.commands.alias_command 'n', 'next'
        Pry.commands.alias_command 'f', 'finish'

        Pry::Commands.command /^$/, "repeat last command" do
          _pry_.run_command Pry.history.to_a.last
        end
      end

      binding.pry
    else
      require 'ruby-debug'; debugger
    end
  end

  ##
  # Indicates whether a supported framework is in use
  # or not
  #
  def framework?
    defined?(::Rails) or defined?(::Sinatra) or defined?(::Padrino) or defined?(::Grape)
  end

  ##
  # These methods should be implemented by the descendants
  # (Oboe_metal, JOboe_metal (JRuby), Heroku_metal)
  #
  def sample?(_opts = {})
    fail 'sample? should be implemented by metal layer.'
  end

  def log(_layer, _label, _options = {})
    fail 'log should be implemented by metal layer.'
  end

  def set_tracing_mode(_mode)
    fail 'set_tracing_mode should be implemented by metal layer.'
  end

  def set_sample_rate(_rate)
    fail 'set_sample_rate should be implemented by metal layer.'
  end
end

module TraceView
  extend TraceViewBase
end

# Setup an alias so we don't bug users
# about single letter capitalization
Traceview = TraceView
TV = TraceView