module Restfully
  module HTTP

    class Request
      include Helper

      attr_reader :session, :method, :uri, :head, :body, :attempts
      attr_accessor :retry_on_error, :wait_before_retry

      def initialize(session, method, path, options = {})
        @session = session
        @attempts = 0

        request = options.symbolize_keys

        @retry_on_error = request[:retry_on_error] || session.config[:retry_on_error]
        @wait_before_retry = request[:wait_before_retry] || session.config[:wait_before_retry]

        request[:method] = method

        request[:head] = sanitize_head(@session.default_headers).merge(
          build_head(request)
        )
        origin_content_type = request[:head].delete('Origin-Content-Type')
        request[:head]['Content-Type'] ||= origin_content_type if origin_content_type

        request[:uri] = @session.uri_to(path)
        if request[:query]
          request[:uri].query_values = sanitize_query(request[:query])
        end

        request[:body] = if [:post, :put].include?(request[:method])
          build_body(request)
        end

        @method, @uri, @head, @body = request.values_at(
          :method, :uri, :head, :body
        )

      end

      # Updates the request header and query parameters.
      # Returns nil if no changes were made, otherwise self.
      def update!(options = {})
        objects_that_may_be_updated = [@uri, @head]
        old_hash = objects_that_may_be_updated.map(&:hash)
        opts = options.symbolize_keys
        @head.merge!(build_head(opts))
        if opts[:query]
          @uri.query_values = sanitize_query(opts[:query])
        end
        if old_hash == objects_that_may_be_updated.map(&:hash)
          nil
        else
          self
        end
      end

      def inspect
        "#{method.to_s.upcase} #{uri.to_s}, head=#{head.inspect}, body=#{body.inspect}"
      end

      def no_cache?
        head['Cache-Control'] && head['Cache-Control'].include?('no-cache')
      end

      def no_cache!
        @forced_cache = true
        head['Cache-Control'] = 'no-cache'
      end

      def forced_cache?
        !!@forced_cache
      end

      def execute!
        session.logger.debug self.inspect
        resource = RestClient::Resource.new(
          uri.to_s,
          :headers => head
        )

        begin
          reqcode, reqhead, reqbody = resource.send(method, body || {})
          response = Response.new(session, reqcode, reqhead, reqbody)
          session.logger.debug response.inspect
          response
        rescue Errno::ECONNREFUSED => e
          retry! || raise(e)
        end
      end

      def retry!
        if @attempts < @retry_on_error
          @attempts+=1
          session.logger.warn "Encountered connection or server error. Retrying in #{@wait_before_retry}s... [#{@attempts}/#{@retry_on_error}]"
          sleep @wait_before_retry if @wait_before_retry > 0
          execute!
        else
          false
        end
      end

      def remove_no_cache!
        @forced_cache = false
        if head['Cache-Control']
          head['Cache-Control'] = head['Cache-Control'].split(/\s+,\s+/).reject{|v|
            v =~ /no-cache/i
          }.join(",")
        end
      end

      protected
      def build_head(options = {})
        sanitize_head(
          options.delete(:headers) || options.delete(:head) || {}
        )
      end

      def build_body(options = {})
        if options[:body]
          type = MediaType.find(options[:head]['Content-Type'])
          if type.nil?
            type = MediaType.find('application/x-www-form-urlencoded')
            options[:head]['Content-Type'] = type.default_type
          end
          type.serialize(options[:body], :serialization => options[:serialization])
        else
          nil
        end
      end

    end
  end
end