#!/usr/bin/ruby -ws

$d ||= false
$d ||= ENV["DELETE"]
$t ||= false
$t ||= ENV["DELETE_TIMEOUT"]
$m ||= false
$m ||= ENV["MOVE_TIMEOUT"]
$q ||= false
$q ||= ENV["QUIET"]
$v ||= ENV["V"] || "20"

require 'rubygems'
require 'ruby_parser'
require 'fileutils'

$parser_class = case $v
                when "18" then
                  Ruby18Parser
                when "19" then
                  Ruby19Parser
                when "20" then
                  Ruby20Parser
                else
                  abort "Unknown version #{$v.inspect}. Needs to be 18, 19, or 20"
                end

class IO
  RUBY19 = "<3".respond_to? :encoding

  class << self
    alias :binread :read unless RUBY19
  end
end

ARGV.push "-" if ARGV.empty?

class Racc::Parser
  def extract_defs
    ss = lexer.src

    raise "can't access source. possible encoding issue" unless ss

    src = ss.string
    pre_error = src[0...ss.pos]

    defs = pre_error.grep(/^ *(?:def|it)/)

    raise "can't figure out where the bad code starts" unless defs.last

    last_def_indent = defs.last[/^ */]

    post_error = src[ss.pos..-1]
    idx = post_error =~ /^#{last_def_indent}end.*/

    raise "can't figure out where the bad code ends" unless idx

    src = pre_error + post_error[0..idx+$&.length]

    src.scan(/^(( *)(?:def|it) .*?^\2end)/m)
  end

  def retest_for_errors defs
    parser = self.class.new

    parser.process(defs.join("\n\n"))
  rescue SyntaxError, StandardError
    nil
  end
end

def expand path
  if File.directory? path then
    require 'find'

    files = []

    Find.find(*Dir[path]) do |f|
      files << f if File.file? f
    end

    files.sort
  else
    Dir.glob path
  end
end

def process_error parser
  defs = parser.extract_defs

  if parser.retest_for_errors defs then
    warn "Can't reproduce error with just methods, punting..."
    return
  end

  catch :extract_done do
    (1..defs.size).each do |perm_size|
      defs.combination(perm_size).each do |trial|
        unless parser.retest_for_errors trial then
          puts trial.join "\n"
          throw :extract_done
        end
      end
    end
  end
rescue RuntimeError, Racc::ParseError => e
  warn "# process error: #{e.message.strip}"
end

def process file
  ruby = file == "-" ? $stdin.binread : File.binread(file)
  time = (ENV["RP_TIMEOUT"] || 10).to_i

  $stderr.print "# Validating #{file}: "
  parser = $parser_class.new
  parser.process(ruby, file, time)
  warn "good"
  File.unlink file if $d
rescue Timeout::Error
  $exit = 1
  warn "TIMEOUT parsing #{file}. Skipping."

  if $m then
    dir = File.join $m, File.dirname(file)
    FileUtils.mkdir_p dir
    FileUtils.move file, dir
  elsif $t then
    File.unlink file
  end
rescue StandardError, SyntaxError, Racc::ParseError => e
  $exit = 1
  warn ""
  warn "# error: #{e.message.strip}" unless $q
  warn ""
  return if $q

  process_error parser
end

$exit = 0
$stdout.sync = true

ARGV.each do |path|
  expand(path).each do |file|
    next unless File.file? file # omg... why would you name a dir support.rb?
    process file
  end
end

exit $exit
