require 'em-http'
require 'oauth/helper'
require 'oauth/request_proxy/em_http_request'

# Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient
# instance. This is purely syntactic sugar.
class EventMachine::HttpClient

  attr_reader :oauth_helper

  # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
  # this may add a header, additional query string parameters, or additional POST body parameters.
  # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
  # header.
  #
  # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host.
  # * consumer - OAuth::Consumer instance
  # * token - OAuth::Token instance
  # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
  #   +signature_method+, +nonce+, +timestamp+)
  #
  # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
  #
  # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1]
  def oauth!(http, consumer = nil, token = nil, options = {})
    options = { :request_uri      => normalized_oauth_uri(http),
                :consumer         => consumer,
                :token            => token,
                :scheme           => 'header',
                :signature_method => nil,
                :nonce            => nil,
                :timestamp        => nil }.merge(options)

    @oauth_helper = OAuth::Client::Helper.new(self, options)
    self.__send__(:"set_oauth_#{options[:scheme]}")
  end

  # Create a string suitable for signing for an HTTP request. This process involves parameter
  # normalization as specified in the OAuth specification. The exact normalization also depends
  # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
  # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
  # header.
  #
  # * http - Configured Net::HTTP instance
  # * consumer - OAuth::Consumer instance
  # * token - OAuth::Token instance
  # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
  #   +signature_method+, +nonce+, +timestamp+)
  #
  # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
  def signature_base_string(http, consumer = nil, token = nil, options = {})
    options = { :request_uri      => normalized_oauth_uri(http),
                :consumer         => consumer,
                :token            => token,
                :scheme           => 'header',
                :signature_method => nil,
                :nonce            => nil,
                :timestamp        => nil }.merge(options)

    OAuth::Client::Helper.new(self, options).signature_base_string
  end

  # This code was lifted from the em-http-request because it was removed from
  # the gem June 19, 2010
  # see: http://github.com/igrigorik/em-http-request/commit/d536fc17d56dbe55c487eab01e2ff9382a62598b
  def normalize_uri
    @normalized_uri ||= begin
      uri = @uri.dup
      encoded_query = encode_query(@uri, @options[:query])
      path, query = encoded_query.split("?", 2)
      uri.query = query unless encoded_query.empty?
      uri.path  = path
      uri
    end
  end

  protected

  def combine_query(path, query, uri_query)
    combined_query = if query.kind_of?(Hash)
      query.map { |k, v| encode_param(k, v) }.join('&')
    else
      query.to_s
    end
    if !uri_query.to_s.empty?
      combined_query = [combined_query, uri_query].reject {|part| part.empty?}.join("&")
    end
    combined_query.to_s.empty? ? path : "#{path}?#{combined_query}"
  end

  # Since we expect to get the host etc details from the http instance (...),
  # we create a fake url here. Surely this is a horrible, horrible idea?
  def normalized_oauth_uri(http)
    uri = URI.parse(normalize_uri.path)
    uri.host = http.address
    uri.port = http.port

    if http.respond_to?(:use_ssl?) && http.use_ssl?
      uri.scheme = "https"
    else
      uri.scheme = "http"
    end
    uri.to_s
  end

  def set_oauth_header
    headers = (self.options[:head] ||= {})
    headers['Authorization'] = @oauth_helper.header
  end

  def set_oauth_body
    raise NotImplementedError, 'please use the set_oauth_header method instead'
  end

  def set_oauth_query_string
    raise NotImplementedError, 'please use the set_oauth_header method instead'
  end

end
