# encoding: utf-8
require 'ting_yun/support/quantile_p2'
module TingYun
  module Agent
    class Transaction
      module InstanceMethod

        def ignore!
          @ignore_this_transaction = true
        end

        def ignore?
          @ignore_this_transaction
        end

        def create_nested_frame(state, category, options)
          @has_children = true
          frame_stack.push TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_header(state, Time.now.to_f)
          name_last_frame(options[:transaction_name])

          set_default_transaction_name(options[:transaction_name], category)
        end


        def set_default_transaction_name(name, category)
          if @frozen_name
            TingYun::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@frozen_name}'.")
            return
          end
          if influences_transaction_name?(category)
            @default_name = name
            @category = category if category
          end
        end

        def make_transaction_name(name, category=nil)
          namer = TingYun::Instrumentation::Support::TransactionNamer
          "#{namer.prefix_for_category(self, category)}#{name}"
        end

        def name_last_frame(name)
          frame_stack.last.name = name
        end


        def best_name
          @frozen_name || @default_name || ::TingYun::Agent::UNKNOWN_METRIC
        end


        def influences_transaction_name?(category)
          !category || frame_stack.size == 1 || similar_category?(category)
        end

        WEB_TRANSACTION_CATEGORIES = [:controller, :uri, :rack, :sinatra, :grape, :middleware, :thrift, :action_cable, :message].freeze

        def web_category?(category)
          WEB_TRANSACTION_CATEGORIES.include?(category)
        end

        def similar_category?(category)
          web_category?(@category) == web_category?(category)
        end


        def needs_middleware_summary_metrics?(name)
          name.start_with?(MIDDLEWARE_PREFIX)
        end

        alias_method :ignore, :needs_middleware_summary_metrics?

        def record_summary_metrics(state, outermost_node_name,end_time)
          unless @frozen_name == outermost_node_name
            time = (end_time.to_f - start_time.to_f) * 1000
            @metrics.record_unscoped(@frozen_name, time)
            if @frozen_name.start_with?('WebAction')
              state.current_transaction.base_quantile_hash[@frozen_name] = time
            end
          end
        end

        def assign_agent_attributes

          @attributes.add_agent_attribute(:threadName,  "pid-#{$$}");

          if @request_attributes
            @request_attributes.assign_agent_attributes @attributes
          end

          @attributes.add_agent_attribute(:tx_id,  @guid);
          @attributes.add_agent_attribute(:metric_name,  best_name);

        end


        # This transaction-local hash may be used as temprory storage by
        # instrumentation that needs to pass data from one instrumentation point
        # to another.
        #
        # For example, if both A and B are instrumented, and A calls B
        # but some piece of state needed by the instrumentation at B is only
        # available at A, the instrumentation at A may write into the hash, call
        # through, and then remove the key afterwards, allowing the
        # instrumentation at B to read the value in between.
        #
        # Keys should be symbols, and care should be taken to not generate key
        # names dynamically, and to ensure that keys are removed upon return from
        # the method that creates them.
        #
        def instrumentation_state
          @instrumentation_state ||= {}
        end

        def with_database_metric_name(model, method, product=nil)
          previous = self.instrumentation_state[:datastore_override]
          model_name = case model
                         when Class
                           model.name
                         when String
                           model
                         else
                           model.to_s
                       end
          @instrumentation_state[:datastore_override] = [method, model_name, product]
          yield
        ensure
          @instrumentation_state[:datastore_override] = previous
        end

        def freeze_name_and_execute
          unless @frozen_name
            @frozen_name = best_name
          end

          yield if block_given?
        end



        HEX_DIGITS = (0..15).map{|i| i.to_s(16)}
        GUID_LENGTH = 16

        # generate a random 64 bit uuid
        private
        def generate_guid
          guid = ''
          GUID_LENGTH.times do
            guid << HEX_DIGITS[rand(16)]
          end
          guid
        end
      end
    end
  end
end