# frozen_string_literal: true

# Copyright (C) 2015-2020 MongoDB 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 Mongo
  class BulkWrite
    # Defines behavior around transformations.
    #
    # @api private
    #
    # @since 2.1.0
    module Transformable
      # The delete many model constant.
      #
      # @since 2.1.0
      DELETE_MANY = :delete_many

      # The delete one model constant.
      #
      # @since 2.1.0
      DELETE_ONE = :delete_one

      # The insert one model constant.
      #
      # @since 2.1.0
      INSERT_ONE = :insert_one

      # The replace one model constant.
      #
      # @since 2.1.0
      REPLACE_ONE = :replace_one

      # The update many model constant.
      #
      # @since 2.1.0
      UPDATE_MANY = :update_many

      # The update one model constant.
      #
      # @since 2.1.0
      UPDATE_ONE = :update_one

      # Proc to transform delete many ops.
      #
      # @since 2.1.0
      DELETE_MANY_TRANSFORM = lambda { |doc|
        {
          Operation::Q => doc[:filter],
          Operation::LIMIT => 0,
        }.tap do |d|
          d[Operation::COLLATION] = doc[:collation] if doc[:collation]
          d['hint'] = doc[:hint] if doc[:hint]
        end
      }

      # Proc to transform delete one ops.
      #
      # @since 2.1.0
      DELETE_ONE_TRANSFORM = lambda { |doc|
        {
          Operation::Q => doc[:filter],
          Operation::LIMIT => 1,
        }.tap do |d|
          d[Operation::COLLATION] = doc[:collation] if doc[:collation]
          d['hint'] = doc[:hint] if doc[:hint]
        end
      }

      # Proc to transform insert one ops.
      #
      # @since 2.1.0
      INSERT_ONE_TRANSFORM = lambda { |doc|
        doc
      }

      # Proc to transform replace one ops.
      #
      # @since 2.1.0
      REPLACE_ONE_TRANSFORM = lambda { |doc|
        {
          Operation::Q => doc[:filter],
          Operation::U => doc[:replacement],
        }.tap do |d|
          d['upsert'] = true if doc[:upsert]
          d[Operation::COLLATION] = doc[:collation] if doc[:collation]
          d['hint'] = doc[:hint] if doc[:hint]
          d['sort'] = doc[:sort] if doc[:sort]
        end
      }

      # Proc to transform update many ops.
      #
      # @since 2.1.0
      UPDATE_MANY_TRANSFORM = lambda { |doc|
        {
          Operation::Q => doc[:filter],
          Operation::U => doc[:update],
          Operation::MULTI => true,
        }.tap do |d|
          d['upsert'] = true if doc[:upsert]
          d[Operation::COLLATION] = doc[:collation] if doc[:collation]
          d[Operation::ARRAY_FILTERS] = doc[:array_filters] if doc[:array_filters]
          d['hint'] = doc[:hint] if doc[:hint]
        end
      }

      # Proc to transform update one ops.
      #
      # @since 2.1.0
      UPDATE_ONE_TRANSFORM = lambda { |doc|
        {
          Operation::Q => doc[:filter],
          Operation::U => doc[:update],
        }.tap do |d|
          d['upsert'] = true if doc[:upsert]
          d[Operation::COLLATION] = doc[:collation] if doc[:collation]
          d[Operation::ARRAY_FILTERS] = doc[:array_filters] if doc[:array_filters]
          d['hint'] = doc[:hint] if doc[:hint]
          d['sort'] = doc[:sort] if doc[:sort]
        end
      }

      # Document mappers from the bulk api input into proper commands.
      #
      # @since 2.1.0
      MAPPERS = {
        DELETE_MANY => DELETE_MANY_TRANSFORM,
        DELETE_ONE => DELETE_ONE_TRANSFORM,
        INSERT_ONE => INSERT_ONE_TRANSFORM,
        REPLACE_ONE => REPLACE_ONE_TRANSFORM,
        UPDATE_MANY => UPDATE_MANY_TRANSFORM,
        UPDATE_ONE => UPDATE_ONE_TRANSFORM
      }.freeze

      private

      def transform(name, document)
        validate(name, document)
        MAPPERS[name].call(document)
      end
    end
  end
end
