module DataMapper
  module Adapters
    class CouchDBAdapter < AbstractAdapter
      ConnectionError = Class.new(StandardError)
      
      # Persists one or many new resources
      #
      # @example
      #   adapter.create(collection)  # => 1
      #
      # Adapters provide specific implementation of this method
      #
      # @param [Enumerable<Resource>] resources
      #   The list of resources (model instances) to create
      #
      # @return [Integer]
      #   The number of records that were actually saved into the data-store
      #
      # @api semipublic
      def create(resources)
        raise NotImplementedError, "#{self.class}#create not implemented"
      end

      # Reads one or many resources from a datastore
      #
      # @example
      #   adapter.read(query)  # => [ { 'name' => 'Dan Kubb' } ]
      #
      # Adapters provide specific implementation of this method
      #
      # @param [Query] query
      #   the query to match resources in the datastore
      #
      # @return [Enumerable<Hash>]
      #   an array of hashes to become resources
      #
      # @api semipublic
      def read(query)
        with_connection do |connection|
          
        end
      end

      # Updates one or many existing resources
      #
      # @example
      #   adapter.update(attributes, collection)  # => 1
      #
      # Adapters provide specific implementation of this method
      #
      # @param [Hash(Property => Object)] attributes
      #   hash of attribute values to set, keyed by Property
      # @param [Collection] collection
      #   collection of records to be updated
      #
      # @return [Integer]
      #   the number of records updated
      #
      # @api semipublic
      def update(attributes, collection)
        raise NotImplementedError, "#{self.class}#update not implemented"
      end

      # Deletes one or many existing resources
      #
      # @example
      #   adapter.delete(collection)  # => 1
      #
      # Adapters provide specific implementation of this method
      #
      # @param [Collection] collection
      #   collection of records to be deleted
      #
      # @return [Integer]
      #   the number of records deleted
      #
      # @api semipublic
      def delete(collection)
        raise NotImplementedError, "#{self.class}#delete not implemented"
      end
      
      # Returns the name of the CouchDB database.
      #
      # @raise [RuntimeError] if the CouchDB database name is invalid.
      def db_name
        result = options[:path].scan(/^\/?([-_+%()$a-z0-9]+?)\/?$/).flatten[0]
        if result != nil
          return Addressable::URI.unencode_component(result)
        else
          raise StandardError, "Invalid database path: '#{options[:path]}'"
        end
      end

      # Returns the name of the CouchDB database after being escaped.
      def escaped_db_name
        return Addressable::URI.encode_component(
          self.db_name, Addressable::URI::CharacterClasses::UNRESERVED)
      end

            
      private
      
      def initialize(repo_name, options = {})
        super
        
        # When giving a repository URI rather than a hash, the database name
        # is :path, with a leading slash.
        if options[:path] && options[:database].nil?
          options[:database] = db_name
        end
        
        @resource_naming_convention = NamingConventions::Resource::Underscored
        @uri = Addressable::URI.new(options.only(:scheme, :host, :path, :port))
      end
      
      
      # Returns the CouchRest::Database instance for this process.
      #
      # @return [CouchRest::Database]
      #
      # @raise [ConnectionError]
      #   If the database requires you to authenticate, and the given username
      #   or password was not correct, a ConnectionError exception will be
      #   raised.
      #
      # @api semipublic
      def database
        unless defined?(@database)
          @database = connection.database!(@options[:database])
        end
        @database
      rescue Errno::ECONNREFUSED
        DataMapper.logger.error("Could Not Connect to Database!")
        raise(ConnectionError, "The adapter could not connect to Couchdb running at '#{@uri}'")
      end
      
      def with_connection
        begin
          yield connection
        rescue => e
          DataMapper.logger.error(exception.to_s)
          raise e
        end
      end
      
      # @see #connection
      def connection
        @connection ||= open_connection
      end
      
      # Returns CouchRest::Server instance
      # @return [CouchRest::Server]
      # @todo reset! connection and allow #uuid_batch_count to change
      #       also....do I need to use #chainable for this?
      # @api semipublic
      def open_connection
        CouchRest::Server.new(@uri)
      end
    end # CouchDBAdapter

    # Required naming scheme.
    CouchdbAdapter = CouchDBAdapter
    const_added(:CouchdbAdapter)
  end
end