module CouchRest
  module Model
    module ClassProxy
      
      def self.included(base)
        base.extend(ClassMethods)
      end
      
      module ClassMethods

        # Return a proxy object which represents a model class on a
        # chosen database instance. This allows you to DRY operations
        # where a database is chosen dynamically.
        #  
        # ==== Example:
        #  
        #   db = CouchRest::Database.new(...)
        #   articles = Article.on(db)
        #
        #   articles.all { ... }
        #   articles.by_title { ... }
        #
        #   u = articles.get("someid")
        #
        #   u = articles.new(:title => "I like plankton")
        #   u.save    # saved on the correct database

        def on(database)
          Proxy.new(self, database)
        end
      end

      class Proxy #:nodoc:
        def initialize(klass, database)
          @klass = klass
          @database = database
        end
        
        # Base
        
        def new(*args)
          doc = @klass.new(*args)
          doc.database = @database
          doc
        end
        
        def method_missing(m, *args, &block)
          if has_view?(m)
            query = args.shift || {}
            return view(m, query, *args, &block)
          elsif m.to_s =~ /^find_(by_.+)/
            view_name = $1
            if has_view?(view_name)
              return first_from_view(view_name, *args) 
            end
          end
          super
        end
        
        # DocumentQueries
        
        def all(opts = {}, &block)
          docs = @klass.all({:database => @database}.merge(opts), &block)
          docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
          docs
        end
        
        def count(opts = {}, &block)
          @klass.all({:database => @database, :raw => true, :limit => 0}.merge(opts), &block)['total_rows']
        end
        
        def first(opts = {})
          doc = @klass.first({:database => @database}.merge(opts))
          doc.database = @database if doc && doc.respond_to?(:database)
          doc
        end
        
        def get(id)
          doc = @klass.get(id, @database)
          doc.database = @database if doc && doc.respond_to?(:database)
          doc
        end
        alias :find :get
        
        # Views
        
        def has_view?(view)
          @klass.has_view?(view)
        end
        
        def view(name, query={}, &block)
          docs = @klass.view(name, {:database => @database}.merge(query), &block)
          docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
          docs
        end
        
        def first_from_view(name, *args)
          # add to first hash available, or add to end
          (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
          doc = @klass.first_from_view(name, *args)
          doc.database = @database if doc && doc.respond_to?(:database)
          doc
        end
       
        # DesignDoc
        
        def design_doc
          @klass.design_doc
        end
        
        def refresh_design_doc
          @klass.refresh_design_doc(@database)
        end
        
        def save_design_doc
          @klass.save_design_doc(@database)
        end

      end
    end
  end
end