require 'rubygems'
gem 'ruby_parser'
# >= 2.11.8 because of https://github.com/cucumber/gherkin/commit/90121f513000b89fce4d24c4e7dfdae00a5b177f
gem 'gherkin', '>= 2.11.8', '< 4.0.0'

require 'ruby_parser'
require 'yaml'
require 'gherkin'
require 'gherkin/formatter/json_formatter'

class StepExtractor < Gherkin::Formatter::JSONFormatter
  def initialize
    super(StringIO.new)
  end

  # returns a Hash describing step at the line or nil if nothing
  # executable found
  def step_at(line)
    @feature_hashes[0]['elements'].each do |element|
      element['steps'].each do |step|
        if step['line'] == line
          if element['examples']
            rows = element['examples'][0]['rows'].each
            header = rows.next['cells']
            examples = []
            loop do
              row = rows.next['cells']
              example = {}
              row.each_with_index do |val, idx|
                example[header[idx]] = val
              end
              examples.push(example)
            end
            step['examples'] = examples
          end
          return step
        end
      end
    end
    nil
  end
end

class Step
  attr_reader :file, :line, :regexp
  def initialize(regexp, file, line)
    @file, @line = file, line
    self.regexp = regexp
  end

  def regexp=(value)
    @regexp =
      case value
      when String
        pieces, regexp = [], value.dup
        regexp.gsub!(/\$\w+/) { |match| pieces << match; "TOKEN" }
        regexp.gsub!(/(?=\{)(.*?)(\})/, "TOKEN")
        regexp = Regexp.escape(regexp)
        regexp.gsub!(/TOKEN/) { |match| "(.*)" }
        Regexp.new("^#{regexp}$")
      when Regexp
        value
      else
        STDERR.puts "Warning: invalid parameter to Given/When/Then on #{file}:#{line}.  Expected Regexp or String, got #{value.class} #{value.inspect}"
        Regexp.new(/^INVALID PARAM$/)
      end
  end

  def match?(text)
    @regexp.match(text)
  end
end

class StepParser
  attr_accessor :steps, :file

  def initialize(file, keywords)
    @file = file
    @steps = []
    @keywords = keywords
    extract_steps(RubyParser.new.parse(File.read(file)))
  end

  def extract_steps(sexp)
    return unless sexp.is_a?(Sexp)
    case sexp.first
    when :block
      sexp[1..-1].each do |child_sexp|
        extract_steps(child_sexp)
      end
    when :iter
      child_sexp = sexp[1]
      return unless child_sexp[0] == :call && @keywords.include?(child_sexp[2])
      regexp = child_sexp[3][1]
      @steps << Step.new(regexp, file, child_sexp.line)
    else
      sexp.each do |child|
        extract_steps(child)
      end
    end
  end
end

iso_code, feature_path, line, step_search_path, step_search_path_extra, = ARGV
extractor = StepExtractor.new
parser = Gherkin::Parser::Parser.new(extractor, true, 'root', false, iso_code)
parser.parse(IO.read(feature_path), feature_path, 0)
step_info = extractor.step_at(line.to_i)
inputs = if step_info['examples']
  # If the step has example placeholders, we should substitute them
  # and provide user a choice
  placeholders = step_info['examples'].first.keys.map { |key| %r(<(#{key})>) }
  step_info['examples'].map do |example|
    step_info['name'].gsub(Regexp.union(placeholders)) do |p|
      example[p.gsub(/[<>]/, '')]
    end
  end.uniq
else
  [step_info['name']]
end
quick_exit = inputs.size == 1

def results_to_alist(results)
  results.uniq! { |(_, file, line)| [file, line]}
  pairs = results.reduce("") do |acc, (input, file, line)|
    acc << sprintf("(%s . %s)", input.inspect, [file, line].join(":").inspect)
  end
  "(#{pairs})"
end

results = []
keywords = Gherkin::I18n.get(iso_code).step_keywords[1..-1].map { |k| k.strip.to_sym }
files = Dir[step_search_path]
files.push(*Dir[step_search_path_extra]) if !step_search_path_extra.nil?
files.each_with_index do |file, i|
  StepParser.new(file, keywords).steps.each do |step|
    inputs.each do |input_text|
      if step.match?(input_text)
        results.push([input_text, step.file, step.line])
        if quick_exit
          print results_to_alist(results)
          exit
        end
      end
    end
  end
end
print results_to_alist(results)
