# Copyright 2010-2014 Greg Hurrell. All rights reserved.
# Licensed under the terms of the BSD 2-clause license.

require 'command-t/scanner'

module CommandT
  # Reads the current directory recursively for the paths to all regular files.
  #
  # This is an abstract superclass; the real work is done by subclasses which
  # obtain file listings via different strategies (for examples, see the
  # RubyFileScanner and FindFileScanner subclasses).
  class FileScanner < Scanner
    class FileLimitExceeded < ::RuntimeError; end
    attr_accessor :path

    def initialize(path = Dir.pwd, options = {})
      @paths                = {}
      @paths_keys           = []
      @path                 = path
      @max_depth            = options[:max_depth] || 15
      @max_files            = options[:max_files] || 30_000
      @max_caches           = options[:max_caches] || 1
      @scan_dot_directories = options[:scan_dot_directories] || false
      @wild_ignore          = options[:wild_ignore]
      @base_wild_ignore     = wild_ignore
    end

    def paths
      @paths[@path] ||= begin
        ensure_cache_under_limit
        @prefix_len = @path.chomp('/').length + 1
        set_wild_ignore { paths! }
      end
    end

    def flush
      @paths = {}
    end

  private

    def wild_ignore
      VIM::exists?('&wildignore') && ::VIM::evaluate('&wildignore').to_s
    end

    def paths!
      raise RuntimeError, 'Subclass responsibility'
    end

    def ensure_cache_under_limit
      # Ruby 1.8 doesn't have an ordered hash, so use a separate stack to
      # track and expire the oldest entry in the cache
      if @max_caches > 0 && @paths_keys.length >= @max_caches
        @paths.delete @paths_keys.shift
      end
      @paths_keys << @path
    end

    def path_excluded?(path, prefix_len = @prefix_len)
      if apply_wild_ignore?
        # first strip common prefix (@path) from path to match VIM's behavior
        path = path[prefix_len..-1]
        path = VIM::escape_for_single_quotes path
        ::VIM::evaluate("empty(expand(fnameescape('#{path}')))").to_i == 1
      end
    end

    def has_custom_wild_ignore?
      @wild_ignore && !@wild_ignore.empty?
    end

    # Used to skip expensive calls to `expand()` when there is no applicable
    # wildignore.
    def apply_wild_ignore?
      has_custom_wild_ignore? || @base_wild_ignore
    end

    def set_wild_ignore(&block)
      ::VIM::command("set wildignore=#{@wild_ignore}") if has_custom_wild_ignore?
      yield
    ensure
      ::VIM::command("set wildignore=#{@base_wild_ignore}") if has_custom_wild_ignore?
    end
  end # class FileScanner
end # module CommandT
