require 'set'

module Plucky
  module Normalizers
    class CriteriaHashValue

      # Internal: Used by normalized_value to determine if we need to run the
      # value through another criteria hash to normalize it.
      NestingOperators = Set[:$or, :$and, :$nor]

      def initialize(criteria_hash)
        @criteria_hash = criteria_hash
      end

      # Public: Returns value normalized for Mongo
      #
      # parent_key - The parent key if nested, otherwise same as key
      # key - The key we are currently normalizing
      # value - The value that should be normalized
      #
      # Returns value normalized for Mongo
      def call(parent_key, key, value)
        case value
          when Array, Set
            if object_id?(parent_key)
              value = value.map { |v| to_object_id(v) }
            end

            if nesting_operator?(key)
              value.map  { |v| criteria_hash_class.new(v, options).to_hash }
            elsif parent_key == key && !modifier?(key) && !value.empty?
              # we're not nested and not the value for a symbol operator
              {:$in => value.to_a}
            else
              # we are a value for a symbol operator or nested hash
              value.to_a
            end
          when Time
            value.utc
          when String
            if object_id?(key)
              return to_object_id(value)
            end
            value
          when Hash
            value.each { |k, v| value[k] = call(key, k, v) }
            value
          when Regexp
            Regexp.new(value)
          else
            value
        end
      end

      # Private: Ensures value is object id if possible
      def to_object_id(value)
        Plucky.to_object_id(value)
      end

      # Private: Returns class of provided criteria hash
      def criteria_hash_class
        @criteria_hash.class
      end

      # Private: Returns options of provided criteria hash
      def options
        @criteria_hash.options
      end

      # Private: Returns true or false if key should be converted to object id
      def object_id?(key)
        @criteria_hash.object_id?(key)
      end

      # Private: Returns true or false if key is a nesting operator
      def nesting_operator?(key)
        NestingOperators.include?(key)
      end

      def modifier?(key)
        Plucky.modifier?(key)
      end
    end
  end
end
