# Copyright (C) 2014 Corey Osman
#
#     This library is free software; you can redistribute it and/or
#     modify it under the terms of the GNU Lesser General Public
#     License as published by the Free Software Foundation; either
#     version 2.1 of the License, or (at your option) any later version.
#
#     This library is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#     Lesser General Public License for more details.
#
#     You should have received a copy of the GNU Lesser General Public
#     License along with this library; if not, write to the Free Software
#     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#     USA
#


require 'rubyipmi/ipmitool/connection'
require 'rubyipmi/freeipmi/connection'
require 'logger'

class NullLogger < Logger
  def initialize(*args)
  end

  def add(*args, &block)
  end
end

module Rubyipmi
  PRIV_TYPES = ['CALLBACK', 'USER', 'OPERATOR', 'ADMINISTRATOR']
  attr_accessor :logger, :log_level

  # set a logger instance yourself to customize where the logs should go
  # you will need to set the log level yourself
  def self.logger=(log)
    @logger = log
  end

  # sets the log level, this should be called first if logging to a file is desired
  # if you wish to customize the logging options, set the logger yourself with logger=
  # valid levels are of the type Logger::INFO, Logger::DEBUG, Logger::ERROR, ...
  def self.log_level=(level)
    @log_level = level
  end

  # this is an read only method that only creates a real logger if the log_level is set
  # if the log_level is not setup it creates a null logger which logs nothing
  def self.logger
    # by default the log will be set to info
    unless @logger
      if @log_level and @log_level >= 0
        @logger = Logger.new('/tmp/rubyipmi.log')
        @logger.progname = 'Rubyipmi'
        @logger.level = @log_level
      else
        @logger = NullLogger.new
      end
    end
    @logger
  end

  def self.valid_drivers
    ['auto', "lan15", "lan20", "open"]
  end

  def self.valid_providers
    ['auto', 'ipmitool', 'freeipmi']
  end
  # The connect method will create a connection object based the provider type passed in
  # If provider is left blank the function will use the first available provider
  # When the driver is set to auto, rubyipmi will try and figure out which driver to use by common error messages.  We will most likely be using
  # the lan20 driver, but in order to support a wide use case we default to auto.
  def self.connect(user, pass, host, provider='any', opts={:driver => 'auto', :timeout => 'default'})
    # use this variable to reduce cmd calls
    installed = false

    # allow the user to specify an options hash instead of the provider
    # in the future I would stop using the provider and use the opts hash instead to get the provider
    # This allows us to be a little more flexible if the user is doesn't supply us what we need.
    if provider.is_a?(Hash)
      opts = provider
      provider = opts[:provider] ||= 'any'
    end

    # Verify options just in case user passed in a incomplete hash
    opts[:driver]  ||= 'auto'
    opts[:timeout] ||= 'default'

    if opts[:privilege] and not supported_privilege_type?(opts[:privilege])
      logger.error("Invalid privilege type :#{opts[:privilege]}, must be one of: #{PRIV_TYPES.join("\n")}") if logger
      raise "Invalid privilege type :#{opts[:privilege]}, must be one of: #{PRIV_TYPES.join("\n")}"
    end

    # use the first available provider
    if provider == 'any'
      if is_provider_installed?("freeipmi")
        provider = "freeipmi"
        installed = true
      elsif is_provider_installed?("ipmitool")
        provider = "ipmitool"
        installed = true
      else
        logger.error("No IPMI provider is installed, please install freeipmi or ipmitool")
        raise "No IPMI provider is installed, please install freeipmi or ipmitool"
      end
    end

    # Support multiple drivers
    # Note: these are just generic names of drivers that need to be specified for each provider
    unless valid_drivers.include?(opts[:driver])
      logger.debug("You must specify a valid driver: #{valid_drivers.join(',')}") if logger
      raise "You must specify a valid driver: #{valid_drivers.join(',')}"
    end

    # If the provider is available create a connection object
    if installed or is_provider_installed?(provider)
      if provider == "freeipmi"
        Rubyipmi::Freeipmi::Connection.new(user, pass, host, opts)
      elsif provider == "ipmitool"
        Rubyipmi::Ipmitool::Connection.new(user,pass,host, opts)
      else
        logger.error("Incorrect provider given, must use one of #{valid_providers.join(', ')}") if logger
        raise "Incorrect provider given, must use one of #{valid_providers.join(', ')}"
      end
    else
      # Can't find the provider command line tool, maybe try other provider?
      logger.error("The IPMI provider: #{provider} is not installed") if logger
      raise "The IPMI provider: #{provider} is not installed"
    end
  end

  # returns boolean true if privilege type is valid
  def self.supported_privilege_type?(type)
     PRIV_TYPES.include?(type)
  end

  # method used to find the command which also makes it easier to mock with
  def self.locate_command(commandname)
    location = `which #{commandname}`.strip
    if not $?.success?
      location = nil
    end
    location
  end

  # Return true or false if the provider is available
  def self.is_provider_installed?(provider)
    case provider
      when "freeipmi"
        cmdpath = locate_command('ipmipower')
      when "ipmitool"
        cmdpath = locate_command('ipmitool')
      else
        logger.error("Invalid BMC provider type #{provider}") if logger
        false
    end
    # return false if command was not found
    return ! cmdpath.nil?
  end

  def self.providers
    ["freeipmi", "ipmitool"]
  end

  # returns true if any of the providers are installed
  def self.provider_installed?
    providers_installed.length > 0
  end

  def self.providers_installed
    available = []
    providers.each do |prov|
      if is_provider_installed?(prov)
        available << prov
      end
    end
    return available
  end

  # gets data from the bmc device and puts in a hash for diagnostics
  def self.get_diag(user, pass, host, opts={:driver => 'auto', :timeout => 'default'})
    data = {}
    if Rubyipmi.is_provider_installed?('freeipmi')
      freeconn = Rubyipmi.connect(user, pass, host, 'freeipmi', opts)
      if freeconn
        puts "Retrieving freeipmi data"
        data[:freeipmi] = freeconn.get_diag
      end
    end
    if Rubyipmi.is_provider_installed?('ipmitool')
      ipmiconn = Rubyipmi.connect(user, pass, host, 'ipmitool', opts)
      if ipmiconn
        puts "Retrieving ipmitool data"
        data[:ipmitool] = ipmiconn.get_diag
      end
    end
    File.open('/tmp/rubyipmi_diag_data.txt', 'w') {|f| f.write(data)}
    puts "Created file /tmp/rubyipmi_diag_data.txt"
  end
end