require 'yaml'
require 'open-uri'
require 'cgi'

def eval_and_fetch_constants(x)
  old = Module.constants.map{|c| c.to_s}
  eval(x)
  new = (Module.constants.map{|c| c.to_s} - old)
  new.map{|const| const_get(const) }
end

class Scout
  class Plugin
    OPTIONS = {}.to_yaml

    def self.clean_class_name
      to_s.split('::')[1..-1].join('::')
    end

    protected

    def report(metrics)
      metrics.each do |key, value|
        Deputy.send_report "#{self.class.clean_class_name}.#{key}", value
      end
    end

    def error(*args)
      report :error => args.map{|a| a.inspect}.join(', ')
    end

    def alert(*args)
      report :alert => args.map{|a| a.inspect}.join(', ')
    end

    def memory(key)
      complete_memory[key]
    end

    def remember(data)
      all = complete_memory.merge(data)
      File.open(memory_file, 'w'){|f| f.write all.to_yaml }
    end

    def needs(lib)
      require lib
    end

    private

    def complete_memory
      return {} unless File.exist?(memory_file)
      YAML.load(File.read(memory_file)) || {}
    end

    def memory_file
      "/tmp/deputy.memory.#{self.class.clean_class_name}.yml"
    end

    # stub options for now...
    def option(key)
      (YAML.load(self.class::OPTIONS)[key.to_s]||{})['default']
    end
  end

  def self.plugins(code)
    eval_and_fetch_constants(code).map do |container|
      interval = container.interval
      unless plugin = plugin_in_container(container)
        Deputy.send_report "Deputies.Plugin not found", code
        next
      end
      [interval, plugin]
    end.compact
  end

  def self.plugin_in_container(container)
    constants = container.constants.map{|constant_name| container.const_get(constant_name)}
    constants.detect{|c| c.instance_methods.map{|m| m.to_s}.include?('build_report') }
  end
end

module Deputy
  START_MINUTE = (Time.now.to_i + 30) / 60 # we could start at 58..02 seconds -> always in middle of minute
  VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip

  def self.install_cron
    executable = `which deputy`.strip
    `crontab -l | { cat; echo "* * * * * #{executable} --run-plugins >> /tmp/deputy.log 2>&1"; } | crontab -`
    if executable !~ %r{^/usr/}
      puts "make deputy globally available! or e.g. calls from inside cronjobs do not know deputy"
      puts "sudo ln -s #{executable} /usr/bin/deputy"
    end
  end

  def self.run_plugins
    content = get("/plugins.rb")

    Scout.plugins(content).each do |interval, plugin|
      run_every_n_minutes = interval/60
      minutes_to_wait = run_every_n_minutes - (START_MINUTE % run_every_n_minutes)
      if minutes_to_wait == run_every_n_minutes
        puts "#{plugin.clean_class_name}: running"
        plugin.new.build_report
      else
        puts "#{plugin.clean_class_name}: waiting another #{minutes_to_wait} minutes"
      end
    end
    send_report 'Deputies.finished', 1
  rescue Exception => e
    send_report "Deputies.Error", e.message
    raise e
  end

  def self.send_report(metric, value)
    raise "Use with dot e.g. Mysql.status" if metric.split('.',2).size != 2
    get "/report/#{CGI.escape metric}?value=#{CGI.escape value.to_s}"
  end

  def self.get(path)
    url = "#{sheriff_url}#{path}"
    Timeout.timeout(1) do
      open(url).read
    end
  rescue => e
    e.message << url
    raise e
  end

  def self.sheriff_url
    config['sheriff_url'].sub(%r{/$},'')
  end

  def self.config
    home = File.expand_path('~')
    ["#{home}/.deputy.yml", '/etc/deputy.yml'].each do |file|
      return YAML.load(File.read(file)) if File.exist?(file)
    end
    raise "No deputy.yml found in /etc or #{home}"
  end

  # stolen from klarlack -- http://github.com/schoefmax/klarlack
  # to get an reliable timeout that wont fail on other platforms
  # or if sytem_timer is missing
  Timeout = begin
    # Try to use the SystemTimer gem instead of Ruby's timeout library
    # when running on something that looks like Ruby 1.8.x. See:
    # http://ph7spot.com/articles/system_timer
    # We don't want to bother trying to load SystemTimer on jruby and
    # ruby 1.9+.
    if RUBY_VERSION =~ /^1\.8\./ and RUBY_PLATFORM !~ /java/
      require 'system_timer'
      SystemTimer
    else
      require 'timeout'
      Timeout
    end
  rescue LoadError => e
    $stderr.puts "Could not load SystemTimer gem, falling back to Ruby's slower/unsafe timeout library: #{e.message}"
    require 'timeout'
    Timeout
  end
end