# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


module Google
  class APIClient
    ##
    # This class wraps a result returned by an API call.
    class Result
      extend Forwardable
      
      ##
      # Init the result
      #
      # @param [Google::APIClient::Request] request
      #   The original request
      # @param [Faraday::Response] response
      #   Raw HTTP Response
      def initialize(request, response)
        @request = request
        @response = response
        @media_upload = reference if reference.kind_of?(ResumableUpload)
      end

      # @return [Google::APIClient::Request] Original request object
      attr_reader :request
      # @return [Faraday::Response] HTTP response
      attr_reader :response
      # @!attribute [r] reference
      #   @return [Google::APIClient::Request] Original request object
      #   @deprecated See {#request}
      alias_method :reference, :request # For compatibility with pre-beta clients

      # @!attribute [r] status
      #   @return [Fixnum] HTTP status code
      # @!attribute [r] headers
      #   @return [Hash] HTTP response headers
      # @!attribute [r] body
      #   @return [String] HTTP response body
      def_delegators :@response, :status, :headers, :body

      # @!attribute [r] resumable_upload
      # @return [Google::APIClient::ResumableUpload] For resuming media uploads
      def resumable_upload        
        @media_upload ||= (
          options = self.reference.to_hash.merge(
            :uri => self.headers['location'],
            :media => self.reference.media
          )
          Google::APIClient::ResumableUpload.new(options)
        )
      end
      
      ##
      # Get the content type of the response
      # @!attribute [r] media_type
      # @return [String]
      #  Value of content-type header
      def media_type
        _, content_type = self.headers.detect do |h, v|
          h.downcase == 'Content-Type'.downcase
        end
        if content_type
          return content_type[/^([^;]*);?.*$/, 1].strip.downcase
        else
          return nil
        end
      end
      
      ##
      # Check if request failed
      #
      # @!attribute [r] error?
      # @return [TrueClass, FalseClass]
      #   true if result of operation is an error
      def error?
        return self.response.status >= 400
      end

      ##
      # Check if request was successful
      #
      # @!attribute [r] success?
      # @return [TrueClass, FalseClass]
      #   true if result of operation was successful
      def success?
        return !self.error?
      end
      
      ##
      # Extracts error messages from the response body
      #
      # @!attribute [r] error_message
      # @return [String]
      #   error message, if available
      def error_message
        if self.data?
          if self.data.respond_to?(:error) &&
             self.data.error.respond_to?(:message)
            # You're going to get a terrible error message if the response isn't
            # parsed successfully as an error.
            return self.data.error.message
          elsif self.data['error'] && self.data['error']['message']
            return self.data['error']['message']
          end
        end
        return self.body
      end

      ##
      # Check for parsable data in response
      #
      # @!attribute [r] data?
      # @return [TrueClass, FalseClass]
      #   true if body can be parsed
      def data?
        !(self.body.nil? || self.body.empty? || self.media_type != 'application/json')
      end
      
      ##
      # Return parsed version of the response body.
      #
      # @!attribute [r] data
      # @return [Object, Hash, String]
      #   Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
      def data
        return @data ||= (begin
          if self.data?
            media_type = self.media_type
            data = self.body
            case media_type
            when 'application/json'
              data = MultiJson.load(data)
              # Strip data wrapper, if present
              data = data['data'] if data.has_key?('data')
            else
              raise ArgumentError,
                "Content-Type not supported for parsing: #{media_type}"
            end
            if @request.api_method && @request.api_method.response_schema
              # Automatically parse using the schema designated for the
              # response of this API method.
              data = @request.api_method.response_schema.new(data)
              data
            else
              # Otherwise, return the raw unparsed value.
              # This value must be indexable like a Hash.
              data
            end
          end
        end)
      end

      ##
      # Get the token used for requesting the next page of data
      #
      # @!attribute [r] next_page_token
      # @return [String]
      #   next page token
      def next_page_token
        if self.data.respond_to?(:next_page_token)
          return self.data.next_page_token
        elsif self.data.respond_to?(:[])
          return self.data["nextPageToken"]
        else
          raise TypeError, "Data object did not respond to #next_page_token."
        end
      end

      ##
      # Build a request for fetching the next page of data
      # 
      # @return [Google::APIClient::Request]
      #   API request for retrieving next page, nil if no page token available
      def next_page
        return nil unless self.next_page_token
        merged_parameters = Hash[self.reference.parameters].merge({
          self.page_token_param => self.next_page_token
        })
        # Because Requests can be coerced to Hashes, we can merge them,
        # preserving all context except the API method parameters that we're
        # using for pagination.
        return Google::APIClient::Request.new(
          Hash[self.reference].merge(:parameters => merged_parameters)
        )
      end

      ##
      # Get the token used for requesting the previous page of data
      #
      # @!attribute [r] prev_page_token
      # @return [String]
      #   previous page token
      def prev_page_token
        if self.data.respond_to?(:prev_page_token)
          return self.data.prev_page_token
        elsif self.data.respond_to?(:[])
          return self.data["prevPageToken"]
        else
          raise TypeError, "Data object did not respond to #next_page_token."
        end
      end

      ##
      # Build a request for fetching the previous page of data
      # 
      # @return [Google::APIClient::Request]
      #   API request for retrieving previous page, nil if no page token available
      def prev_page
        return nil unless self.prev_page_token
        merged_parameters = Hash[self.reference.parameters].merge({
          self.page_token_param => self.prev_page_token
        })
        # Because Requests can be coerced to Hashes, we can merge them,
        # preserving all context except the API method parameters that we're
        # using for pagination.
        return Google::APIClient::Request.new(
          Hash[self.reference].merge(:parameters => merged_parameters)
        )
      end
      
      ##
      # Pagination scheme used by this request/response
      #
      # @!attribute [r] pagination_type
      # @return [Symbol]
      #  currently always :token
      def pagination_type
        return :token
      end

      ##
      # Name of the field that contains the pagination token
      #
      # @!attribute [r] page_token_param
      # @return [String]
      #  currently always 'pageToken'
      def page_token_param
        return "pageToken"
      end

    end
  end
end
