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

require 'pp'

module TraceView
  module API
    ##
    # General utility methods for the gem
    module Util
      BACKTRACE_CUTOFF = 200

      # Internal: Check whether the provided key is reserved or not. Reserved
      # keys are either keys that are handled by liboboe calls or the traceview gem.
      #
      # key - the key to check.
      #
      # Return a boolean indicating whether or not key is reserved.
      def valid_key?(key)
        !%w(Label Layer Edge Timestamp Timestamp_u).include? key.to_s
      end

      # Internal: Get the current backtrace.
      #
      # ignore - Number of frames to ignore at the end of the backtrace. Use
      #          when you know how many layers deep in oboe the call is being
      #          made.
      #
      # Returns a string with each frame of the backtrace separated by '\r\n'.
      #
      # FIXME: ignore is not currently used (see BACKTRACE_CUTOFF)
      def backtrace(_ignore = 1)
        trim_backtrace(Kernel.caller).join("\r\n")
      end

      # Internal: Trim a backtrace to a manageable size
      #
      # backtrace - the backtrace (an array of stack frames/from Kernel.caller)
      #
      # Returns a trimmed backtrace
      def trim_backtrace(backtrace)
        return backtrace unless backtrace.is_a?(Array)

        length = backtrace.size
        if length > BACKTRACE_CUTOFF
          # Trim backtraces by getting the first 180 and last 20 lines
          trimmed = backtrace[0, 180] + ['...[snip]...'] + backtrace[length - 20, 20]
        else
          trimmed = backtrace
        end
        trimmed
      end

      # Internal: Check if a host is blacklisted from tracing
      #
      # addr_port - the addr_port from Net::HTTP although this method
      # can be used from any component in reality
      #
      # Returns a boolean on blacklisted state
      def blacklisted?(addr_port)
        return false unless TraceView::Config.blacklist

        # Ensure that the blacklist is an array
        unless TraceView::Config.blacklist.is_a?(Array)
          val = TraceView::Config[:blacklist]
          TraceView::Config[:blacklist] = [val.to_s]
        end

        TraceView::Config.blacklist.each do |h|
          return true if addr_port.to_s.match(h.to_s)
        end

        false
      end

      # Internal: Pretty print a list of arguments for reporting
      #
      # args - the list of arguments to work on
      #
      # Returns a pretty string representation of arguments
      def pps(*args)
        old_out = $stdout
        begin
          s = StringIO.new
          $stdout = s
          pp(*args)
        ensure
          $stdout = old_out
        end
        s.string
      end

      # Internal: Determine a string to report representing klass
      #
      # args - an instance of a Class, a Class or a Module
      #
      # Returns a string representation of klass
      def get_class_name(klass)
        kv = {}

        if klass.to_s =~ /::/
          klass.class.to_s.rpartition('::').last
        else
          if klass.is_a?(Class) && klass.is_a?(Module)
            # Class
            kv['Class'] = klass.to_s

          elsif (!klass.is_a?(Class) && !klass.is_a?(Module))
            # Class instance
            kv['Class'] = klass.class.to_s

          else
            # Module
            kv['Module'] = klass.to_s
          end
        end
        kv
      end
    end
  end
end