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

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

  # The Ping::UDP class encapsulates methods for UDP pings.
  class Ping::UDP < Ping
    @@service_check = true
      
    # Returns whether or not the connect behavior should enforce remote
    # service availability as well as reachability. The default is true.
    #
    def self.service_check
      @@service_check
    end
      
    # Set whether or not the connect behavior should enforce remote
    # service availability as well as reachability. If set to false
    # then Errno::ECONNREFUSED or Errno::ECONNRESET will be considered
    # a successful ping, meaning no actual data handshaking is required.
    # By default, if either of those errors occurs it is considered a failed
    # ping.
    #
    def self.service_check=(bool)
      unless bool.kind_of?(TrueClass) || bool.kind_of?(FalseClass)
        raise ArgumentError, 'argument must be true or false'
      end
      @@service_check = bool         
    end
      
    # The maximum data size that can be sent in a UDP ping.
    MAX_DATA = 64
      
    # The data to send to the remote host. By default this is 'ping'.
    # This should be MAX_DATA size characters or less.
    # 
    attr_reader :data
      
    # Creates and returns a new Ping::UDP object.  This is effectively
    # identical to its superclass constructor.
    # 
    def initialize(host=nil, port=nil, timeout=5)
      @data = 'ping'

      super(host, port, timeout)

      @bind_host = nil
      @bind_port = nil
    end
      
    # Sets the data string sent to the remote host. This value cannot have
    # a size greater than MAX_DATA.
    # 
    def data=(string)
       if string.size > MAX_DATA
         err = "cannot set data string larger than #{MAX_DATA} characters"
         raise ArgumentError, err
       end
         
       @data = string
    end

    # Associates the local end of the UDP connection with the given +host+
    # and +port+. This is essentially a wrapper for UDPSocket#bind.
    #
    def bind(host, port)
      @bind_host = host
      @bind_port = port
    end

    # Sends a simple text string to the host and checks the return string. If
    # the string sent and the string returned are a match then the ping was
    # successful and true is returned. Otherwise, false is returned.
    #
    def ping(host = @host)
      super(host)

      bool  = false
      udp   = UDPSocket.open
      array = []

      if @bind_host
        udp.bind(@bind_host, @bind_port)
      end

      start_time = Time.now

      begin
        Timeout.timeout(@timeout){
          udp.connect(host, @port)
          udp.send(@data, 0)
          array = udp.recvfrom(MAX_DATA)
        }
      rescue Errno::ECONNREFUSED, Errno::ECONNRESET => err
        if @@service_check
          @exception = err
        else
          bool = true
        end
      rescue Exception => err
        @exception = err
      else
        if array[0] == @data
          bool = true
        end
      ensure
        udp.close if udp
      end

      # There is no duration if the ping failed
      @duration = Time.now - start_time if bool

      bool
    end

  end
end
