
require File.join(File.dirname(__FILE__), 'ping')
require 'win32ole'

# The Net module serves as a namespace only.
#
module HttpPing

  # The Ping::WMI class encapsulates the Win32_PingStatus WMI class for
  # MS Windows.
  #
  class HttpPing::WMI < Ping

    PingStatus = Struct.new(
        'PingStatus',
        :address,
        :buffer_size,
        :no_fragmentation,
        :primary_address_resolution_status,
        :protocol_address,
        :protocol_address_resolved,
        :record_route,
        :reply_inconsistency,
        :reply_size,
        :resolve_address_names,
        :response_time,
        :response_time_to_live,
        :route_record,
        :route_record_resolved,
        :source_route,
        :source_route_type,
        :status_code,
        :timeout,
        :timestamp_record,
        :timestamp_record_address,
        :timestamp_record_address_resolved,
        :timestamp_route,
        :time_to_live,
        :type_of_service
    )

    # Unlike the ping method for other Ping subclasses, this version returns
    # a PingStatus struct which contains various bits of information about
    # the results of the ping itself, such as response time.
    #
    # In addition, this version allows you to pass certain options that are
    # then passed on to the underlying WQL query. See the MSDN documentation
    # on Win32_PingStatus for details.
    #
    # Examples:
    #
    #  # Ping with no options
    #  Ping::WMI.ping('www.perl.com')
    #
    #  # Ping with options
    #  Ping::WMI.ping('www.perl.com', :BufferSize => 64, :NoFragmentation => true)
    #--
    # The PingStatus struct is a wrapper for the Win32_PingStatus WMI class.
    #
    def ping(host = @host, options = {})
      super(host)

      lhost = Socket.gethostname

      cs = "winmgmts:{impersonationLevel=impersonate}!//#{lhost}/root/cimv2"
      wmi = WIN32OLE.connect(cs)

      query = "select * from win32_pingstatus where address = '#{host}'"

      unless options.empty?
        options.each{ |key, value|
          if value.is_a?(String)
            query << " and #{key} = '#{value}'"
          else
            query << " and #{key} = #{value}"
          end
        }
      end

      status = Struct::PingStatus.new

      wmi.execquery(query).each{ |obj|
        status.address                           = obj.Address
        status.buffer_size                       = obj.BufferSize
        status.no_fragmentation                  = obj.NoFragmentation
        status.primary_address_resolution_status = obj.PrimaryAddressResolutionStatus
        status.protocol_address                  = obj.ProtocolAddress
        status.protocol_address_resolved         = obj.ProtocolAddressResolved
        status.record_route                      = obj.RecordRoute
        status.reply_inconsistency               = obj.ReplyInconsistency
        status.reply_size                        = obj.ReplySize
        status.resolve_address_names             = obj.ResolveAddressNames
        status.response_time                     = obj.ResponseTime
        status.response_time_to_live             = obj.ResponseTimeToLive
        status.route_record                      = obj.RouteRecord
        status.route_record_resolved             = obj.RouteRecordResolved
        status.source_route                      = obj.SourceRoute
        status.source_route_type                 = obj.SourceRouteType
        status.status_code                       = obj.StatusCode
        status.timeout                           = obj.Timeout
        status.timestamp_record                  = obj.TimeStampRecord
        status.timestamp_record_address          = obj.TimeStampRecordAddress
        status.timestamp_record_address_resolved = obj.TimeStampRecordAddressResolved
        status.timestamp_route                   = obj.TimeStampRoute
        status.time_to_live                      = obj.TimeToLive
        status.type_of_service                   = obj.TypeOfService
      }

      status.freeze # Read-only data
    end

    # Unlike Net::Ping::WMI#ping, this method returns true or false to
    # indicate whether or not the ping was successful.
    #
    def ping?(host = @host, options = {})
      ping(host, options).status_code == 0
    end
  end
end
