module Restfulie::Client::HTTP #:nodoc:

    #=Response
    # Default response class
    class Response

      attr_reader :method
      attr_reader :path
      attr_reader :code
      attr_reader :body
      attr_reader :headers
      attr_reader :request

      def initialize(method, path, code, body, headers, request)
        @method = method
        @path = path
        @code = code
        @body = body 
        @headers = headers
        @request = request
      end
      
      def parse
        self
      end

    end

    #=ResponseHandler
    # You can change instance registering a class according to the code.
    #
    #==Example
    #
    #   class RequestExecutor
    #     include RequestAdapter
    #     def initialize(host)
    #       self.host=host
    #     end
    #   end
    #
    #   class FakeResponse < Restfulie::Client::HTTP::Response
    #   end
    #
    #   Restfulie::Client::HTTP::ResponseHandler.register(201,FakeResponse)  
    #   @re = Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com')
    #   puts @re.as('application/atom+xml').get!('/posts').class.to_i #=> FakeResponse
    #
    module ResponseHandler

      @@response_handlers = {}
      ## 
      # :singleton-method:
      # Response handlers attribute reader
      # * code: HTTP status code
      def self.handlers(code)
        @@response_handlers[code] 
      end

      ## 
      # :singleton-method:
      # Use to register response handlers
      #
      # * <tt>code: HTTP status code</tt>
      # * <tt>response_class: Response class</tt>
      #
      #==Example:
      #   class FakeResponse < ::Restfulie::Client::HTTP::Response
      #   end
      #
      #   Restfulie::Client::HTTP::ResponseHandler.register(200,FakeResponse)  
      #
      def self.register(code,response_class)
        @@response_handlers[code] = response_class 
      end

      ## 
      # :singleton-method:
      # Request Adapter uses this method to choose response instance
      #
      # *<tt>method: :get,:post,:delete,:head,:put</tt>
      # *<tt>path: '/posts'</tt>
      # *<tt>http_response</tt>
      #
      def self.handle(method, path, http_response, request)
        response_class = @@response_handlers[http_response.code.to_i] || Response
        headers = {}
        http_response.header.each { |k, v| headers[k] = v }
        response_class.new( method, path, http_response.code.to_i, http_response.body, headers, request)
      end

    end

    #
    # Request Adapter provides a minimal interface to exchange information between server over HTTP protocol through simple adapters.
    # 
    # All the concrete adapters follow the interface laid down in this module.
    # Default connection provider is net/http
    #
    #==Example
    #
    #   @re = ::Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com') #this class includes RequestAdapter module.
    #   puts @re.as('application/atom+xml').get!('/posts').title #=> 'Hello World!'
    #
    module RequestAdapter

      attr_reader   :host
      attr_accessor :cookies
      attr_writer   :default_headers

      def host=(host)
        if host.is_a?(::URI)
          @host = host
        else
          @host = ::URI.parse(host)
        end
      end

      def default_headers
        @default_headers ||= {}
      end

      #GET HTTP verb without {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def get(path, *args)
        request(:get, path, *args)
      end

      #HEAD HTTP verb without {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def head(path, *args)
        request(:head, path, *args)
      end

      #POST HTTP verb without {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>payload: 'some text'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def post(path, payload, *args)
        request(:post, path, payload, *args)
      end

      #PUT HTTP verb without {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>payload: 'some text'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def put(path, payload, *args)
        request(:put, path, payload, *args)
      end

      #DELETE HTTP verb without {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def delete(path, *args)
        request(:delete, path, *args)
      end

      #GET HTTP verb {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def get!(path, *args)
        request!(:get, path, *args)
      end

      #HEAD HTTP verb {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def head!(path, *args)
        request!(:head, path, *args)
      end

      #POST HTTP verb {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>payload: 'some text'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def post!(path, payload, *args)
        request!(:post, path, payload, *args)
      end

      #PUT HTTP verb {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>payload: 'some text'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def put!(path, payload, *args)
        request!(:put, path, payload, *args)
      end

      #DELETE HTTP verb {Error}
      # * <tt>path: '/posts'</tt>
      # * <tt>headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def delete!(path, *args)
        request!(:delete, path, *args)
      end

      #Executes a request against your server and return a response instance without {Error}
      # * <tt>method: :get,:post,:delete,:head,:put</tt>
      # * <tt>path: '/posts'</tt>
      # * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def request(method, path, *args)
        request!(method, path, *args) 
      rescue Error::RESTError => se
        se.response
      end

      #Executes a request against your server and return a response instance.
      # * <tt>method: :get,:post,:delete,:head,:put</tt>
      # * <tt>path: '/posts'</tt>
      # * <tt>args: payload: 'some text' and/or headers: {'Accpet' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
      def request!(method, path, *args)
        headers = default_headers.merge(args.extract_options!)
        unless @host.user.blank? && @host.password.blank?
          headers["Authorization"] = "Basic " + ["#{@host.user}:#{@host.password}"].pack("m").delete("\r\n")
        end
        headers['cookie'] = @cookies if @cookies
        args << headers

        ::Restfulie::Common::Logger.logger.info(request_to_s(method, path, *args)) if ::Restfulie::Common::Logger.logger
        begin
          connection = get_connection_provider.send(method, path, *args)
          response = ResponseHandler.handle(method, path, connection, self).parse
        rescue Exception => e
          raise Error::ServerNotAvailableError.new(self, Response.new(method, path, 503, nil, {}, self), e )
        end 

        case response.code
        when 100..299
          response 
        when 300..399
          raise Error::Redirection.new(self, response)
        when 400
          raise Error::BadRequest.new(self, response)
        when 401
          raise Error::Unauthorized.new(self, response)
        when 403
          raise Error::Forbidden.new(self, response)
        when 404
          raise Error::NotFound.new(self, response)
        when 405
          raise Error::MethodNotAllowed.new(self, response)
        when 407
          raise Error::ProxyAuthenticationRequired.new(self, response)
        when 409
          raise Error::Conflict.new(self, response)
        when 410
          raise Error::Gone.new(self, response)
        when 412
          raise Error::PreconditionFailed.new(self, response)
        when 402, 406, 408, 411, 413..499
          raise Error::ClientError.new(self, response)
        when 501
          raise Error::NotImplemented.new(self, response)
        when 500, 502..599
          raise Error::ServerError.new(self, response)
        else
          raise Error::UnknownError.new(self, response)
        end
      end

      private

      def get_connection_provider
        @connection ||= ::Net::HTTP.new(@host.host, @host.port)
      end

      protected

      def request_to_s(method, path, *args)
        result = ["#{method.to_s.upcase} #{path}"]

        arguments = args.dup
        headers = arguments.extract_options!

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

        result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")

        (result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
      end

    end

    #=RequestBuilder
    # Uses RequestAdapater to create a HTTP Request DSL 
    #
    #==Example:
    #
    #   @builder = ::Restfulie::Client::HTTP::RequestBuilderExecutor.new("http://restfulie.com") #this class includes RequestBuilder module.
    #   @builder.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200
    #
    module RequestBuilder
      include RequestAdapter

      #Set host
      def at(url)
        self.host = url
        self
      end
     
      #Set Content-Type and Accept headers
      def as(content_type)
        headers['Content-Type'] = content_type
        accepts(content_type)
      end
     
      #Set Accept headers
      def accepts(content_type)
        headers['Accept'] = content_type
        self
      end
     
      #
      #Merge internal header
      #
      # * <tt>headers (e.g. {'Cache-control' => 'no-cache'})</tt>
      #
      def with(headers)
        self.headers.merge!(headers)
        self
      end

      def headers
        @headers || @headers = {}
      end
      
      #Path (e.g. http://restfulie.com/posts => /posts)
      def path
        host.path
      end

      def get
        request(:get, path, headers)
      end

      def head
        request(:head, path, headers)
      end

      def post(payload)
        request(:post, path, payload, headers)
      end

      def put(payload)
        request(:put, path, payload, headers)
      end

      def delete
        request(:delete, path, headers)
      end

      def get!
        request!(:get, path, headers)
      end

      def head!
        request!(:head, path, headers)
      end

      def post!(payload)
        request!(:post, path, payload, headers)
      end

      def put!(payload)
        request!(:put, path, payload, headers)
      end

      def delete!
        request!(:delete, path, headers)
      end

      protected

      def headers=(h)
        @headers = h
      end

    end

    #=RequestHistory
    # Uses RequestBuilder and remind previous requests
    #
    #==Example:
    #
    #   @executor = ::Restfulie::Client::HTTP::RequestHistoryExecutor.new("http://restfulie.com") #this class includes RequestHistory module.
    #   @executor.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #first request
    #   @executor.at('/blogs').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #second request
    #   @executor.request_history!(0) #doing first request again
    #
    module RequestHistory
      include RequestBuilder

      attr_accessor_with_default :max_to_remind, 10

      def snapshots
        @snapshots || @snapshots = []
      end

      def request!(method=nil, path=nil, *args)#:nodoc:
        if method == nil || path == nil 
          raise 'History not selected' unless @snapshot
          super( @snapshot[:method], @snapshot[:path], *@snapshot[:args] )
        else
          @snapshot = make_snapshot(method, path, *args)
          unless snapshots.include?(@snapshot)
            snapshots.shift if snapshots.size >= max_to_remind
            snapshots << @snapshot
          end
          super
        end
      end

      def request(method=nil, path=nil, *args)#:nodoc:
        request!(method, path, *args) 
      rescue Error::RESTError => se
        se.response
      end

      def history(number)
        @snapshot = snapshots[number]
        raise "Undefined snapshot for #{number}" unless @snapshot
        self.host    = @snapshot[:host]
        self.cookies = @snapshot[:cookies]
        self.headers = @snapshot[:headers]
        self.default_headers = @snapshot[:default_headers]
        at(@snapshot[:path])
      end

      private

      def make_snapshot(method, path, *args)
        arguments = args.dup
        cutom_headers = arguments.extract_options!
        { :host            => self.host.dup,
          :default_headers => self.default_headers.dup,
          :headers         => self.headers.dup,
          :cookies         => self.cookies,
          :method          => method, 
          :path            => path,
          :args            => arguments << self.headers.merge(cutom_headers)   }
      end

    end

    #=This class includes RequestAdapter module.
    class RequestExecutor
      include RequestAdapter

      # * <tt> host (e.g. 'http://restfulie.com') </tt>
      # * <tt> default_headers  (e.g. {'Cache-control' => 'no-cache'} ) </tt>
      def initialize(host, default_headers = {})
        self.host=host
        self.default_headers=default_headers
      end

    end

    #=This class includes RequestBuilder module.
    class RequestBuilderExecutor
      include RequestBuilder

      # * <tt> host (e.g. 'http://restfulie.com') </tt>
      # * <tt> default_headers  (e.g. {'Cache-control' => 'no-cache'} ) </tt>
      def initialize(host, default_headers = {})
        self.host=host
        self.default_headers=default_headers
      end
      def host=(host)
        super
        at(self.host.path)
      end
      def at(path)
        @path = path
        self
      end
      def path
        @path
      end
    end

    #=This class inherits RequestBuilderExecutor and include RequestHistory module.
    class RequestHistoryExecutor < RequestBuilderExecutor
      include RequestHistory
    end

end