module LintTrappings
  # Miscellaneus collection of helper functions.
  module Utils
    module_function

    # Returns whether a glob pattern (or any of a list of patterns) matches the
    # specified file.
    #
    # This is defined here so our file globbing options are consistent
    # everywhere we perform globbing.
    #
    # @param glob [String, Array]
    # @param file [String]
    # @return [Boolean]
    def any_glob_matches?(globs_or_glob, file)
      Array(globs_or_glob).any? do |glob|
        ::File.fnmatch?(glob, file,
                        ::File::FNM_PATHNAME | # Wildcards don't match path separators
                        ::File::FNM_DOTMATCH)  # `*` wildcard matches dotfiles
      end
    end

    # Find all consecutive items satisfying the given block of a minimum size,
    # yielding each group of consecutive items to the provided block.
    #
    # @param items [Array]
    # @param satisfies [Proc] function that takes an item and returns true/false
    # @param min_consecutive [Fixnum] minimum number of consecutive items before
    #   yielding the group
    # @yield Passes list of consecutive items all matching the criteria defined
    #   by the `satisfies` {Proc} to the provided block
    # @yieldparam group [Array] List of consecutive items
    # @yieldreturn [Boolean] block should return whether item matches criteria
    #   for inclusion
    def for_consecutive_items(items, satisfies, min_consecutive = 2)
      current_index = -1

      while (current_index += 1) < items.count
        next unless satisfies[items[current_index]]

        count = count_consecutive(items, current_index, &satisfies)
        next unless count >= min_consecutive

        # Yield the chunk of consecutive items
        yield items[current_index...(current_index + count)]

        current_index += count # Skip this patch of consecutive items to find more
      end
    end

    # Count the number of consecutive items satisfying the given {Proc}.
    #
    # @param items [Array]
    # @param offset [Fixnum] index to start searching from
    # @yield [item] Passes item to the provided block.
    # @yieldparam item [Object] Item to evaluate as matching criteria for
    #   inclusion
    # @yieldreturn [Boolean] whether to include the item
    # @return [Integer]
    def count_consecutive(items, offset = 0)
      count = 1
      count += 1 while (offset + count < items.count) && yield(items[offset + count])
      count
    end

    # Convert a class name or CamelCase string into snake_case.
    #
    # @see stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby
    #
    # @param str [String]
    #
    # @return [String]
    def snake_case(str)
      str.gsub(/::/, '/')
         .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
         .gsub(/([a-z\d])([A-Z])/, '\1_\2')
         .tr('-', '_')
         .downcase
    end

    # Converts a string containing underscores/hyphens/spaces into CamelCase.
    #
    # @param str [String]
    #
    # @return [String]
    def camel_case(str)
      str.split(/_|-| /).map { |part| part.sub(/^\w/, &:upcase) }.join
    end

    # Returns the plural of the word if necessary based on the given count.
    #
    # @param word [String]
    # @param count [Integer]
    #
    # @return [String]
    def pluralize(word, count)
      count == 1 ? word : "#{word}s"
    end

    # Strips off excess leading indentation from each line so we can use Heredocs
    # for writing code without having the leading indentation count.
    def normalize_indent(code)
      leading_indent = code[/^(\s*)/, 1]
      code.lstrip.gsub(/\n#{leading_indent}/, "\n")
    end

    # Calls a block of code with a modified set of environment variables,
    # restoring them once the code has executed.
    #
    # @param env [Hash] environment variables to set
    def with_environment(env)
      old_env = {}
      env.each do |var, value|
        old_env[var] = ENV[var.to_s]
        ENV[var.to_s] = value
      end

      yield
    ensure
      old_env.each { |var, value| ENV[var.to_s] = value }
    end
  end
end
