#Request builder for marshalled data that unmarshalls content after receiving it.
module Restfulie::Client::HTTP

  module ResponseHolder
    attr_accessor :response
    
    def respond_to?(symbol)
      respond_to_rel?(symbol.to_s) || super(symbol)
    end
    
    private
    # whether this response contains specific relations
    def respond_to_rel?(rel)
      links.any? { |link| link.rel==rel }
    end
    
  end

  module RequestMarshaller
    include ::Restfulie::Client::HTTP::RequestBuilder
    
    # accepts a series of media types by default
    def initialize
      @acceptable_mediatypes = "application/atom+xml"
    end

    @@representations = {
      'application/atom+xml' => ::Restfulie::Common::Representation::Atom,
      'application/xml' => ::Restfulie::Common::Representation::XmlD
    }
    def self.register_representation(media_type,representation)
      @@representations[media_type] = representation
    end

    def self.content_type_for(media_type)
      content_type = media_type.split(';')[0] # [/(.*?);/, 1]
      type = @@representations[content_type]
      type ? type.new : nil
    end

    def accepts(media_types)
      @acceptable_mediatypes = media_types
      @default_representation = @@representations[media_types]
      raise "Undefined representation for #{media_types}" unless @default_representation
      super
    end

    def raw
      @raw = true
      self
    end

    #Executes super and unmarshalls it
    def request!(method, path, *args)
      representation = do_conneg_and_choose_representation(method, path, *args)
      if representation
        if has_payload?(method, path, *args)
          payload = get_payload(method, path, *args)
          rel = self.respond_to?(:rel) ? self.rel : ""
          payload = representation.marshal(payload, rel)
          args = set_marshalled_payload(method, path, payload, *args)
        end
        args = add_representation_headers(method, path, representation, *args)
      end
      response = super(method, path, *args) 
      parse_response(response, representation)
    end

    private
    
    # parses the http response.
    # first checks if its a 201, redirecting to the resource location.
    # otherwise check if its a raw request, returning the content itself.
    # finally, tries to parse the content with a mediatype handler or returns the response itself.
    def parse_response(response, representation)
      if response.code == 201
        location = response.headers['location']
        Restfulie.at(location).accepts(@acceptable_mediatypes).get!
      elsif @raw
        response
      elsif !response.body.empty?
        representation = RequestMarshaller.content_type_for(response.headers['content-type']) || Restfulie::Common::Representation::Generic.new
        unmarshalled = representation.unmarshal(response.body)
        unmarshalled.extend(ResponseHolder)
        unmarshalled.response = response
        unmarshalled
      else
        response
      end
    end
    
    def content_type_for(media_type)
      @@representations[media_type.split(';')[0]].new # [/(.*?);/, 1]
    end

    def do_conneg_and_choose_representation(method, path, *args)
      #TODO make a request to server (conneg)
      representation = @default_representation || @default_representation = @@representations['application/atom+xml']
      representation.new
    end

    def has_payload?(method, path, *args)
      [:put,:post].include?(method)
    end

    def get_payload(method, path, *args)
      args.extract_options! #remove header
      args.shift #payload
    end

    def set_marshalled_payload(method, path, payload, *args)
      headers = args.extract_options!
      args.shift #old payload
      args << payload << headers
      args
    end

    def add_representation_headers(method, path, representation, *args)
      headers = args.extract_options!
      args << representation.headers[method] 
      args
    end

  end

  #=This class includes RequestBuilder module.
  class RequestMarshallerExecutor
    include RequestMarshaller

    # * <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 at(path)
      @path = path
      self
    end
    def path
      @path
    end
  end

end