# encoding: utf-8

require 'tunemygc/spies'
require 'tunemygc/network'

module TuneMyGc
  class Interposer
    attr_reader :spies
    attr_accessor :installed

    def initialize(spies = TuneMyGc.spies)
      reset
      @spies = spies.map{|s| TuneMyGc::Spies.const_get(s).new }
    end

    def spy
      @spies.first
    end

    def on_initialized
      GC.start(full_mark: true, :immediate_sweep => true)
      TuneMyGc.install_gc_tracepoint
      TuneMyGc.log "hooked: GC tracepoints"
      TuneMyGc.snapshot(:BOOTED, TuneMyGc.count_objects)
      TuneMyGc.interposer.spies.each{|s| s.install }
    end

    def install
      return if @installed
      TuneMyGc.log "interposing"
      if TuneMyGc.rails?
        require 'active_support'
        ActiveSupport.on_load(:after_initialize) do
          TuneMyGc.interposer.on_initialized
        end
      else
        TuneMyGc.interposer.on_initialized
      end
      TuneMyGc.log "hooked: after_initialize"

      at_exit do
        if @installed
          TuneMyGc.log "at_exit"
          @spies.each{|s| s.uninstall }
          TuneMyGc.terminated
          TuneMyGc.recommendations
        end
      end
      TuneMyGc.log "hooked: at_exit"
      @installed = true
      TuneMyGc.log "interposed"
    end

    def check_uninstall
      @spies.each{|s| s.check_uninstall }
    end

    def uninstall
      TuneMyGc.uninstall_gc_tracepoint
      TuneMyGc.log "uninstalled GC tracepoint"
      @spies.each{|s| s.uninstall }
      reset
    end

    def kamikaze
      Thread.new do
        TuneMyGc.terminated
        TuneMyGc.log "kamikaze: synching #{TuneMyGc.snapshotter.size} GC sample snapshots ahead of time (usually only on process exit)"
        Timeout.timeout(TuneMyGc::KAMIZE_SYNC_TIMEOUT) do
          begin
            uninstall
            TuneMyGc.recommendations
          rescue Timeout::Error
            # Discard the TERMINATED snapshot, retry in the at_exit block
            TuneMyGc.snapshotter.deq
            TuneMyGc.log "kamikaze: timeout syncing #{TuneMyGc.snapshotter.size} GC samples ahead of time"
          end
        end
      end
    end

    private
    def reset
      @installed = false
    end
  end
end
