module MCollective
  class Discovery
    def initialize(client)
      @known_methods = find_known_methods
      @default_method = Config.instance.default_discovery_method
      @client = client
    end

    def find_known_methods
      PluginManager.find("discovery")
    end

    def has_method?(method)
      @known_methods.include?(method)
    end

    def force_direct_mode?
      discovery_method != "mc"
    end

    def discovery_method
      method = "mc"

      if @client.options[:discovery_method]
        method = @client.options[:discovery_method]
      else
        method = @default_method
      end

      raise "Unknown discovery method %s" % method unless has_method?(method)

      unless method == "mc"
        raise "Custom discovery methods require direct addressing mode" unless Config.instance.direct_addressing
      end

      return method
    end

    def discovery_class
      method = discovery_method.capitalize

      PluginManager.loadclass("MCollective::Discovery::#{method}") unless self.class.const_defined?(method)

      self.class.const_get(method)
    end

    def ddl
      @ddl ||= DDL.new(discovery_method, :discovery)

      # if the discovery method got changed we might have an old DDL cached
      # this will detect that and reread the correct DDL from disk
      unless @ddl.meta[:name] == discovery_method
        @ddl = DDL.new(discovery_method, :discovery)
      end

      return @ddl
    end

    # Agent filters are always present no matter what, so we cant raise an error if the capabilities
    # suggest the discovery method cant do agents we just have to rely on the discovery plugin to not
    # do stupid things in the presense of a agent filter
    def check_capabilities(filter)
      capabilities = ddl.discovery_interface[:capabilities]

      unless capabilities.include?(:classes)
        raise "Cannot use class filters while using the '%s' discovery method" % discovery_method unless filter["cf_class"].empty?
      end

      unless capabilities.include?(:facts)
        raise "Cannot use fact filters while using the '%s' discovery method" % discovery_method unless filter["fact"].empty?
      end

      unless capabilities.include?(:identity)
        raise "Cannot use identity filters while using the '%s' discovery method" % discovery_method unless filter["identity"].empty?
      end

      unless capabilities.include?(:compound)
        raise "Cannot use compound filters while using the '%s' discovery method" % discovery_method unless filter["compound"].empty?
      end
    end

    # checks if compound filters are used and then forces the 'mc' discovery plugin
    def force_discovery_method_by_filter(filter)
      unless discovery_method == "mc"
        unless filter["compound"].empty?
          Log.info "Switching to mc discovery method because compound filters are used"
          @client.options[:discovery_method] = "mc"

          return true
        end
      end

      return false
    end

    # if a compound filter is specified and it has any function
    # then we read the DDL for each of those plugins and sum up
    # the timeout declared in the DDL
    def timeout_for_compound_filter(compound_filter)
      return 0 if compound_filter.nil? || compound_filter.empty?

      timeout = 0

      compound_filter.each do |filter|
        filter.each do |statement|
          if statement["fstatement"]
            pluginname = Data.pluginname(statement["fstatement"]["name"])
            ddl = DDL.new(pluginname, :data)
            timeout += ddl.meta[:timeout]
          end
        end
      end

      timeout
    end

    def discovery_timeout(timeout, filter)
      timeout = ddl.meta[:timeout] unless timeout

      unless (filter["compound"] && filter["compound"].empty?)
        timeout + timeout_for_compound_filter(filter["compound"])
      else
        timeout
      end
    end

    def discover(filter, timeout, limit)
      raise "Limit has to be an integer" unless limit.is_a?(Integer)

      force_discovery_method_by_filter(filter)

      check_capabilities(filter)

      discovered = discovery_class.discover(filter, discovery_timeout(timeout, filter), limit, @client)

      if limit > 0
        return discovered[0,limit]
      else
        return discovered
      end
    end
  end
end
