# Facter - Host Fact Detection and Reporting
#
# Copyright 2011 Puppet Labs Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'facter/version'

module Facter
  # This is just so the other classes have the constant.
  module Util; end

  require 'facter/util/fact'
  require 'facter/util/collection'
  require 'facter/util/monkey_patches'

  include Comparable
  include Enumerable

  # = Facter
  # Functions as a hash of 'facts' you might care about about your
  # system, such as mac address, IP address, Video card, etc.
  # returns them dynamically

  # == Synopsis
  #
  # Generally, treat <tt>Facter</tt> as a hash:
  # == Example
  # require 'facter'
  # puts Facter['operatingsystem']
  #

  GREEN = "[0;32m"
  RESET = "[0m"
  @@debug = 0
  @@timing = 0
  @@messages = {}
  @@debug_messages = {}

  # module methods

  def self.collection
    unless defined?(@collection) and @collection
      @collection = Facter::Util::Collection.new(
        Facter::Util::Loader.new,
        Facter::Util::Config.ext_fact_loader)
    end
    @collection
  end

  # Add some debugging
  def self.debug(string)
    if string.nil?
      return
    end
    if self.debugging?
      puts GREEN + string + RESET
    end
  end

  # Debug once.
  def self.debugonce(msg)
    if msg and not msg.empty? and @@debug_messages[msg].nil?
      @@debug_messages[msg] = true
      debug(msg)
    end
  end

  def self.debugging?
    @@debug != 0
  end

  # show the timing information
  def self.show_time(string)
    puts "#{GREEN}#{string}#{RESET}" if string and Facter.timing?
  end

  def self.timing?
    @@timing != 0
  end

  # Facter.json? is meant to provide a lightweight way to check if the JSON
  # "feature" is available.
  def self.json?
    begin
      require 'json'
      true
    rescue LoadError
      false
    end
  end

  # Return a fact object by name.  If you use this, you still have to call
  # 'value' on it to retrieve the actual value.
  def self.[](name)
    collection.fact(name)
  end

  class << self
    [:fact, :flush, :list, :value].each do |method|
      define_method(method) do |*args|
        collection.send(method, *args)
      end
    end

    [:list, :to_hash].each do |method|
      define_method(method) do |*args|
        collection.load_all
        collection.send(method, *args)
      end
    end
  end


  # Add a resolution mechanism for a named fact.  This does not distinguish
  # between adding a new fact and adding a new way to resolve a fact.
  def self.add(name, options = {}, &block)
    collection.add(name, options, &block)
  end

  def self.each
    # Make sure all facts are loaded.
    collection.load_all

    collection.each do |*args|
      yield(*args)
    end
  end

  class << self
    # Allow users to call fact names directly on the Facter class,
    # either retrieving the value or comparing it to an existing value.
    def method_missing(name, *args)
      question = false
      if name.to_s =~ /\?$/
        question = true
        name = name.to_s.sub(/\?$/,'')
      end

      if fact = collection.fact(name)
        if question
          value = fact.value.downcase
          args.each do |arg|
            if arg.to_s.downcase == value
              return true
            end
          end

          # If we got this far, there was no match.
          return false
        else
          return fact.value
        end
      else
        # Else, fail like a normal missing method.
        raise NoMethodError, "Could not find fact '%s'" % name
      end
    end
  end

  # Clear all facts.  Mostly used for testing.
  def self.clear
    Facter.flush
    Facter.reset
  end

  # Clear all messages. Used only in testing. Can't add to self.clear
  # because we don't want to warn multiple times for items that are warnonce'd
  def self.clear_messages
    @@messages.clear
  end

  # Set debugging on or off.
  def self.debugging(bit)
    if bit
      case bit
      when TrueClass; @@debug = 1
      when FalseClass; @@debug = 0
      when Fixnum
        if bit > 0
          @@debug = 1
        else
          @@debug = 0
        end
      when String;
        if bit.downcase == 'off'
          @@debug = 0
        else
          @@debug = 1
        end
      else
        @@debug = 0
      end
    else
      @@debug = 0
    end
  end

  # Set timing on or off.
  def self.timing(bit)
    if bit
      case bit
      when TrueClass; @@timing = 1
      when Fixnum
        if bit > 0
          @@timing = 1
        else
          @@timing = 0
        end
      end
    else
      @@timing = 0
    end
  end

  def self.warn(msg)
    if Facter.debugging? and msg and not msg.empty?
      msg = [msg] unless msg.respond_to? :each
      msg.each { |line| Kernel.warn line }
    end
  end

  # Warn once.
  def self.warnonce(msg)
    if msg and not msg.empty? and @@messages[msg].nil?
      @@messages[msg] = true
      Kernel.warn(msg)
    end
  end

  # Remove them all.
  def self.reset
    @collection = nil
  end

  # Load all of the default facts, and then everything from disk.
  def self.loadfacts
    collection.load_all
  end

  @search_path = []

  # Register a directory to search through.
  def self.search(*dirs)
    @search_path += dirs
  end

  # Return our registered search directories.
  def self.search_path
    @search_path.dup
  end
end
