# encoding: utf-8
#
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you 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 'selenium/webdriver/common/platform'
require 'socket'

module Selenium
  module WebDriver
    class SocketPoller
      def initialize(host, port, timeout = 0, interval = 0.25)
        @host     = host
        @port     = Integer(port)
        @timeout  = Float(timeout)
        @interval = interval
      end

      #
      # Returns true if the server is listening within the given timeout,
      # false otherwise.
      #
      # @return [Boolean]
      #

      def connected?
        with_timeout { listening? }
      end

      #
      # Returns true if the server has stopped listening within the given timeout,
      # false otherwise.
      #
      # @return [Boolean]
      #

      def closed?
        with_timeout { !listening? }
      end

      private

      CONNECT_TIMEOUT = 5

      NOT_CONNECTED_ERRORS = [Errno::ECONNREFUSED, Errno::ENOTCONN, SocketError].freeze
      NOT_CONNECTED_ERRORS << Errno::EPERM if Platform.cygwin?

      CONNECTED_ERRORS = [Errno::EISCONN].freeze
      CONNECTED_ERRORS << Errno::EINVAL if Platform.windows?

      if Platform.jruby?
        # we use a plain TCPSocket here since JRuby has issues select()ing on a connecting socket
        # see http://jira.codehaus.org/browse/JRUBY-5165
        def listening?
          TCPSocket.new(@host, @port).close
          true
        rescue *NOT_CONNECTED_ERRORS
          false
        end
      else
        def listening?
          addr     = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
          sock     = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
          sockaddr = Socket.pack_sockaddr_in(@port, addr[0][3])

          begin
            sock.connect_nonblock sockaddr
          rescue Errno::EINPROGRESS
            retry if IO.select(nil, [sock], nil, CONNECT_TIMEOUT)
            raise Errno::ECONNREFUSED
          rescue *CONNECTED_ERRORS
            # yay!
          end

          sock.close
          true
        rescue *NOT_CONNECTED_ERRORS
          sock.close if sock
          $stderr.puts [@host, @port].inspect if $DEBUG
          false
        end
      end

      def with_timeout
        max_time = time_now + @timeout

        (
          return true if yield
          wait
        ) until time_now > max_time

        false
      end

      def wait
        sleep @interval
      end

      # for testability
      def time_now
        Time.now
      end
    end # SocketPoller
  end # WebDriver
end # Selenium
