# frozen_string_literal: true

# The Suitable mixin provides mechanisms for confining objects to run on
# certain platforms and determining the run precedence of these objects.
#
# Classes that include the Suitable mixin should define a `#confines` method
# that returns an Array of zero or more Facter::Util::Confine objects.
module LegacyFacter
  module Core
    module Suitable
      # Sets the weight of this resolution. If multiple suitable resolutions
      # are found, the one with the highest weight will be used.  If weight
      # is not given, the number of confines set on a resolution will be
      # used as its weight (so that the most specific resolution is used).
      #
      # @param weight [Integer] the weight of this resolution
      #
      # @return [void]
      #
      # @api public
      def has_weight(weight) # rubocop:disable Naming/PredicateName
        @weight = weight
        self
      end

      # Sets the conditions for this resolution to be used.  This method accepts
      # multiple forms of arguments to determine suitability.
      #
      # @return [void]
      #
      # @api public
      #
      # @overload confine(confines)
      #   Confine a fact to a specific fact value or values.  This form takes a
      #   hash of fact names and values. Every fact must match the values given for
      #   that fact, otherwise this resolution will not be considered suitable. The
      #   values given for a fact can be an array, in which case the value of the
      #   fact must be in the array for it to match.
      #   @param [Hash{String,Symbol=>String,Array<String>}] confines set of facts identified by the hash keys whose
      #     fact value must match the argument value.
      #   @example Confining to Linux
      #       Facter.add(:powerstates) do
      #         # This resolution only makes sense on linux systems
      #         confine :kernel => "Linux"
      #         setcode do
      #           File.read('/sys/power/states')
      #         end
      #       end
      #
      # @overload confine(confines, &block)
      #   Confine a fact to a block with the value of a specified fact yielded to
      #   the block.
      #   @param [String,Symbol] confines the fact name whose value should be
      #     yielded to the block
      #   @param [Proc] block determines the suitability of the fact.  If the block
      #     evaluates to `false` or `nil` then the confined fact will not be
      #     evaluated.
      #   @yield [value] the value of the fact identified by {confines}
      #   @example Confine the fact to a host with an ipaddress in a specific
      #     subnet
      #       confine :ipaddress do |addr|
      #         require 'ipaddr'
      #         IPAddr.new('192.168.0.0/16').include? addr
      #       end
      #
      # @overload confine(&block)
      #   Confine a fact to a block.  The fact will be evaluated only if the block
      #   evaluates to something other than `false` or `nil`.
      #   @param [Proc] block determines the suitability of the fact.  If the block
      #     evaluates to `false` or `nil` then the confined fact will not be
      #     evaluated.
      #   @example Confine the fact to systems with a specific file.
      #       confine { File.readable? '/bin/foo' }
      def confine(confines = nil, &block)
        case confines
        when Hash
          confines.each do |fact, values|
            @confines.push LegacyFacter::Util::Confine.new(fact, *values)
          end
        else
          if block
            if confines
              @confines.push LegacyFacter::Util::Confine.new(confines, &block)
            else
              @confines.push LegacyFacter::Util::Confine.new(&block)
            end
          end
        end
      end

      # Returns the importance of this resolution. If the weight was not
      # given, the number of confines is used instead (so that a more
      # specific resolution wins over a less specific one).
      #
      # @return [Integer] the weight of this resolution
      #
      # @api private
      def weight
        @weight || @confines.length
      end

      # Is this resolution mechanism suitable on the system in question?
      #
      # @api private
      def suitable?
        @confines.all?(&:true?)
      end
    end
  end
end
