# Copyright (C) 2008 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 GData
  module Client
    
    # A client object used to interact with different Google Data APIs.
    class Base
    
      # A subclass of GData::Auth that handles authentication signing.
      attr_accessor :auth_handler
      # A subclass of GData::HTTP that handles making HTTP requests.
      attr_accessor :http_service
      # Headers to include in every request.
      attr_accessor :headers
      # The API version being used.
      attr_accessor :version
      # The default URL for ClientLogin.
      attr_accessor :clientlogin_url
      # A default service name for ClientLogin (overriden by subclasses).
      attr_accessor :clientlogin_service
      # The broadest AuthSub scope for working with an API.
      # This is overriden by the service-specific subclasses.
      attr_accessor :authsub_scope
      # A short string identifying the current application.
      attr_accessor :source
      
      def initialize(options = {})
        options.each do |key, value|
          self.send("#{key}=", value)
        end
        
        @headers ||= {}
        @http_service ||= GData::HTTP::DefaultService
        @version ||= '2'
        @source ||= 'AnonymousApp'
      end
      
      # Sends an HTTP request with the given file as a stream
      def make_file_request(method, url, file_path, mime_type, entry = nil)
        if not File.readable?(file_path)
          raise ArgumentError, "File #{file_path} is not readable."
        end
        file = File.open(file_path, 'rb')
        @headers['Slug'] = File.basename(file_path)
        if entry
          @headers['MIME-Version'] = '1.0'
          body = GData::HTTP::MimeBody.new(entry, file, mime_type)
          @headers['Content-Type'] = body.content_type
          response = self.make_request(method, url, body)
        else
          @headers['Content-Type'] = mime_type
          response = self.make_request(method, url, file)
        end
        file.close
        return response
      end
      
      # Sends an HTTP request and return the response.
      def make_request(method, url, body = '')
        headers = self.prepare_headers
        request = GData::HTTP::Request.new(url, :headers => headers, 
          :method => method, :body => body)
        
        if @auth_handler and @auth_handler.respond_to?(:sign_request!)
          @auth_handler.sign_request!(request)
        end

        service = http_service.new
        response = service.make_request(request)
        
        case response.status_code  
        when 200, 201, 302
          #Do nothing, it's a success.
        when 401, 403
          raise AuthorizationError.new(response)
        when 400
          raise BadRequestError, response.body
        when 409
          raise VersionConflictError.new(response)
        when 500
          raise ServerError.new(response)
        else
          raise UnknownError.new(response)
        end
        
        return response
      end
      
      # Performs an HTTP GET against the API.
      def get(url)
        return self.make_request(:get, url)
      end
      
      # Performs an HTTP PUT against the API.
      def put(url, body)
        return self.make_request(:put, url, body)
      end
      
      # Performs an HTTP PUT with the given file
      def put_file(url, file_path, mime_type, entry = nil)
        return self.make_file_request(:put, url, file_path, mime_type, entry)
      end
      
      # Performs an HTTP POST against the API.
      def post(url, body)
        return self.make_request(:post, url, body)
      end
      
      # Performs an HTTP POST with the given file
      def post_file(url, file_path, mime_type, entry = nil)
        return self.make_file_request(:post, url, file_path, mime_type, entry)
      end
      
      # Performs an HTTP DELETE against the API.
      def delete(url)
        return self.make_request(:delete, url)
      end
      
      # Constructs some necessary headers for every request.
      def prepare_headers
        headers = @headers
        headers['GData-Version'] = @version
        headers['User-Agent'] = GData::Auth::SOURCE_LIB_STRING + @source
        # by default we assume we are sending Atom entries
        if not headers.has_key?('Content-Type')
          headers['Content-Type'] = 'application/atom+xml'
        end
        return headers
      end
      
      # Performs ClientLogin for the service. See GData::Auth::ClientLogin
      # for details.
      def clientlogin(username, password, captcha_token = nil, 
        captcha_answer = nil, service = nil, account_type = nil)
        if service.nil?
          service = @clientlogin_service
        end
        options = { :account_type => account_type }
        self.auth_handler = GData::Auth::ClientLogin.new(service, options)
        if @clientlogin_url
          @auth_handler.auth_url = @clientlogin_url
        end
        source = GData::Auth::SOURCE_LIB_STRING + @source
        @auth_handler.get_token(username, password, source, captcha_token, captcha_answer)
      end
      
      def authsub_url(next_url, secure = false, session = true, domain = nil,
        scope = nil)
        if scope.nil?
          scope = @authsub_scope
        end
        GData::Auth::AuthSub.get_url(next_url, scope, secure, session, domain)
      end
      
      # Sets an AuthSub token for the service.
      def authsub_token=(token)
        self.auth_handler = GData::Auth::AuthSub.new(token)
      end
      
      # Sets a private key to use with AuthSub requests.
      def authsub_private_key=(key)
        if @auth_handler.class == GData::Auth::AuthSub
          @auth_handler.private_key = key
        else
          raise Error, "An AuthSub token must be set first."
        end
      end
    end
  end
end
