# File lib/deep_merge/core.rb, line 78
  def self.deep_merge!(source, dest, options = {})
    # turn on this line for stdout debugging text
    merge_debug = options[:merge_debug] || false
    overwrite_unmergeable = !options[:preserve_unmergeables]
    knockout_prefix = options[:knockout_prefix] || nil
    raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!" if knockout_prefix == ""
    raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!" if knockout_prefix && !overwrite_unmergeable
    # if present: we will split and join arrays on this char before merging
    array_split_char = options[:unpack_arrays] || false
    # request that we avoid merging arrays
    overwrite_arrays = options[:overwrite_arrays] || false
    # request that we sort together any arrays when they are merged
    sort_merged_arrays = options[:sort_merged_arrays] || false
    # request that arrays of hashes are merged together
    merge_hash_arrays = options[:merge_hash_arrays] || false
    # request to extend existing arrays, instead of overwriting them
    extend_existing_arrays = options[:extend_existing_arrays] || false
    # request that arrays keep duplicate elements
    keep_array_duplicates = options[:keep_array_duplicates] || false
    # request that nil values are merged or skipped (Skipped/false by default)
    merge_nil_values = options[:merge_nil_values] || false

    di = options[:debug_indent] || ''
    # do nothing if source is nil
    return dest if !merge_nil_values && source.nil?
    # if dest doesn't exist, then simply copy source to it
    if !(dest) && overwrite_unmergeable
      dest = source; return dest
    end

    puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
    if source.kind_of?(Hash)
      puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
      source.each do |src_key, src_value|
        if dest.kind_of?(Hash)
          puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
          if dest[src_key]
            puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
            dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + '  '))
          else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
            puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
            # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
            begin
              src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
            rescue TypeError
              src_dup = src_value
            end
            dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + '  '))
          end
        elsif dest.kind_of?(Array) && extend_existing_arrays
          dest.push(source)
        else # dest isn't a hash, so we overwrite it completely (if permitted)
          if overwrite_unmergeable
            puts "#{di}  overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over->  #{dest.inspect}" if merge_debug
            dest = overwrite_unmergeables(source, dest, options)
          end
        end
      end
    elsif source.kind_of?(Array)
      puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
      if overwrite_arrays
        puts "#{di} overwrite arrays" if merge_debug
        dest = source
      else
        # if we are instructed, join/split any source arrays before processing
        if array_split_char
          puts "#{di} split/join on source: #{source.inspect}" if merge_debug
          source = source.join(array_split_char).split(array_split_char)
          if dest.kind_of?(Array)
            dest = dest.join(array_split_char).split(array_split_char)
          end
        end
        # if there's a naked knockout_prefix in source, that means we are to truncate dest
        if knockout_prefix && source.index(knockout_prefix)
          dest = clear_or_nil(dest); source.delete(knockout_prefix)
        end
        if dest.kind_of?(Array)
          if knockout_prefix
            print "#{di} knocking out: " if merge_debug
            # remove knockout prefix items from both source and dest
            source.delete_if do |ko_item|
              retval = false
              item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item
              if item != ko_item
                print "#{ko_item} - " if merge_debug
                dest.delete(item)
                dest.delete(ko_item)
                retval = true
              end
              retval
            end
            puts if merge_debug
          end
          puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
          source_all_hashes = source.all? { |i| i.kind_of?(Hash) }
          dest_all_hashes = dest.all? { |i| i.kind_of?(Hash) }
          if merge_hash_arrays && source_all_hashes && dest_all_hashes
            # merge hashes in lists
            list = []
            dest.each_index do |i|
              list[i] = deep_merge!(source[i] || {}, dest[i],
                                    options.merge(:debug_indent => di + '  '))
            end
            list += source[dest.count..-1] if source.count > dest.count
            dest = list
          elsif keep_array_duplicates
            dest = dest.concat(source)
          else
            dest = dest | source
          end
          dest.sort! if sort_merged_arrays
        elsif overwrite_unmergeable
          puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
          dest = overwrite_unmergeables(source, dest, options)
        end
      end
    else # src_hash is not an array or hash, so we'll have to overwrite dest
      if dest.kind_of?(Array) && extend_existing_arrays
        dest.push(source)
      else
        puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
        dest = overwrite_unmergeables(source, dest, options)
      end
    end
    puts "#{di}Returning #{dest.inspect}" if merge_debug
    dest
  end