module CouchRest
  module Model
    module Designs

      # Design Document Migrations Support
      #
      # A series of methods used inside design documents in order to perform migrations.
      #
      module Migrations

        # Migrate the design document preventing downtime on a production
        # system. Typically this will be used when auto updates are disabled.
        #
        # Steps taken are:
        #
        #  1. Compare the checksum with the current version
        #  2. If different, create a new design doc with timestamp
        #  3. Wait until the view returns a result
        #  4. Copy over the original design doc
        #
        # If a block is provided, it will be called with the result of the migration:
        #
        #  * :no_change - Nothing performed as there are no changes.
        #  * :created   - Add a new design doc as non existed
        #  * :migrated  - Migrated the existing design doc.
        #
        # This can be used for progressivly printing the results of the migration.
        #
        # After completion, either a "cleanup" Proc object will be provided to finalize
        # the process and copy the document into place, or simply nil if no cleanup is
        # required. For example:
        #
        #     print "Synchronising Cat model designs: "
        #     callback = Cat.design_doc.migrate do |res|
        #       puts res.to_s
        #     end
        #     if callback
        #       puts "Cleaning up."
        #       callback.call
        #     end
        #
        def migrate(db = nil, &block)
          db    ||= database
          doc     = load_from_database(db)
          cleanup = nil
          id      = self['_id']

          if !doc
            # make sure the checksum has been calculated
            checksum! if !self['couchrest-hash']
            
            # no need to migrate, just save it
            new_doc = to_hash.dup
            db.save_doc(new_doc)

            result = :created
          elsif doc['couchrest-hash'] != checksum
            id += "_migration"

            # Delete current migration if there is one
            old_migration = load_from_database(db, id)
            db.delete_doc(old_migration) if old_migration

            # Save new design doc
            new_doc = doc.merge(to_hash)
            new_doc['_id'] = id
            new_doc.delete('_rev')
            db.save_doc(new_doc)

            # Proc definition to copy the migration doc over the original
            cleanup = Proc.new do
              db.copy_doc(new_doc, doc)
              db.delete_doc(new_doc)
              self
            end

            result = :migrated
          else
            # Already up to date
            result = :no_change
          end

          wait_for_view_update_completion(db, new_doc)

          yield result if block_given?

          cleanup
        end

        # Perform a single migration and inmediatly request a cleanup operation:
        #
        #     print "Synchronising Cat model designs: "
        #     Cat.design_doc.migrate! do |res|
        #       puts res.to_s
        #     end
        #
        def migrate!(db = nil, &block)
          callback = migrate(db, &block)
          if callback.is_a?(Proc)
            callback.call
          else
            callback
          end
        end

        private

        def wait_for_view_update_completion(db, attrs)
          if attrs && !attrs['views'].empty?
            # Prepare a design doc we can use
            doc = CouchRest::Design.new(attrs)
            doc.database = db

            # Request view, to trigger a *background* view update
            doc.view(doc['views'].keys.first, :limit => 1, :stale => "update_after")

            # Poll the view update process
            while true
              sleep 1
              info = doc.info
              if !info || !info['view_index']
                raise "Migration error, unable to load design doc info: #{db.root}/#{doc.id}"
              end
              break if !info['view_index']['updater_running']
            end
          end
        end


      end

    end
  end
end
