def normalized_search_arguments(*orig_arguments)
      arguments = {}

      # Two-arg style to one hash, if present
      if (orig_arguments.length > 1 ||
          (orig_arguments.length == 1 && ! orig_arguments.first.kind_of?(Hash)))
        arguments[:query] = orig_arguments.delete_at(0)
      end

      arguments.merge!(orig_arguments.first)  if orig_arguments.length > 0


      # allow strings for pagination (like from url query), change to
      # int please.
      [:page, :per_page, :start].each do |key|
        arguments.delete(key) if arguments[key].blank?
        arguments[key] = arguments[key].to_i if arguments[key]
      end
      arguments[:per_page] ||= configuration.default_per_page || DefaultPerPage

      # illegal arguments
      if (arguments[:start] && arguments[:page])
        raise ArgumentError.new("Can't supply both :page and :start")
      end
      if ( arguments[:per_page] &&
           self.max_per_page &&
           arguments[:per_page] > self.max_per_page)
        raise ArgumentError.new("#{arguments[:per_page]} is more than maximum :per_page of #{self.max_per_page} for #{self.class}")
      end


      # Normalize :page to :start, and vice versa
      if arguments[:page]
        arguments[:start] = (arguments[:page] - 1) * arguments[:per_page]
      elsif arguments[:start]
        arguments[:page] = (arguments[:start] / arguments[:per_page]) + 1
      end

      # normalize :sort from possibly symbol to string
      # TODO: raise if unrecognized sort key?
      if arguments[:sort]
        arguments[:sort] = arguments[:sort].to_s
      end


      # Multi-field search
      if arguments[:query].kind_of? Hash
        # Only if allowed
        unless self.multi_field_search?
          raise ArgumentError.new("You supplied a :query as a hash, but this engine (#{self.class}) does not suport multi-search. #{arguments[:query].inspect}")
        end
        # Multi-field search incompatible with :search_field or :semantic_search_field
        if arguments[:search_field].present?
          raise ArgumentError.new("You supplied a :query as a Hash, but also a :search_field, you can only use one. #{arguments.inspect}")
        end
        if arguments[:semantic_search_field].present?
          raise ArgumentError.new("You supplied a :query as a Hash, but also a :semantic_search_field, you can only use one. #{arguments.inspect}")
        end

        # translate semantic fields, raising for unfound fields if configured
        arguments[:query].transform_keys! do |key|
          new_key = self.semantic_search_map[key.to_s] || key

          if ( config_arg(arguments, :unrecognized_search_field) == "raise" &&
              ! self.search_keys.include?(new_key))
            raise ArgumentError.new("#{self.class.name} does not know about search_field #{new_key}, in query Hash #{arguments[:query]}")
          end

          new_key
        end

      end

      # translate semantic_search_field to search_field, or raise if
      # can't.
      if (semantic = arguments.delete(:semantic_search_field)) && ! semantic.blank?
        semantic = semantic.to_s
        # Legacy publication_title is now called source_title
        semantic = "source_title" if semantic == "publication_title"

        mapped = self.semantic_search_map[semantic]
        if config_arg(arguments, :unrecognized_search_field) == "raise" && ! mapped
          raise ArgumentError.new("#{self.class.name} does not know about :semantic_search_field #{semantic}")
        end
        arguments[:search_field] = mapped
      end
      if config_arg(arguments, :unrecognized_search_field) == "raise" && ! search_keys.include?(arguments[:search_field])
        raise ArgumentError.new("#{self.class.name} does not know about :search_field #{arguments[:search_field]}")
      end


      return arguments
    end
def normalized_search_arguments(*orig_arguments)
      arguments = {}

      # Two-arg style to one hash, if present
      if (orig_arguments.length > 1 ||
          (orig_arguments.length == 1 && ! orig_arguments.first.kind_of?(Hash)))
        arguments[:query] = orig_arguments.delete_at(0)
      end

      arguments.merge!(orig_arguments.first)  if orig_arguments.length > 0


      # allow strings for pagination (like from url query), change to
      # int please.
      [:page, :per_page, :start].each do |key|
        arguments.delete(key) if arguments[key].blank?
        arguments[key] = arguments[key].to_i if arguments[key]
      end
      arguments[:per_page] ||= configuration.default_per_page || DefaultPerPage

      # illegal arguments
      if (arguments[:start] && arguments[:page])
        raise ArgumentError.new("Can't supply both :page and :start")
      end
      if ( arguments[:per_page] &&
           self.max_per_page &&
           arguments[:per_page] > self.max_per_page)
        raise ArgumentError.new("#{arguments[:per_page]} is more than maximum :per_page of #{self.max_per_page} for #{self.class}")
      end


      # Normalize :page to :start, and vice versa
      if arguments[:page]
        arguments[:start] = (arguments[:page] - 1) * arguments[:per_page]
      elsif arguments[:start]
        arguments[:page] = (arguments[:start] / arguments[:per_page]) + 1
      end

      # normalize :sort from possibly symbol to string
      # TODO: raise if unrecognized sort key?
      if arguments[:sort]
        arguments[:sort] = arguments[:sort].to_s
      end


      # Multi-field search
      if arguments[:query].kind_of? Hash
        # Only if allowed
        unless self.multi_field_search?
          raise ArgumentError.new("You supplied a :query as a hash, but this engine (#{self.class}) does not suport multi-search. #{arguments[:query].inspect}")
        end
        # Multi-field search incompatible with :search_field or :semantic_search_field
        if arguments[:search_field].present?
          raise ArgumentError.new("You supplied a :query as a Hash, but also a :search_field, you can only use one. #{arguments.inspect}")
        end
        if arguments[:semantic_search_field].present?
          raise ArgumentError.new("You supplied a :query as a Hash, but also a :semantic_search_field, you can only use one. #{arguments.inspect}")
        end

        # translate semantic fields, raising for unfound fields if configured
        arguments[:query].transform_keys! do |key|
          new_key = self.semantic_search_map[key.to_s] || key

          if ( config_arg(arguments, :unrecognized_search_field) == "raise" &&
              ! self.search_keys.include?(new_key))
            raise ArgumentError.new("#{self.class.name} does not know about search_field #{new_key}, in query Hash #{arguments[:query]}")
          end

          new_key
        end

      end

      # translate semantic_search_field to search_field, or raise if
      # can't.
      if (semantic = arguments.delete(:semantic_search_field)) && ! semantic.blank?
        semantic = semantic.to_s
        # Legacy publication_title is now called source_title
        semantic = "source_title" if semantic == "publication_title"

        mapped = self.semantic_search_map[semantic]
        if config_arg(arguments, :unrecognized_search_field) == "raise" && ! mapped
          raise ArgumentError.new("#{self.class.name} does not know about :semantic_search_field #{semantic}")
        end
        arguments[:search_field] = mapped
      end
      if config_arg(arguments, :unrecognized_search_field) == "raise" && ! search_keys.include?(arguments[:search_field])
        raise ArgumentError.new("#{self.class.name} does not know about :search_field #{arguments[:search_field]}")
      end


      return arguments
    end
Take the arguments passed into #search, which can be flexibly given
 in several ways, and normalize to an expected single hash that
 will be passed to an engine's #search_implementation. The output
 of this method is a single hash, and is what a #search_implementation
 can expect to receive as an argument, with keys:

 [:query]        the query
 [:per_page]     will _always_ be present, using the default per_page if
                 none given by caller
 [:start, :page] both :start and :page will _always_ be present, regardless
                 of which the caller used. They will both be integers, even if strings passed in.
 [:search_field] A search field from the engine's #search_field_definitions, as string.
                 Even if the caller used :semantic_search_field, it'll be normalized
                 to the actual local search_field key on output.
 [:sort]         Sort key.