module Turn
  require 'turn/colorize'
  require 'yaml'
  require 'open3'

  # = IsoRunner
  #
  # Iso Runner provides means from running unit test
  # in isolated processes. It can do this either by running
  # each test in isolation (solo testing) or in pairs (cross testing).
  #
  # The IsoRunner proiveds some variery in ouput formats and can also
  # log results to a file.
  #
  class IsoRunner
    include Turn::Colorize

    attr :reporter

  private

    def initialize
      @config = Turn.config

      @reporter = @config.reporter
      #yield(self) if block_given?
      @loadpath = @config.loadpath
      @requires = @config.requires
      @live     = @config.live?
      @minitest = @config.framework == :minitest
    end

  public

    # Runs the list of test calls passed to it.
    # This is used by #test_solo and #test_cross.
    #
    def start
      suite = TestSuite.new
      testruns = @config.files.collect do |file|
        name = file.sub(Dir.pwd+'/','')
        suite.new_case(name, file)
      end
      test_loop_runner(suite)
    end

  private

    # The IsoRunner actually shells out to turn in
    # manifest mode, to gather results from isolated
    # runs.
    def test_loop_runner(suite)
      reporter.start_suite(suite)

      recase = []

      suite.each_with_index do |kase, index|
        reporter.start_case(kase)

        turn_path = File.expand_path(File.dirname(__FILE__) + '/../bin.rb')

        files = kase.files.map{ |f| f.sub(Dir.pwd+'/', '') }

        # FRACKING GENIUS RIGHT HERE !!!!!!!!!!!!
        cmd = []
        cmd << "ruby"
        cmd << "-I#{@loadpath.join(':')}" unless @loadpath.empty?
        cmd << "-r#{@requires.join(':')}" unless @requires.empty?
        cmd << "--"
        cmd << turn_path
        cmd << "--marshal"
        cmd << %[--loadpath="#{@loadpath.join(':')}"] unless @loadpath.empty?
        cmd << %[--requires="#{@requires.join(':')}"] unless @requires.empty?
        cmd << "--live" if @live
        cmd << "--minitest" if @minitest
        cmd << files.join(' ')
        cmd = cmd.join(' ')

        #out = `#{cmd}`
        #err = ''

        out, err = nil, nil
#p cmd
        Open3.popen3(cmd) do |stdin, stdout, stderr|
          stdin.close
          out = stdout.read.chomp
          err = stderr.read.chomp
        end

        # TODO: how to report? will need to add something to reporter
        # b/c it may have redirected stdout. Or use STDOUT?
        #if !err.empty?
        #  puts err
        #  raise
        #end

        files = kase.files

        # remove any unexpected output injected at the beginning
        b = out.index(/^---/m)
        yaml = out[b..-1]
        sub_suite = YAML.load(yaml)

        # TODO: How to handle pairs?
        #name  = kase.name
        kases = sub_suite.cases
        suite.cases[index] = kases

        kases.each do |kase|
          kase.files = files
          #reporter.start_case(kase)
          kase.tests.each do |test|
            reporter.start_test(test)
            if test.error?
              #reporter.error(test.message)
              reporter.error(test.raised)
            elsif test.fail?
              #reporter.fail(test.message)
              reporter.error(test.raised)
            else
              reporter.pass
            end
            reporter.finish_test(test)
          end
          reporter.finish_case(kase)
        end
      end

      suite.cases.flatten!

      reporter.finish_suite(suite)

      # shutdown auto runner
      if @minitest

      else
        ::Test::Unit.run=true rescue nil
      end

      suite
    end

    #
    #def test_parse_result(result)
    #  if md = /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/.match(result)
    #    count = md[1..4].collect{|q| q.to_i}
    #  else
    #    count = [1, 0, 0, 1]  # SHOULD NEVER HAPPEN
    #  end
    #  return count
    #end

    # NOT USED YET.
    def log_report(report)
      if log #&& !dryrun?
        #logfile = File.join('log', apply_naming_policy('testlog', 'txt'))
        FileUtils.mkdir_p('log')
        logfile = File.join('log', 'testlog.txt')
        File.open(logfile, 'a') do |f|
          f << "= #{self.class} Test @ #{Time.now}\n"
          f << report
          f << "\n"
        end
      end
    end

  end#class IsoRunner

end#module Turn
