require 'openssl'

module OEmbed
  module HttpHelper

    private

    # Given a URI, make an HTTP request
    #
    # The options Hash recognizes the following keys:
    # :timeout:: specifies the timeout (in seconds) for the http request.
    # :max_redirects:: the number of times this request will follow 3XX redirects before throwing an error. Default: 4
    def http_get(uri, options = {})
      found = false
      remaining_redirects = options[:max_redirects] ? options[:max_redirects].to_i : 4
      scheme, host, port = uri.scheme, uri.host, uri.port
      until found
        http = Net::HTTP.new(host, port)
        http.use_ssl = scheme == 'https'
        http.verify_mode = OpenSSL::SSL::VERIFY_PEER
        http.read_timeout = http.open_timeout = options[:timeout] if options[:timeout]

        methods = if RUBY_VERSION < "2.2"
            %w{scheme userinfo host port registry}
        else
            %w{scheme userinfo host port}
        end
        methods.each { |method| uri.send("#{method}=", nil) }
        req = Net::HTTP::Get.new(uri.to_s)
        req['User-Agent'] = "Mozilla/5.0 (compatible; ruby-oembed/#{OEmbed::VERSION})"
        res = http.request(req)

        if remaining_redirects == 0
          found = true
        elsif res.is_a?(Net::HTTPRedirection) && res.header['location']
          uri = URI.parse(res.header['location'])
          # If the 'location' is a relative path, keep the last scheme, host, & port.
          scheme = uri.scheme || scheme
          host = uri.host || host
          port = uri.port || port
          remaining_redirects -= 1
        else
          found = true
        end
      end

      case res
      when Net::HTTPNotImplemented
        raise OEmbed::UnknownFormat
      when Net::HTTPNotFound
        raise OEmbed::NotFound, uri
      when Net::HTTPSuccess
        res.body
      else
        raise OEmbed::UnknownResponse, res && res.respond_to?(:code) ? res.code : 'Error'
      end
    rescue StandardError
      # Convert known errors into OEmbed::UnknownResponse for easy catching
      # up the line. This is important if given a URL that doesn't support
      # OEmbed. The following are known errors:
      # * Net::* errors like Net::HTTPBadResponse
      # * JSON::JSONError errors like JSON::ParserError
      if defined?(::JSON) && $!.is_a?(::JSON::JSONError) || $!.class.to_s =~ /\ANet::/
        raise OEmbed::UnknownResponse, res && res.respond_to?(:code) ? res.code : 'Error'
      else
        raise $!
      end
    end

  end
end
