#!/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] [FILENAME]

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            start probe as a client console
  -l            start probe as a server
  -p PORT       listen on/connect to local port PORT
  -h            display this help

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

def cmd(*args)
  puts args * ' '
  system(*args) or exit $?.exitstatus
end

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

$config = Utils::Config::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
$opt = go 'lcCp:t:n:h', $args
$opt['h'] and usage

$uri = "druby://localhost:#{$opt['p'] || 6623}"

def start_server
  Thread.abort_on_exception = $DEBUG

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

  puts "Starting probe server listening to #{$uri.inspect}."
  DRb.start_service($uri, Utils::ProbeServer.new)
  begin
    DRb.thread.join
  rescue Interrupt
    warn " *** Interrupted ***"
    exit
  end
end

def connect_server
  puts "Connecting probe server on #{$uri.inspect}."
  DRb.start_service
  probe_server = DRbObject.new_with_uri($uri)
  if $opt['c']
    opts = $opt.subhash('n', 't').map { |k, v|
      v.full? { "-#{k} #{v.inspect}" }
    }.compact
    probe_server.enqueue opts + $args
    exit
  elsif $opt['C']
    ARGV.clear
    Tins::IRB.examine probe_server
    exit
  end
end

case
when $opt['l']
  start_server
else
  connect_server
end

$args.empty? and fail "require filename or filename:linenumber as arguments"
puts "Running tests in #{$args.inspect}"

case ($opt['t'] || $config.probe.test_framework).to_sym
when :rspec
  rspec = find_cmd('rspec', 'spec')
  if linenumber = $opt['n']
    cmd 'ruby', '-I', $config.probe.include_dirs_argument, rspec, '-l',
      linenumber, *($args + testrunner_args)
  else
    $args = $args.map do |a|
      if Utils::Editor::FILE_LINENUMBER_REGEXP =~ a
        $~.captures * ':'
      else
        a
      end
    end
    cmd 'ruby', '-I', $config.probe.include_dirs_argument, rspec,
      *($args + testrunner_args)
  end
when :'test-unit'
  testrb = find_cmd('testrb')
  if testname = $opt['n']
    cmd 'ruby', '-I', $config.probe.include_dirs_argument, testrb,
      '-n', testname, *($args + testrunner_args)
  else
    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, testrb,
            '-n', testname, sl.filename, *testrunner_args
        else
          warn "no test found before line #{sl.linenumber}"
        end
      else
        cmd 'ruby', '-I', $config.probe.include_dirs_argument, testrb,
          sl.filename, *testrunner_args
      end
    end
  end
when :cucumber
  cucumber = find_cmd('cucumber')
  if linenumber = $opt['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 * ':'
      else
        a
      end
    end
    cmd 'ruby', cucumber, '-r', $config.probe.include_dirs_argument,
      *($args + testrunner_args)
  end
end