#!/usr/bin/env ruby
# encoding: UTF-8

require 'tins/xt'
require 'tins/lines_file'
include Tins::GO
require 'utils'
include Utils
require 'drb'

def usage
  puts <<-EOT
Usage: #{File.basename($0)} [OPTS] [FILENAME[:LINENO]]

Options are

  -n TESTNAME   run the test TESTNAME in file FILENAME
  -t FRAMEWORK  use test framework FRAMEWORK (rspec, test-unit or cucumber)
  -c            start probe as a client
  -C FOO[=BAR]  set/get env variable on probe server
  -l            start probe as a server
  -u URI        use this DRb URI communication
  -h            display this help

Version is #{File.basename($0)} #{Utils::VERSION}.
  EOT
  exit 1
end

def cmd(*args)
  if ENV.key?('BUNDLE_GEMFILE')
    args.unshift 'bundle', 'exec'
  end
  puts args * ' '
  system(*args) or exit $?.exitstatus
end

def find_cmd(*cmds, on_fail: -> *cmds { raise fail "no #{cmds * '|'} command found" })
  cmds.map { |c| `which #{c}`.full?(:chomp) }.compact.first or on_fail.(*cmds)
end

def spring?
  `bin/spring status`.lines.first =~ /^Spring is running:/
rescue Errno::ENOENT
  false
end
singleton_class.class_eval do
  memoize_function :spring?
end

def start_server
  Thread.abort_on_exception = $DEBUG
  spring? and
    puts "Found spring running, I'll try to use it for running tests."

  begin
    DRb.start_service
    DRbObject.new_with_uri($uri).shutdown
  rescue DRb::DRbConnError
  end

  Utils::ProbeServer.new($uri).start
end

def connect_server
  DRb.start_service
  probe_server = DRbObject.new_with_uri($uri)
  if setting = $opts[?C]
    case setting
    when /\A([^=]+)=([^=]+)\z/
      probe_server.env[$1] = $2
    when /\A([^=]+)\z/
      puts probe_server.env[$1]
    else
      usage
    end
  end
  if $opts[?c]
    opts = $opts.subhash('n', 't').each_with_object([]) { |(k, v), a|
      v.full? and a.concat [ "-#{k}", v ]
    }
    probe_server.enqueue opts + $args
    exit
  end
end

$config = Utils::ConfigFile.new
$config.configure_from_paths
testrunner_args = []
if i = ARGV.index('--')
  testrunner_args.concat ARGV[(i + 1)..-1]
  $args = ARGV[0...i]
else
  $args = ARGV.dup
end
$opts = go 'lct:n:u:C:h', $args
$opts[?h] and usage

$uri = $opts[?u] || 'drbunix:probe.socket'

case
when $opts[?l]
  start_server
  exit
when $opts[?c], $opts[?C]
  connect_server
end

$args.empty? and exit
puts "Running tests in #{$args.inspect}"

case ($opts[?t] || $config.probe.test_framework).to_sym
when :rspec
  case
  when spring?
    rspec = %w[ bin/spring rspec ]
  else
    rspec = [ find_cmd('rspec', 'spec') ]
  end
  rspec << '-rutils' << '-f' << 'Utils::LineFormatter'
  $args = $args.map do |a|
    if Utils::Editor::FILE_LINENUMBER_REGEXP =~ a
      $~.captures.compact * ':'
    else
      a
    end
  end
  cmd 'ruby', '-I', $config.probe.include_dirs_argument, *rspec,
    *($args + testrunner_args)
when :'test-unit'
  if testname = $opts[?n]
    cmd 'ruby', '-I', $config.probe.include_dirs_argument,
      *($args + testrunner_args), '-n', testname
  else
    $args = $args.inject([]) { |args, a|
      args.push(*File.directory?(a) ? Dir[File.join(a, ?*)] : a)
    }
    for filename in $args
      sl = filename.source_location
      if sl.linenumber
        lf = Tins::LinesFile.for_filename(*sl)
        if testname = lf.match_backward(/def\s+(\S+?)(?:\(|\s*$)/).full?(:first)
          cmd 'ruby', '-I', $config.probe.include_dirs_argument,
            sl.filename, *testrunner_args, '-n', testname
        else
          warn "no test found before line #{sl.linenumber}"
        end
      else
        cmd 'ruby', '-I', $config.probe.include_dirs_argument,
          sl.filename, *testrunner_args
      end
    end
  end
when :cucumber
  cucumber = find_cmd('cucumber')
  if linenumber = $opts[?n]
    cmd 'ruby', cucumber, '-r', $config.probe.include_dirs_argument, '-l',
      linenumber, *($args + testrunner_args)
  else
    $args = $args.map do |a|
      if Utils::Editor::FILE_LINENUMBER_REGEXP =~ a
        $~.captures.compact * ':'
      else
        a
      end
    end
    cmd 'ruby', cucumber, '-r', $config.probe.include_dirs_argument,
      *($args + testrunner_args)
  end
end