# encoding: utf-8
module TingYun
  module Instrumentation
    module Timings
      def record_memcached_duration(duration)
        state = TingYun::Agent::TransactionState.tl_get
        if state
          state.timings.mc_duration = state.timings.mc_duration + duration * 1000
        end
      end
    end

    module VersionSupport
      require 'ting_yun/support/version_number'
      module_function

      VERSION1 = '2.6.4'.freeze

      def new_version_support?
        ::TingYun::Support::VersionNumber.new(Dalli::VERSION) >= ::TingYun::Support::VersionNumber.new(VERSION1)
      end
    end
  end
end


TingYun::Support::LibraryDetection.defer do
  named :memcached

  depends_on do
    defined?(::Memcached) || (defined?(::Dalli) && defined?(::Dalli::Client))
  end

  depends_on do
    !::TingYun::Agent.config[:disable_memcache]
  end


  executes do
    TingYun::Agent.logger.info "Installing Memcached Instrumentation" if defined?(::Memcached)
    TingYun::Agent.logger.info "Installing Dalli Instrumentation" if defined?(::Dalli::Client)
    require 'ting_yun/agent/transaction/transaction_state'
  end

  executes do
    require 'ting_yun/agent/datastore'

    if defined?(::Memcached)
      ::Memcached.class_eval do

        include TingYun::Instrumentation::Timings

        methods = [:set, :add, :increment, :decrement, :replace, :append, :prepend, :cas, :delete, :flush, :get, :exist,
                   :get_from_last, :server_by_key, :stats, :set_servers]

        methods.each do |method|
          next unless public_method_defined? method

          alias_method "#{method}_without_tingyun_trace".to_sym, method.to_sym

          define_method method do |*args, &block|
            TingYun::Agent::Datastore.wrap('Memcached', method.to_s, nil, nil, nil, nil, method(:record_memcached_duration)) do
              send "#{method}_without_tingyun_trace", *args, &block
            end
          end
        end

        alias :incr :increment
        alias :decr :decrement
        alias :compare_and_swap :cas if public_method_defined? :compare_and_swap
      end
    end

    if defined?(::Dalli::Server)
      ::Dalli::Server.class_eval do

        include TingYun::Instrumentation::Timings

        connect_method = (private_method_defined? :connect) ? :connect : :connection
        private
        alias_method :connect_without_tingyun_trace, connect_method

        define_method connect_method do |*args, &block|
          if @sock
            connect_without_tingyun_trace *args, &block
          else
            TingYun::Agent::Datastore.wrap('Memcached', 'connect', nil, hostname, port, nil, method(:record_memcached_duration)) do
              connect_without_tingyun_trace *args, &block
            end
          end
        end
      end
    end

    if defined?(::Dalli::Client)
      ::Dalli::Client.class_eval do

        include TingYun::Instrumentation::Timings

        private
        alias_method :perform_without_tingyun_trace, :perform
        def perform(*args, &block)
          return block.call if block
          op, key = args[0..1]
          current_ring = self.class.private_method_defined?(:ring) ? ring : @ring
          server = current_ring.server_for_key(validate_key(key.to_s)) rescue nil
          if server
            host = server.hostname
            port = server.port
          end
          TingYun::Agent::Datastore.wrap('Memcached', op.to_s, nil, host, port, nil, method(:record_memcached_duration)) do
            perform_without_tingyun_trace(*args, &block)
          end
        end

        methods = [:flush, :stats, :reset_stats, :close]
        methods += [:get_multi, :get_multi_cas] if TingYun::Instrumentation::VersionSupport.new_version_support?

        methods.each do |method|
          next unless public_method_defined? method
          alias_method "#{method}_without_tingyun_trace".to_sym, method.to_sym

          define_method method do |*args, &block|
            TingYun::Agent::Datastore.wrap('Memcached', method.to_s, nil, nil, nil, nil, method(:record_memcached_duration)) do
              send "#{method}_without_tingyun_trace", *args, &block
            end
          end
        end

        alias :flush_all :flush
        alias :reset :close if public_method_defined? :reset
      end
    end
  end
end